GOne to Two

Chapter 6: The Foundation Audit

13 min readThis chapter is still being written

Before the audit, your understanding of the system‘s health was vibes. “I have a bad feeling about the auth.” “I think the deployment might be fragile.” “Something about the config system keeps me up at night but I can’t say exactly what.” You built a mental model of the codebase in the last chapter — the architecture, the dependencies, the critical path. Now you need to do something harder: turn your anxiety about that codebase into a document. Specific, prioritized, shareable. The foundation audit is how you externalize the worry.

I‘ll tell you what happens when you start looking under the hood of a prototype. Everything feels urgent. Tests are missing. Auth is held together with flags that have “dangerously” in their names. The database isn’t backed up. Error handling swallows exceptions like they're vitamins. Every file you open, another problem.

The data backs up the dread. Research has found that up to 45% of AI-generated code contains security vulnerabilities. Gartner predicted that 30% of generative AI projects would be abandoned after proof of concept — not because the ideas were bad, but because the gap between “works as a demo” and “works in production” was wider than anyone budgeted for.

The instinct is to fix everything. Resist it. That's how you spend three months on infrastructure and ship zero value. The opposite instinct — ignore the problems, keep shipping — is equally lethal. The foundation audit is the middle path: systematic triage. What needs to happen now, what can wait, what you can live with forever. Every item you write down is one less thing living rent-free in your head at 2am.

Before I give you the categories, I want to talk about how to use them — because the categories aren't the insight. The triage is.

You‘re going to walk through each category, find problems, and write them down. Then you’re going to put every problem in one of four buckets:

Fix now (this week): Customer data at risk. Credentials exposed. One bad day away from catastrophe.

Fix soon (this month): Auth gaps, deployment improvements, error handling. Won't kill you today. Will kill you the day your user count doubles.

Fix eventually (this quarter): Scale assumptions, performance, architectural improvements. Important but not urgent.

Accept forever: This is the wisdom category, and it‘s the one most builders get wrong — not because they accept too much, but because they can’t bring themselves to accept anything.

Some things aren‘t worth fixing. Not in the abstract, hand-wavy way it’s usually said. I mean specific things you understand fully and consciously decide to leave alone.

At Apex, four examples.

The telemetry collector had a race condition in its aggregation logic. Under very specific timing — two instances reporting within the same 50-millisecond window — the counts would occasionally be off by one. I found it during the audit, understood exactly what caused it, and did the math. At our current scale, the bug manifested maybe once a day. It made one dashboard number occasionally wrong by one unit. The fix would have required restructuring how telemetry events were queued, touching six files, and introducing a new dependency. I wrote it up, labeled it “accept forever,” and moved on. It's still there. Nobody has ever noticed.

The admin UI had a CSS layout that broke on viewports narrower than 900 pixels. The sidebar would overlap the main content area, making buttons unreachable. I was the only person who used the admin UI, and I used it on a laptop with a 1440-pixel-wide screen. Fixing it meant rewriting the sidebar as a responsive component — three or four hours of work, minimum. For a screen that one person ever sees, on a device that already works. I left it.

The naming conventions across the codebase were a mess. The AI had used camelCase in some modules, snake_case in others. Some files called it a “session,” others called the same concept a “connection,” others an “instance.” It looked unprofessional. It bothered me every time I read the code. But renaming things is one of the most dangerous refactors in a codebase you don't fully understand — every rename is a chance to miss a reference, break an API contract, confuse a string match in a config file somewhere. The inconsistency was ugly. It was also completely harmless. I left it.

Those three are all minor. Here‘s one that isn’t. Apex used SQLite for local data storage on each customer instance. I knew — from Day 4 of the codebase read — that SQLite would buckle under concurrent writes if we ever moved to a shared-database architecture. The “right” thing to do was migrate to Postgres. The cost was two weeks minimum: schema migration, connection pooling, deployment changes, testing across every instance. Two weeks where the visionary sees zero new features. Meanwhile, SQLite was working perfectly at our current scale. Every customer had their own instance with their own database. The concurrent-write problem was real but hypothetical — it only materialized if we changed the architecture, which we had no immediate plans to do. I accepted it. Not because I was tired. Because reaching the scale where SQLite broke was a good problem to have, and I‘d rather spend those two weeks shipping features that got us there. That’s “accept forever” at the architectural level — a deliberate bet, not a default.

The common thread: each was a real problem I genuinely understood — a known issue with a known scope and a known cost of inaction. The cost of fixing exceeded the cost of living with it, and I could articulate why. That‘s the test. If you can’t explain why you‘re accepting something — if your reasoning is “I’m tired” or “it‘s not that bad” — you’re not making a strategic decision. You're procrastinating.

Now the categories. I‘m going to walk through six. These are the six that bit me at Apex — the ones that produced real incidents, real data loss, real 3am panic. Your product will have its own version of this list. You might have five categories or eight. But start here, because these are the ones that kill you silently. They’re roughly in priority order.

1. Data: can you lose customer data?

At Apex, every EC2 deploy used a new instance ID for the data directory. I didn't know this. I found out when a deploy wiped user data. Not in staging. In production. A real customer, real data, gone. Issue #108: “EC2 cloud deploys are not viable for real users without this.”

That moment rearranged my priorities permanently. You can recover from a buggy feature. You can recover from downtime. You cannot recover from losing a customer's data. Data loss is the one failure mode that can end your company in a single incident.

Before you fix anything else, prove that customer data survives every scenario you can imagine. Restart, deploy, crash, migration, scaling event. If any of those can destroy data, stop everything. Fix this first.

Is the database backed up? Have you tested a restore? (You haven‘t. A backup you’ve never tested is a hypothesis, not a safety net.) What happens to user data on restart? On deploy? What data lives outside the database — in config files, in local storage, in third-party services where your API key is the only link?

At Apex this was EC2 instance IDs. At your product it might be a Vercel deploy that drops environment variables pointing to your database, or a Supabase migration that silently skips a column, or user uploads stored in a temp directory that gets wiped on restart.

2. Secrets: are credentials exposed?

Apex's config files had mode 644. World-readable. I discovered this not through a security audit but through a routine file listing where I noticed the permissions and felt my stomach drop. In the same sweep, I found flags named dangerouslyDisableDeviceAuth=true — names that told me exactly what the AI had done during prototyping and how cavalier it had been about what those flags meant in production.

During zero-to-one, security is a speed bump you drive over because the only user is you. The moment a real customer shows up, every shortcut becomes a vulnerability. Credential exposure is silent. Nobody tells you your API key is on GitHub. You find out when someone exploits it.

Where do your credentials actually live? Environment variables, config files, hardcoded strings, committed to the repo? Are any secrets shared between staging and production? (If yes, a staging leak is a production leak.) Have you actually checked the file permissions on your production configs, or are you just assuming they're fine?

3. Auth: who can do what?

At Apex, our health endpoints were wide open. Issue #203. Anyone with an instance ID could see container state, Docker telemetry, OpenClaw version, uptime. I found this by accident — typing a URL I expected to 403 and watching the data pour out instead. The system had no concept of who was asking. It just answered.

Prototypes implement the happy path and skip the enforcement. The login screen exists. The JWT is generated. But the API either doesn't check the token or checks it inconsistently — some endpoints gated, others wide open. The AI built each endpoint in isolation, and each one made its own assumptions about auth.

Partial auth is worse than no auth, because it gives you false confidence. The login screen works. The main API checks tokens. Meanwhile, the health endpoint, the admin route the AI scaffolded, the debug endpoint you forgot about — all open doors.

Map which endpoints require authentication and which ones actually enforce it — these are different lists. What can an unauthenticated user access? What can a user access that belongs to another user? If you‘re on Supabase, check your Row Level Security policies — the AI probably created tables without them. If you’re on Firebase, check your security rules. The AI defaults to open.

4. Deployment: can you ship without fear?

March 12, 4am. I committed: “Isolate staging from production: separate domain + env files.” The fact that this was a fix — not part of the original setup — tells you everything. Staging and production had been bleeding into each other. The same config pointed at the same database. A test in staging could corrupt production data. I found out because it almost did.

The deployment story of every AI-built prototype is “push to main and pray.” It works until it doesn‘t. And when it stops working, there’s no rollback plan, no staging environment, no way to verify a change before it hits production.

Deployment should be boring. If you feel anything when you push a change — adrenaline, anxiety, the urge to watch the logs — your deployment process is telling you it can't be trusted.

Can you deploy without SSHing into a server? Do staging and production use separate infrastructure, separate databases, separate credentials? Can you roll back a bad deploy? What happens if a deploy fails halfway through? Does the process live in your muscle memory, or could someone else follow it?

5. Error handling: do you know when things break?

Settings sync at Apex reported success while files remained unchanged. The permission error was never checked. Issue #164. The system told me sync had completed — green checkmarks, success callbacks, the works. But the config file hadn‘t been written. The permission was wrong, the write failed silently, and the system lied to me with a straight face. I only caught it because a customer reported that their settings kept reverting, and I spent four hours tracing a “working” system to find out it wasn’t.

Vibe-coded prototypes handle the happy path beautifully and swallow everything else. Errors are caught and dismissed rather than caught and reported. Every silent failure is a mystery you'll solve later, under pressure, probably at night.

A system that fails silently is worse than a system that crashes loudly. A crash is honest. A silent failure hides the damage until it compounds into something much worse.

When something breaks, how do you find out? Customer complaint? Your own monitoring? Noticing something odd in the data three weeks later? Search your codebase for try/catch blocks that swallow exceptions. You'll find them. What does the system do when an external dependency is unreachable — retry, fail, or pretend it succeeded?

6. Scale assumptions: what breaks at 10x?

Remember the SSH tunnel story from Chapter 2 — the architecture that worked fine at five instances but convinced the server it was under attack at fifty. The telemetry system polling every few seconds per instance, fine at small numbers, devastating at scale. None of this was visible at prototype scale. The architecture had implicit assumptions about load that nobody had stated because nobody had thought to state them. The AI certainly didn't. It built for the prompt, and the prompt never said “this needs to work with a hundred concurrent users.”

Every prototype has invisible walls. The single-threaded process that works until two requests arrive at the same time. The in-memory cache that works until it exceeds available RAM. The API rate limit you‘ve never hit because your prototype makes ten calls a day. These aren’t bugs — they're design constraints valid at prototype scale that will destroy you at production scale. The insidious part: success itself triggers the failure.

How to find them without a load test: Open every database query in your critical path. What happens if it returns 10,000 rows instead of 10? Open every API call. What happens if the response takes 30 seconds? Open every background job. What happens if 50 run at the same time? Look for polling loops whose frequency scales with users — those are the SSH tunnel story waiting to repeat.

Writing these down — “this query assumes fewer than 100 rows,” “this connection pool assumes fewer than 20 concurrent users” — costs nothing and saves everything when the wall hits.

Once you've audited all six categories, triage everything into the four buckets. This is where the real work happens — not in finding the problems, but in deciding which ones matter.

The hardest part isn‘t finding the problems. It’s the prioritization. Every problem feels urgent when you first see it. But you have limited time, and the visionary is out selling the product while you're reading config files.

The first audit at Apex took me about three days, done in the margins. Two hours in the morning before the Slack notifications started, another hour in the evening. It didn't need to be a formal process. It needed to be honest.

The real output isn‘t the list of issues. It’s the confidence. Before the audit, you were navigating by anxiety — a vague sense that things were broken, no way to know what mattered most. After the audit, you've turned “I have a bad feeling about everything” into “here are the twelve things that matter, ranked, with three of them urgent.” You can see the cliffs. You can see the paths. And you can show that map to someone else.

Which brings me to the last thing: don't audit alone.

Two months in, I have a moment of clarity. I‘m losing the plot. My bias toward releasing as fast as possible — the same bias that made the early velocity feel so good — is going to take its toll. Roddy is indicating he’s willing to add more capital. The Apex company is starting to form around this product. And I‘m the only person who’s looked at the code.

So I bring in Scott Graves and Ken Cone. Both members of 7CTOs, my CTO community. Both accomplished CTOs who've scaled enormous systems. Scott built his career on disciplined engineering — issues first, PRs as architecture documentation. Ken is a brilliant mind who comes from low earth orbit satellite systems — a hardware guy who looks at the world with extreme precision. When Ken examines a system, nothing is approximate.

They look at the code.

Scott introspects the codebase and immediately sees the problem with my SSH strategy. Not a bug — the strategy itself. He starts designing a fleet management system that works on a queue basis, with an agent embedded in each Apex instance that can receive commands from the mothership. A fundamentally different architecture. Something I hadn‘t even considered, because I’m so deep in the race that I can‘t see past the next fix. Ken looks at the infrastructure and sees that my EC2 approach is too volatile — the thing I’ve been patching around for weeks, he names in an afternoon.

Within days, a whole set of best practices land. Not theoretical ones — the kind that come from people who‘ve watched systems break at real scale and know exactly which corners you can’t cut. Scott‘s fleet management design eventually gets built — with Ken’s and my help — and it‘s the thing that replaces the SSH architecture entirely. The system that fourteen bug fixes couldn’t save, two fresh sets of eyes redesign in a week.

The moral support matters almost as much as the technical precision. I‘ve been solo-building for weeks, carrying the anxiety alone, wondering if my instincts are right or if I’m just too close to see clearly. Scott and Ken don‘t just audit the code. They audit my judgment. And some of it holds up, and some of it doesn’t, and the relief of knowing which is which is worth more than any fix.

A solo builder auditing their own work has blind spots the size of continents. You know what you built. You know what you tested. You know your own assumptions. Which means you‘re going to miss the things you assumed were fine, because the assumption lives so deep you don’t even see it as an assumption. I missed the config file permissions for weeks because I‘d set them up once and never questioned them. That’s the kind of blind spot that's obvious to fresh eyes and invisible to yours.

If you don‘t have a Scott and Ken — and most solo builders don’t — use your AI as a rough substitute. Not the same session that built the code — a fresh one. Hand it the audit document and prompt it adversarially: “What categories of risk did I miss entirely? What‘s the most dangerous thing in this codebase that isn’t on this list? Where are the assumptions I‘m not questioning?” Don’t ask “does this look good?” — you‘ll get “your audit looks comprehensive” and learn nothing. Ask it to attack the gaps. The AI won’t catch everything — it won‘t redesign your fleet management architecture the way Scott did. But it’ll catch the structural gaps, the entire categories of risk you forgot to look at because you were too deep in the ones you did.

The audit is the map. The map is a living document — you‘ll update it as you fix things, as new problems surface, as the product grows. But the first version, the one you build this week, is the most important. It’s the moment you stop reacting and start navigating.

Next chapter: what to do with the map. Specifically — how to decide what to fix in place, what to throw away and rebuild, and how to know the difference.