DEV Community

Quinn Zhu
Quinn Zhu

Posted on

Write a Blast-Radius File Before Your First AI Patch

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.

Why day-one AI diffs explode

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.

What you will build today

You will add two artifacts on your branch. Keep both files in the first PR.

  1. blast_radius.py — the contract for this change.
  2. 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.

Step 1: Freeze one target file

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"
Enter fullscreen mode Exit fullscreen mode

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.

Step 2: Write the blast-radius contract

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",
}
Enter fullscreen mode Exit fullscreen mode

missing_means: old_path is the fail-closed rule. A missing flag must not enable new behavior. That single line is the drill.

Step 3: Add a failing test first

Do not ask a model for the feature yet. Write the test that proves the old path.

# 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
Enter fullscreen mode Exit fullscreen mode

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.

Step 4: Draft only inside the fence

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.
Enter fullscreen mode Exit fullscreen mode

Reject any answer that adds extra files. Reject lockfile churn without discussion. Reject "while we are here" cleanups on sight.

Step 5: Install the checker

Save this as scripts/check_blast_radius.py. Run it with Python 3.

#!/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())
Enter fullscreen mode Exit fullscreen mode
python3 scripts/check_blast_radius.py
Enter fullscreen mode Exit fullscreen mode

The script is strict on purpose. It should fail your first attempt. That failure is the actual lesson here.

Step 6: Rehearse the revert on a branch

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Never force-push this proof to main. Keep the revert commit on the drill branch. Copy only the lesson into your real PR.

Step 7: Open a PR that quotes the contract

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
Enter fullscreen mode Exit fullscreen mode

Ask the reviewer one question only today. Do not ask them to love the design.

Decision table for model pressure

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.

Common failure modes

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
Enter fullscreen mode Exit fullscreen mode

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.

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)
Enter fullscreen mode Exit fullscreen mode

Treat that snippet as unexecuted sample code only. Wire it to your real totals function after tests exist.

Limitations you must accept

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.

Who should not use this drill

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.

What you should leave with

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.

Top comments (0)