# Production Readiness Audit: five rules and a five-check script (free sample)

Version 1.0, 2026-10-28. By PaperChaseWebb. For anyone about to put an agent workflow in front of real customers, built with Grok Bot, Claude Code, Codex, Hermes or anything else. One Markdown file and one Python 3 script, standard library only. The script was tested on its worked example (5 findings, then 0) and on a missing file (exit 2). It was not run inside any agent harness or on a real system.

Readiness checklists are easy to pass. "Rollback plan: yes." "Error handling: yes." Signed by the person who built it. The problems show up later: a rollback nobody had tried, a gap nobody owned, an agent that did whatever an email told it to. These five rules ask for proof on the points that hurt most, and the script checks them in one small file.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('audit_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 audit_mini.py audit-lite.json
```

## Five rules

1. **A pass needs evidence.** Name a file: a config excerpt, a log sample, a test output, a drill note. "Works" and "tested" are not evidence.
2. **Every gap has a named owner and a due date.** Not "TBD", not "the team".
3. **Roll back once on purpose before launch.** Write down the date it passed.
4. **Test refusals.** Give the agent something it must decline (a price it should not promise, an instruction hidden in an email, a stop request) and record the result. All of them pass.
5. **Someone who did not build it signs the verdict.** A colleague, the owner, or a second agent that had no hand in the build.

## The five checks

| Id | Check |
|---|---|
| M1 | every pass names real evidence, not a word like "works" |
| M2 | every gap has a named owner and a due date |
| M3 | a rollback drill passed with a real date, not in the future |
| M4 | at least one refusal test, and all of them passed |
| M5 | the verdict is signed by someone other than the builder |

Exit 0 clean, 1 findings, 2 missing or unreadable file. The script only reads.

## Worked example

A made-up invoice reminder agent, about to go live. Before:

```json
{
 "system": "invoice reminder agent (made up)",
 "builder": "Jo Builder",
 "signed_by": "Jo Builder",
 "areas": [
  {"area": "failure_handling", "status": "pass", "evidence": "works"},
  {"area": "secrets", "status": "pass", "evidence": "secrets-scan-2026-10-20.txt"},
  {"area": "logging", "status": "gap", "evidence": ""}
 ],
 "gaps": [
  {"area": "logging", "owner": "TBD", "due": ""}
 ],
 "drills": [
  {"kind": "rollback", "result": "pass", "run_date": ""},
  {"kind": "refuse", "result": "fail", "run_date": "2026-10-21"}
 ]
}
```

Output:

```
M1 pass with no real evidence: failure_handling (name a file, log or test output)
M2 gap with no named owner and due date: logging
M3 no rollback drill passed with a real date; roll back once on purpose
M4 no refusal test, or one did not pass; try an out-of-scope request and record the result
M5 the verdict is unsigned or signed by the builder; someone who did not build it signs
5 checks, 5 findings
```

The fixes: a timeout test file for failure handling, Jo named as owner of the logging gap with a due date, the rollback date written down, the refusal prompt fixed and the drill rerun, and Sam (who did not build it) signing. After:

```
5 checks, 0 findings
```

## One prompt to try

Paste into your agent: "Read audit-lite.json. For each area marked pass, open the evidence file it names and tell me in one line whether it actually proves the area. Mark any that do not as gap. Do not sign anything."

## What the full pack adds

The Production Readiness Audit (pp-production-readiness-audit, $39) turns these five rules into a full audit folder: twelve areas with dated evidence files, five areas that block go-live until fixed, gaps, drills (rollback, owner pause, restore, failure, cost stop, alert, refusals), a secrets sheet, an access review, monitors that must have fired once, and fifteen read-only checks. The verdict can never be softer than the open gaps allow. The report is sealed to a hash of the evidence, and the owner's go-live yes is typed at their own terminal and tied to the report hash, so later edits show up. It comes with a skill file for Claude Code, Codex and Hermes, an AGENTS.md snippet, a Grok Bot rules block, ten prompts and 46 tests. If you would rather have it done with you, the $5,000 Install sprint on paperchasewebb.ai ends with this audit, run by us as the outside auditor.

## The script

~~~python
#!/usr/bin/env python3
"""audit_mini.py: five checks on a one-file readiness audit (audit-lite.json). Standard library only, read-only.
Usage: python3 audit_mini.py audit-lite.json    exit 0 clean, 1 findings, 2 missing or broken file"""
import datetime as dt, json, sys

VAGUE = {"", "works", "tested", "done", "yes", "ok", "looks good", "builder says so"}


def day(s):
    try:
        d = dt.date.fromisoformat(str(s or ""))
        return d if d <= dt.date.today() else None
    except ValueError:
        return None


def main(path):
    try:
        a = json.load(open(path, encoding="utf-8"))
    except (OSError, ValueError) as e:
        print(f"cannot read {path}: {e}", file=sys.stderr)
        return 2
    areas, gaps, drills = a.get("areas", []), a.get("gaps", []), a.get("drills", [])
    found = []
    vague = [x.get("area", "?") for x in areas if x.get("status") == "pass" and str(x.get("evidence", "")).strip().lower() in VAGUE]
    if vague:
        found.append(("M1", f"pass with no real evidence: {', '.join(vague)} (name a file, log or test output)"))
    covered = {g.get("area") for g in gaps if str(g.get("owner", "")).strip().lower() not in ("", "tbd") and str(g.get("due", "")).strip()}
    loose = [x.get("area", "?") for x in areas if x.get("status") == "gap" and x.get("area") not in covered]
    if loose:
        found.append(("M2", f"gap with no named owner and due date: {', '.join(loose)}"))
    if not any(x.get("kind") == "rollback" and x.get("result") == "pass" and day(x.get("run_date")) for x in drills):
        found.append(("M3", "no rollback drill passed with a real date; roll back once on purpose"))
    refuse = [x for x in drills if x.get("kind") == "refuse"]
    if not refuse or any(x.get("result") != "pass" for x in refuse):
        found.append(("M4", "no refusal test, or one did not pass; try an out-of-scope request and record the result"))
    signer, builder = str(a.get("signed_by", "")).strip(), str(a.get("builder", "")).strip()
    if not signer or signer.lower() == builder.lower():
        found.append(("M5", "the verdict is unsigned or signed by the builder; someone who did not build it signs"))
    for c, m in found:
        print(c, m)
    print(f"5 checks, {len(found)} findings")
    return 1 if found else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1]) if len(sys.argv) > 1 else 2)
~~~

Made-up example; no real business or results are described.

PaperChaseWebb 💸
