The founder sat at a kitchen table after midnight. Tomorrow's only demo sat on the calendar at nine. An agent had rewritten checkout in a single pass.
The generated diff looked calm, complete, and expensive. Redis appeared without warning in the compose file. A worker and object store arrived as confident defaults.
Solo founders now meet this pattern almost every week. Cheap generation makes heavy architecture feel strangely free. Agents fill silent gaps with popular paid cloud defaults.
The real invoice arrives after the kitchen-table demo. The agent never inspected the founder's actual cash. Zero-bill ships need a door, not another prompt.
The freezer door
This workflow treats the stack as a labeled freezer. Leftovers stay named, and unlabeled food stays out. Generated code must match the labels or the ship fails.
A freeze file lives at the repository root on purpose. YAML keeps the contract boring, greppable, and small. The founder lists only infrastructure that already exists.
# stack-freeze.yml
# Zero-bill contract for a solo ship.
version: 1
allow_languages:
- python
- javascript
allow_packages:
python:
- flask
- gunicorn
- psycopg2-binary
- python-dotenv
javascript:
- express
- pg
deny_services:
- redis
- rabbitmq
- kafka
- s3
- cloudfront
- elasticsearch
- managed_queue
allow_infra:
- sqlite
- postgres
- local_disk
max_monthly_usd: 0
notes: >
This product ships on one free server.
Background jobs must stay in-process.
Files stay on local disk until revenue exists.
Everything absent from that file becomes a suspect later. The scanner should stay dull enough for midnight judgment. Python and regular expressions are enough for this door.
A dull scanner
Save the script beside the freeze file before the demo. The code below is a complete starting point. Readers can copy it into an empty repository tonight.
#!/usr/bin/env python3
"""Fail a ship when generated code invents paid infra."""
from __future__ import annotations
import re
import sys
from pathlib import Path
import yaml
ROOT = Path(".").resolve()
FREEZE = yaml.safe_load((ROOT / "stack-freeze.yml").read_text())
SERVICE_HINTS = {
"redis": [r"\bredis\b", r"REDIS_URL", r"from redis", r"ioredis"],
"rabbitmq": [r"pika", r"amqp://", r"celery"],
"kafka": [r"kafka", r"confluent_kafka"],
"s3": [r"boto3", r"aws-sdk", r"S3_BUCKET", r"aiobotocore"],
"cloudfront": [r"cloudfront"],
"elasticsearch": [r"elasticsearch", r"opensearch"],
"managed_queue": [r"sqs", r"@aws-sdk/client-sqs", r"bullmq"],
}
SKIP_DIRS = {".git", "node_modules", ".venv", "dist", "build", "fixtures"}
def iter_files():
for path in ROOT.rglob("*"):
if not path.is_file():
continue
if any(part in SKIP_DIRS for part in path.parts):
continue
if path.suffix.lower() not in {
".py", ".js", ".ts", ".yml", ".yaml", ".env", ".toml", ".json"
}:
continue
yield path
def scan() -> list[str]:
denied = set(FREEZE.get("deny_services", []))
hits = []
for path in iter_files():
text = path.read_text(errors="ignore")
for service, patterns in SERVICE_HINTS.items():
if service not in denied:
continue
for pat in patterns:
if re.search(pat, text, re.I):
rel = path.relative_to(ROOT)
hits.append(f"{rel}: suspected {service} via /{pat}/")
break
return hits
def main() -> int:
hits = scan()
if not hits:
print("stack-freeze: clean")
return 0
print("stack-freeze: contract broken")
for line in hits:
print(f" - {line}")
print("Refuse the ship. Edit freeze or remove the dependency.")
return 1
if __name__ == "__main__":
sys.exit(main())
Install one dependency and run the checker from the root. Keep the working directory at the repository root.
chmod +x stack_freeze.py
python3 -m pip install pyyaml
./stack_freeze.py
echo $?
A non-zero exit code stops the demo train immediately. Each hit prints one path and one suspected service. No dashboard, queue, or extra vendor enters this loop.
A three-file rehearsal
A reader can rehearse the failure without touching production. Place a tiny checkout module next to the scanner. The module below is labeled as a fixture, not advice.
# fixtures/checkout.py
# Unexecuted fixture that should fail the freeze.
import redis
import boto3
def pay(order_id: str) -> None:
cache = redis.Redis.from_url("redis://localhost:6379/0")
cache.set(f"order:{order_id}", "paid")
s3 = boto3.client("s3")
s3.put_object(Bucket="invoices", Key=f"{order_id}.pdf", Body=b"%PDF")
Run the scanner against that fixture after copying files. The commands below assume all three files already exist.
mkdir -p fixtures
# save checkout.py, stack-freeze.yml, and stack_freeze.py
# temporarily remove "fixtures" from SKIP_DIRS to rehearse
python3 stack_freeze.py
The expected console output looks like the block below. A passing run must not look like this block.
stack-freeze: contract broken
- fixtures/checkout.py: suspected redis via /\bredis\b/
- fixtures/checkout.py: suspected s3 via /boto3/
Refuse the ship. Edit freeze or remove the dependency.
The founder then rewrites payment against the allowed box. Postgres or SQLite then stores the paid order row.
A local folder stores the invoice PDF bytes instead. The same scanner must print a clean line afterward.
# fixtures/checkout_frozen.py
# Unexecuted fixture that should pass the freeze.
from pathlib import Path
import json
DATA = Path("./data/invoices")
def pay(order_id: str) -> None:
DATA.mkdir(parents=True, exist_ok=True)
record = {"id": order_id, "status": "paid"}
with Path("./data/orders.jsonl").open("a") as handle:
handle.write(json.dumps(record) + "\n")
(DATA / f"{order_id}.pdf").write_bytes(b"%PDF-placeholder")
# after replacing the violating module
python3 stack_freeze.py
# expected: stack-freeze: clean
That pair of fixtures is the whole test plan. An exit code of one means the door held. Exit zero means the rewrite respected the freeze.
Blunt knives
Regular expressions remain a blunt but honest kitchen knife. Agents hide Redis behind a polite cache wrapper class. They import object-storage clients inside a utilities module.
The freeze file still catches the forbidden string itself. Comments can also trip the same patterns after midnight. That false-positive trade is acceptable at zero budget.
A second pass may ask a coding model for judgment. The prompt must stay narrow or architecture creeps back. The model only classifies freeze hits without redesigns.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. The freeze review can use that model and server when present.
The freeze file still works if that product is absent. Ship decisions stay with the YAML contract first, always. The script below is an unexecuted example, not a benchmark.
# review_freeze.py
# Unexecuted example. Operator supplies a real free-model URL.
import json
import urllib.request
from pathlib import Path
PROMPT = """You are a stack-freeze reviewer for a solo founder.
Budget is zero dollars per month.
Allowed infra: {allow}
Denied services: {deny}
Here are scanner hits:
{hits}
Reply with JSON only:
{{"ship": true, "reason": "one sentence", "drop": ["service"]}}
Do not propose new vendors. Do not invent quotas.
"""
def ask_model(hits, freeze, endpoint):
body = json.dumps({
"messages": [{
"role": "user",
"content": PROMPT.format(
allow=", ".join(freeze["allow_infra"]),
deny=", ".join(freeze["deny_services"]),
hits="\n".join(hits),
),
}]
}).encode()
req = urllib.request.Request(
endpoint,
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode())
The review script talks to an HTTP endpoint the operator supplies. The operator swaps the URL and invents no quotas. If the model call fails, the scanner still owns the ship.
Hooks and a quiet box
A local git hook keeps the ritual on the laptop. The hook is a shell one-liner around the Python scanner.
# .git/hooks/pre-commit
#!/bin/sh
set -e
python3 stack_freeze.py
chmod +x .git/hooks/pre-commit
A free server can repeat the same check after dark. Cron clones or updates the repo and appends a log. Email stays optional because a local file is enough.
# crontab -e (example, times are local)
15 3 * * * cd /srv/app && git pull --ff-only && python3 stack_freeze.py >> /var/log/stack-freeze.log 2>&1
The log is a diary of invented services, not quality. It proves the freezer door stayed shut overnight. It does not prove the checkout path is correct.
Ugly paths that still ship
Indie constraints write the rules in plain language. SQLite may replace Postgres on the first public day. In-process tasks replace workers until a customer pays.
Local disk replaces object storage for invoice PDFs. These choices look ugly and still ship at nine. Paid infrastructure can wait for a real customer invoice.
The freeze file also blocks well-meant performance helpers. An agent may add a CDN for imagined scale. A solo site with ten users does not need one.
The deny list simply says no for now. The founder can edit the YAML in daylight later. Editing the file is a conscious unfreeze, not drift.
Some agents also invent auth providers and email vendors. Those names belong on the deny list until revenue exists. A magic-link printed to logs can wait for users.
The kitchen-table product is allowed to be incomplete. Completeness is how paid defaults sneak through the door. The freeze file prefers a boring yes over a fancy maybe.
Daylight limits
Limitations stay in daylight beside the happy path. String matching misses binaries and console-clicked cloud resources. It also misses a managed database created by hand.
The scanner does not benchmark models or promise uptime. It does not track tax, payroll, or domain costs. Those bills live outside the repository on purpose.
It will not stop a founder from pasting keys into chat. It will not review business law or privacy text. It only argues with the diff about infrastructure names.
Teams with a platform group should skip this workflow. They already run budget alerts and heavier policy-as-code. This method is for one person and one free box.
It accepts missed hits and noisy comment false positives. It accepts YAML as the only source of architectural truth. Larger companies need real inventory, not a kitchen freezer.
Founders already paying for Redis should not pretend otherwise. The freeze file must match the stack that actually runs. Lying to the scanner wastes the only night left.
After the demo
The kitchen table scene ends with a smaller diff. Checkout writes to storage already living on the box. Invoice PDFs land under ./data/invoices on local disk.
There is no queue and no cache cluster. The nine o'clock demo still runs on one process. The bill stays at zero because the door stayed closed.
Operators who already keep a free server warm may hang this scan beside deploy. The freeze file is the product of this night. The model, when used, is only a narrator.
Top comments (0)