I've spent the last three months building and running From Ashes — a collaborative-fiction community platform — completely alone. Next.js 15, TypeScript, Supabase. 117K+ lines, 200+ database migrations, real users with real uptime expectations, and no one to hand off to when something breaks at 2 AM.
By day, I'm a Lead Test Automation Engineer. I test other people's software for a living. This post is about what happens when you have to test — and ship, and operate — your own.
The problem with being both the author and the approver
Every senior engineer knows you shouldn't merge your own PR without review. The reason isn't politeness — it's that you can't see your own blind spots. You already believe your code is correct; that's why you wrote it that way.
Solo, there's no one else to ask. So the review has to come from somewhere that isn't just "me, five minutes later, still convinced I was right."
What I ended up with is an 11-agent delivery workflow with a rule I take seriously: the reviewing agent doesn't see the author's reasoning. It gets the diff, not the plan. No "looks fine, the plan said so." It has to form its own opinion from the code, the same way a human reviewer who wasn't in the design conversation would.
Nothing merges without that review passing, and nothing merges without Playwright E2E going green first.
Is this overkill for a side project? Maybe. But "side project" undersells what it actually is once real people are using it daily — at that point the cost of a bad deploy is the same whether one person or ten approved it.
200+ migrations, and I still haven't squashed them
The migrations directory is append-only. Every schema change, numbered, forever. I know the conventional wisdom — squash them, keep the directory clean. I haven't done it, and the reason isn't inertia: the deployment history has repeatedly been the thing that let me diagnose drift. When something in staging doesn't match production, being able to read the exact sequence of changes that got each environment to its current state is worth more than a tidy migrations/ folder.
The actual safety net isn't the migration history, though — it's operational. Every release runs database invariant checks, and every day, a production dump gets restored into staging. That restore has already caught real drift: a missing chat_messages.edited_at column that existed in one environment and not the other, silently, until the restore surfaced it.
I'm not using Supabase Storage as a backup mechanism. The recovery path is dump → staging restore, not automated rollback — deliberately. I'd rather have a boring, verified process I run daily than a rollback button I've never had to press for real.
Will I need to consolidate 200+ migrations eventually? Almost certainly. I just haven't hit the point where the directory's length costs me more than the history is worth.
The boundary that actually matters: RLS vs. app-level checks
This is the part I think about the most, and the part I'm least finished with.
From Ashes layers two permission systems: Postgres Row Level Security, and application-level RBAC checks in TypeScript. The way I think about the split: RLS is the hard security boundary. The app-level checks exist to fail early and give the user a useful response — they are not supposed to be a second, independent source of truth. For anything sensitive — moderator scope, application review, chat-room access, character.edit.any — the same permission concept is enforced at the RLS/database level, not only in application code.
Here's the honest gap: I don't yet have an exhaustive differential test suite that proves the app's decision and the database's outcome always agree for every user/role/row combination. I have concrete authorization tests — seeded fixtures, unit tests, E2E paths — but not that full cross-check. I consider that a hardening gap, not evidence of an actual mismatch, but I want to be precise about the difference between those two things.
I got real help thinking this through in a Reddit AMA, from a commenter who pushed back on "full agreement" as the target: only one of the two possible mismatch directions is actually a security bug.
- App says allow, RLS says deny → the user hits a broken feature. Annoying, but safe — nothing leaks.
- App says deny, RLS says allow → this is the real problem. The UI hides the action, so no one notices, but a raw API call with that user's token succeeds anyway. The app-level check was giving false assurance. So the suite I actually need doesn't require full agreement — it requires one asymmetric invariant, checkable from the same fixtures I already have:
app deny ⇒ RLS deny
RLS must never be more permissive than the app claims. That's the follow-up I'm building next, and the daily dump-to-staging-restore step is the natural place to run it — the same place that already caught the schema drift.
Why the test count matters more than it sounds like it should
One feature cluster took the suite from 135 tests to 359+. Not because I set a target — because that's what it took to ship a 374-file redesign in 52 commits without taking down something people were actively using.
I don't think of test count as a virtue in itself. I think of it as the thing that let a specific, large, risky change go out safely, solo, without a second engineer's eyes on it live.
What I'm not claiming
Dev.to audiences smell overclaiming fast, so let me be specific about what this isn't:
- No Edge Functions right now. Server-side work is handled by Next.js Route Handlers / Server Actions, Postgres RPCs, and RLS.
- No feed materialization or fan-out-on-write. Public pages (homepage, forum index, category pages) are cached/ISR reads over the same RLS-filtered data everyone else sees. Fan-out is reserved for notifications only — a resolved mention sends one in-app notification per distinct character owner, a reply notifies thread followers, staff alerts run a separate role-based path. If the product grows a real personalized feed, that's a different architecture, and I'd build it then, not now.
- Peak throughput isn't the constraint yet. I run k6 load/stress scenarios, but I treat them as regression tooling — a way to catch a change that quietly made things slower — not as proof the platform operates "at scale." I don't have a meaningful requests/sec number to claim, and I'm not going to invent one. ## What running this taught me back at my day job
The loop closed in a direction I didn't expect. I test automation systems professionally — Playwright, CI governance, quality gates. Building From Ashes didn't just use those skills; it stress-tested my own opinions about them, because for once I was the one who'd get paged.
Blind review, mandatory E2E gates, and an actually-tested restore path aren't abstract best practices to me anymore. They're the reason a solo release cadence hasn't produced an outage I couldn't recover from.
I've since started pulling some of this — agent contracts, enforcement hooks instead of prompted politeness, human-in-the-loop escalation for the risky stuff — into an open-source project called agentic-os, and a Playwright CI reporter, playwright-ai-triage, that classifies failing tests instead of leaving a human to guess. Both came out of the same instinct: governance has to be structural, not a suggestion, whether the "team" reviewing your code is eleven agents or eleven people.
If you want the fuller Q&A on the database side — throughput, backups, the RLS discussion in more depth — r/Supabase AMA thread.


Top comments (0)