Write a Blast-Radius File Before Your First AI Patch A developer at MonkeyCode outlines a fail-closed strategy for introducing AI-assisted code changes, emphasizing the creation of a blast-radius file before any patch. The approach requires a reversible change, a test proving the legacy path, and a checker script as the sole merge gate. Your first AI patch should fail closed today. Do not ship a feature on day one. Prove one target file can revert cleanly now. You joined a messy repo this morning. The assistant wants a wide rewrite. Your job is a tiny reversible cut only. This drill gives you a blast-radius file first. You fill it before any model writes code. Then a short script checks the revert path. Cheap code is not cheap to unwind. One extra import can touch auth. One extra migration can lock deploys. You will not know the architecture yet. You also should not pretend otherwise. A blast-radius file makes unknowns explicit fast. If the file cannot name a revert, stop. You do not prompt for more code. You shrink the change until revert is boring. You will add two artifacts on your branch. Keep both files in the first PR. blast radius.py — the contract for this change. scripts/check blast radius.py — the fail-closed proof.The contract is the source of truth. The checker is the only merge gate. No green checker means no review yet. Pick one production file you can read. Do not pick a whole folder. Do not pick generated vendor code. git ls-files ' .py' ' .ts' ' .go' | head -n 40 TARGET=src/billing/invoice.py wc -l "$TARGET" git log -n 5 --oneline -- "$TARGET" Read the last five commits on that file. Write two plain sentences in notes. Pick a smaller file if you cannot yet. You now have a hard fence. Everything outside that fence is forbidden. Your assistant may not cross it. Create blast radius.py at the repo root. Keep the dict small. Fill every field with your own hands. blast radius.py Day-one contract. Humans edit this. Models do not. BLAST = { "change id": "day-one-001", "intent": "Add a fail-closed guard on invoice totals.", "target files": "src/billing/invoice.py", "tests/billing/test invoice flag off.py", "blast radius.py", "scripts/check blast radius.py", , "forbidden globs": "src/auth/ ", " /migrations/ ", "package-lock.json", "go.sum", "poetry.lock", , "feature flag": { "name": "INVOICE GUARD V1", "default": "off", "missing means": "old path", }, "revert": { "strategy": "git revert", "notes": "Flag off restores the prior totals path.", }, "tests": "pytest tests/billing/test invoice flag off.py -q", , "max diff lines": 80, "max files": 4, "base ref": "origin/main", } missing means: old path is the fail-closed rule. A missing flag must not enable new behavior. That single line is the drill. Do not ask a model for the feature yet. Write the test that proves the old path. python tests/billing/test invoice flag off.py import os from billing.invoice import compute total def test missing flag uses legacy total monkeypatch : monkeypatch.delenv "INVOICE GUARD V1", raising=False assert "INVOICE GUARD V1" not in os.environ assert compute total 100, 20 , tax=0.1 == 120.0 def test flag off uses legacy total monkeypatch : monkeypatch.setenv "INVOICE GUARD V1", "off" assert compute total 100, 20 , tax=0.1 == 120.0 Run that file once before any patch. Watch it fail for a real reason. Put the exact command in BLAST "tests" . If the test cannot run locally, stop here. Fix the harness before any AI edit. A junior without a test command has no proof. Now you may use an assistant carefully. Feed it one file, not the tree. Paste BLAST as the hard constraint. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. Use that loop to draft a patch against invoice.py only. You still type every revert field yourself. Keep the prompt short and strict. Read blast radius.py. Edit only BLAST "target files" . Do not touch BLAST "forbidden globs" . Honor feature flag default = off. A missing flag must use the old path. Keep the diff under max diff lines. Return a unified diff, nothing else. Reject any answer that adds extra files. Reject lockfile churn without discussion. Reject "while we are here" cleanups on sight. Save this as scripts/check blast radius.py . Run it with Python 3. bash /usr/bin/env python3 """Fail the PR when the diff escapes the blast radius.""" from future import annotations import fnmatch import subprocess import sys from pathlib import Path ROOT = Path file .resolve .parents 1 sys.path.insert 0, str ROOT from blast radius import BLAST noqa: E402 def git args: str - str: return subprocess.check output "git", args , cwd=ROOT, text=True def main - int: base ref = BLAST "base ref" try: base = git "merge-base", "HEAD", base ref .strip except subprocess.CalledProcessError: print f"cannot resolve merge-base with {base ref}", file=sys.stderr return 2 names = n for n in git "diff", "--name-only", base .splitlines if n allowed = set BLAST "target files" forbidden = BLAST "forbidden globs" for name in names: for pat in forbidden: if fnmatch.fnmatch name, pat : print f"forbidden path in diff: {name}" return 1 if name not in allowed: print f"file outside blast radius: {name}" return 1 if len names BLAST "max files" : print f"too many files: {len names }" return 1 changed = 0 for row in git "diff", "--numstat", base .splitlines : if not row.strip : continue added, deleted, name = row.split "\t", 2 if added == "-" or deleted == "-": print f"binary file not allowed: {name}" return 1 changed += int added + int deleted if changed BLAST "max diff lines" : print f"diff too large: {changed} lines" return 1 flag = BLAST "feature flag" if flag.get "default" = "off": print "feature flag must default off" return 1 if flag.get "missing means" = "old path": print "missing flag must mean old path" return 1 print "blast-radius contract: ok" for cmd in BLAST "tests" : print f"+ {cmd}" subprocess.check call cmd, shell=True, cwd=ROOT print "fail-closed checks passed" return 0 if name == " main ": raise SystemExit main python3 scripts/check blast radius.py The script is strict on purpose. It should fail your first attempt. That failure is the actual lesson here. Do this on a throwaway branch today. Do not wait for production traffic later. git checkout -b drill/day-one-guard git add blast radius.py scripts/check blast radius.py \ src/billing/invoice.py tests/billing/test invoice flag off.py git commit -m "feat: invoice guard behind fail-closed flag" python3 scripts/check blast radius.py SHA=$ git rev-parse HEAD git revert --no-edit "$SHA" INVOICE GUARD V1=off pytest tests/billing/test invoice flag off.py -q git log -n 3 --oneline You must see the old test pass after revert. If it fails, the flag is not fail-closed. Fix that before you open any PR. Reset the rehearsal when the proof is green. git checkout -B drill/day-one-guard "$SHA" Never force-push this proof to main. Keep the revert commit on the drill branch. Copy only the lesson into your real PR. Your PR body should quote the blast-radius fields. Reviewers need the fence, not a long story. Blast radius - Files: src/billing/invoice.py - Flag: INVOICE GUARD V1 default off - Missing flag: legacy totals - Revert: git revert of this SHA Proof - python3 scripts/check blast radius.py - pytest tests/billing/test invoice flag off.py -q Ask the reviewer one question only today. Do not ask them to love the design. Use this table when the assistant argues for more files. | Model request | You do | Why it fails closed | |---|---|---| | Touch auth "just in case" | Refuse | Forbidden path | | Add a migration | Refuse | Not reversible today | | Rename a package | Refuse | Diff too wide | | Refresh a lockfile | Refuse | Hidden blast radius | | Default the flag on | Refuse | Not fail-closed | | Edit one function plus tests | Allow | Inside the fence | | Split work into two PRs | Allow | Shrinks revert | Print the table beside your editor. Point at it when the model rambles on. Your job is the fence, not speed. The checker says it cannot resolve merge-base. Fetch main, then retry the script once. git fetch origin main git rev-parse origin/main python3 scripts/check blast radius.py The checker says a file sits outside blast radius. You added a helper file by accident. Either shrink the patch or update target files with intent. The old test still passes with the flag on. Your new path is not isolated yet. Put the new logic behind the env read first. python import os Proposal only. Unexecuted sample for a fail-closed read. def compute total items, tax : flag = os.getenv "INVOICE GUARD V1", "off" if flag = "on": return sum items 1 + tax return guarded total items, tax Treat that snippet as unexecuted sample code only. Wire it to your real totals function after tests exist. This drill will not teach system architecture. It will not catch semantic money bugs. It only bounds files, flags, and revert commands. The script trusts origin/main as merge base. Forks with origin/master must change base ref now. You need git, Python 3, and pytest on PATH. A comment is not a feature flag. An env var is a minimum bar. If your team has a flag service, use that name instead. Free model access does not replace human review. A free server does not own production. You still run the tests locally. Do not use this as a senior design review. Do not use it during live incident response. Do not use it to rubber-stamp generated refactors. Skip it if you cannot run tests locally. Skip it if revert needs a force-push. Skip it if the change is a data migration. Security patches need a different fence. Secret rotation is not a day-one AI drill. Ask a teammate before those changes. You leave with a tiny, reversible PR. You also leave with a revert you already ran. That is enough work for day one. Tomorrow you may widen the fence one file. You still start from blast radius.py . Cheap code stays cheap only when revert is boring.