The model never owns your local architecture contract at all. You pin invariants on disk before any generation starts. Every inbound patch stays untrusted until those invariants hold.
Volume is the trap on a free generation path. Free models invite you to spray diffs across the tree. Without a contract, the tree slowly changes owners.
Treat the model like a contractor without a permit. You still own every load-bearing wall in the design. The contractor may paint rooms but never move columns.
This piece reviews that generation loop as architecture. You start with constraints, then follow data, then name failures. You finish with the next change you would actually ship.
Constraint one is simple and easy to forget. The model has no duty to preserve your boundaries. It optimizes for a plausible file, not for ownership.
Constraint two is the host you do not control. A free server is an execution plane, not a studio. It may vanish, throttle, or run a different libc.
Constraint three is the prompt you cannot fully trust. Anything you paste can leak into logs you never see. Secrets, private URLs, and customer names stay off that wire.
Constraint four is review time, which does not scale with volume. Cheap generation does not make cheap understanding of a diff. A fluent patch can still move a column you meant to keep.
You can park execution on a free remote without surrendering authority. MonkeyCode fits that slot as a disposable compile farm. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The project is open source, with free model access. A free server option exists for running generated checks. Keep design authority on your laptop during the whole loop.
Read the data flow as four boxes and three arrows. Box one is your contract file and its hashes. Box two is a redacted prompt with frozen interfaces only.
Box three is the model, and box four is the runner. Arrow one leaves your laptop with no secrets attached. Arrow two returns a patch, logs, and a test verdict.
Arrow three is you, merging only after the checker passes. Notice what never travels on those arrows at all. The contract file never becomes a suggestion for the model.
The model may read a digest of allowed surfaces, nothing more. The important payload is not the whole repository snapshot. It is the contract hash the runner must still prove.
The runner proves the hash still matches after the patch applies. If the hash moves, you reject the entire generation immediately. Style review never starts on a patch that moved a wall.
The free server holds a throwaway worktree for tests only. Clone, run, stream logs, then throw the tree away. A merge commit is born only on your machine.
The first failure domain sits inside the model's confidence. It will invent helpers that look local and feel familiar. Those helpers quietly bypass the authz module you meant to freeze.
The second failure domain sits on the free runner's image. Your laptop uses one OpenSSL, the runner uses another build. Green tests there can still be red tests on your machine.
The third failure domain is prompt residue in cached logs. A pasted stack trace may contain a session cookie or token. You cannot un-send that line after the request leaves.
The fourth failure domain is human fatigue after the tenth patch. You start skimming because the model sounds sure of itself. That is how a renamed identifier becomes a production incident.
Here is a small contract you can check into git. It is a proposal, not a framework you must adopt. Save it as architecture-contract.json at the repository root.
{
"version": 1,
"owners": {
"contract": "local-architect",
"generation": "untrusted-model",
"execution": "ephemeral-runner"
},
"pinned_files": [
"architecture-contract.json",
"src/authz/policy.py",
"src/id.py"
],
"forbidden_write_prefixes": [
"src/authz/",
"migrations/",
".github/"
],
"allowed_write_prefixes": [
"src/handlers/",
"src/generated/",
"tests/generated/"
],
"deny_prompt_patterns": [
"BEGIN OPENSSH PRIVATE KEY",
"AWS_SECRET_ACCESS_KEY",
"api_key",
".env"
]
}
The checker below is local, boring, and intentionally strict. Run it before you look at any model-written diff. If it fails, you never open the patch for taste.
#!/usr/bin/env python3
"""Proposed local gate. Label: run it yourself before trusting it."""
from __future__ import annotations
import hashlib
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CONTRACT_PATH = ROOT / "architecture-contract.json"
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def git_changed_files(base: str) -> list[str]:
out = subprocess.check_output(
["git", "diff", "--name-only", base],
cwd=ROOT,
text=True,
)
return [line.strip() for line in out.splitlines() if line.strip()]
def main() -> int:
contract = json.loads(CONTRACT_PATH.read_text())
base = sys.argv[1] if len(sys.argv) > 1 else "HEAD"
changed = git_changed_files(base)
errors: list[str] = []
for pinned in contract["pinned_files"]:
path = ROOT / pinned
if not path.is_file():
errors.append(f"missing pinned file: {pinned}")
continue
digest_file = ROOT / ".contract-hashes" / f"{pinned.replace('/', '__')}.sha256"
if not digest_file.is_file():
errors.append(f"missing hash for {pinned}; pin it first")
continue
expected = digest_file.read_text().strip()
actual = sha256(path)
if actual != expected:
errors.append(f"hash drift in {pinned}")
for path in changed:
if any(path.startswith(prefix) for prefix in contract["forbidden_write_prefixes"]):
errors.append(f"forbidden write: {path}")
continue
allowed = contract["allowed_write_prefixes"]
if allowed and not any(path.startswith(prefix) for prefix in allowed):
errors.append(f"write outside allowlist: {path}")
diff = subprocess.check_output(["git", "diff", base], cwd=ROOT, text=True)
lowered = diff.lower()
for pattern in contract["deny_prompt_patterns"]:
if pattern.lower() in lowered:
errors.append(f"secret-like pattern in diff: {pattern}")
if errors:
print("CONTRACT FAIL")
print("\n".join(errors))
return 1
print("CONTRACT OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Pin hashes once, after a human review of those load-bearing files. The commands below write the digest files your checker will later trust. Re-pin only when you intend to change the wall.
mkdir -p .contract-hashes
python3 - <<'PY'
import hashlib, json
from pathlib import Path
root = Path('.')
contract = json.loads((root / 'architecture-contract.json').read_text())
for pinned in contract['pinned_files']:
path = root / pinned
digest = hashlib.sha256(path.read_bytes()).hexdigest()
out = root / '.contract-hashes' / f"{pinned.replace('/', '__')}.sha256"
out.write_text(digest + '\n')
print(pinned, digest[:12])
PY
git add architecture-contract.json .contract-hashes
Wire it as a hook so skipping it takes work. The command below installs a proposed pre-push check on your machine. Label this as a local convention, not a tested production control plane.
cat > .git/hooks/pre-push <<'HOOK'
#!/bin/sh
set -e
python3 tools/check_contract.py origin/main
HOOK
chmod +x .git/hooks/pre-push
For the generation step, keep the prompt brutally small. Send signatures, not novels, and never send .env files. The snippet below builds a redacted context pack from git.
# Proposed packager. Inspect the file before any remote call.
mkdir -p /tmp/gen-pack
: > /tmp/gen-pack/context.txt
{
echo "CONTRACT_DIGESTS"
cat .contract-hashes/*.sha256
echo
echo "ALLOWED_SURFACES"
git ls-files 'src/handlers/*.py' 'src/generated/*.py'
echo
echo "PUBLIC_SIGNATURES"
git grep -n "^def \|^class " -- src/handlers src/id.py src/authz/policy.py
} > /tmp/gen-pack/context.txt
# Fail closed if the pack still looks like a secret dump.
if grep -Eiq 'BEGIN OPENSSH|AWS_SECRET|api_key|password=' /tmp/gen-pack/context.txt; then
echo "refusing to send context pack" >&2
exit 1
fi
wc -c /tmp/gen-pack/context.txt
After the model returns a patch, apply it in a detached worktree. Run the contract checker, then run the smallest relevant tests. Only then do you even read the generated code for style.
# Proposed apply-and-prove flow. Adjust the remote runner to your host.
git worktree add /tmp/gen-review HEAD
cd /tmp/gen-review
git apply --check /tmp/model.patch
git apply /tmp/model.patch
python3 tools/check_contract.py HEAD
python3 -m pytest tests/generated -q
cd -
git worktree remove --force /tmp/gen-review
If the tests run on a free server, copy only the patch and the hash files. Do not copy your whole home directory, keys, or production dumps. Bring back logs and a pass or fail bit, nothing else.
The next change is a machine-readable allowlist of writeable paths. Today many teams list forbidden trees, which is the weaker direction. An allowlist fails closed when the model invents a new folder.
The change after that is a second runner with a different image. One green result on one free host is a weak signal. Two images disagreeing is the cheap way to catch libc drift.
You might also pin a golden HTTP transcript for the authz boundary. Generated handlers would have to replay those transcripts without edits. That turns a vague invariant into a failing test you can see.
A tiny transcript test can live beside the contract without much ceremony. The example below is a proposal you should rewrite for your actual routes. Keep it boring enough that a model cannot charm it.
# tests/generated/test_authz_transcript.py
# Proposed example. Swap the client for your app's test client.
TRANSCRIPT = [
("GET", "/public/health", 200),
("GET", "/admin/users", 401),
("GET", "/admin/users", 403), # with a user token, not an admin token
]
def test_authz_transcript_does_not_drift(client):
for method, path, status in TRANSCRIPT:
response = client.open(path, method=method)
assert response.status_code == status, (method, path)
This approach will annoy you on greenfield spikes and demos. A contract assumes you already know which walls are load-bearing. If you are still exploring, the checker becomes a false religion.
It also fails when the contract file itself is wrong. A pinned mistake just makes the model reproduce your old error. Review the contract on a slower cadence than you review patches.
Do not use this if you cannot run git on the laptop. Do not use this if the model must edit schema migrations tonight. Do not use this if legal review forbids any remote generation.
People will ask you to just paste the whole repository over. Refuse that request even when generation feels cheap and endless. Cheap generation is still expensive once it ships the wrong boundary.
Pin the contract. Hash it. Reject patches that touch it. Let the free model fill gaps inside the allowed surfaces. Let a free server prove the tests, then throw that tree away.
If you already have a free execution host, run only the tests there. Keep the contract checker on the machine that can say no.
Top comments (0)