<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yaroslav Krivushenko</title>
    <description>The latest articles on DEV Community by Yaroslav Krivushenko (@jarroslav).</description>
    <link>https://dev.to/jarroslav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4029854%2F442ea3f3-2f1b-4538-98ad-1e83f4e7ff0f.jpg</url>
      <title>DEV Community: Yaroslav Krivushenko</title>
      <link>https://dev.to/jarroslav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jarroslav"/>
    <language>en</language>
    <item>
      <title>Running a 117K-Line Production App Alone: The Pipeline, the Migrations, and One Security Invariant</title>
      <dc:creator>Yaroslav Krivushenko</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:18:07 +0000</pubDate>
      <link>https://dev.to/jarroslav/running-a-117k-line-production-app-alone-the-pipeline-the-migrations-and-one-security-invariant-1lna</link>
      <guid>https://dev.to/jarroslav/running-a-117k-line-production-app-alone-the-pipeline-the-migrations-and-one-security-invariant-1lna</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with being both the author and the approver
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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."&lt;/p&gt;

&lt;p&gt;What I ended up with is an 11-agent delivery workflow with a rule I take seriously: &lt;strong&gt;the reviewing agent doesn't see the author's reasoning.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;Nothing merges without that review passing, and nothing merges without Playwright E2E going green first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7yjiwqw6cftspgoufmk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7yjiwqw6cftspgoufmk6.png" alt="From Ashes release pipeline — change, author agent, blind review, Playwright E2E gate, human approve, merge, plus the daily dump-to-staging restore path" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  200+ migrations, and I still haven't squashed them
&lt;/h2&gt;

&lt;p&gt;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: &lt;strong&gt;the deployment history has repeatedly been the thing that let me diagnose drift.&lt;/strong&gt; 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 &lt;code&gt;migrations/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;chat_messages.edited_at&lt;/code&gt; column that existed in one environment and not the other, silently, until the restore surfaced it.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary that actually matters: RLS vs. app-level checks
&lt;/h2&gt;

&lt;p&gt;This is the part I think about the most, and the part I'm least finished with.&lt;/p&gt;

&lt;p&gt;From Ashes layers two permission systems: Postgres Row Level Security, and application-level RBAC checks in TypeScript. The way I think about the split: &lt;strong&gt;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.&lt;/strong&gt; For anything sensitive — moderator scope, application review, chat-room access, &lt;code&gt;character.edit.any&lt;/code&gt; — the same permission concept is enforced at the RLS/database level, not only in application code.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App says &lt;em&gt;allow&lt;/em&gt;, RLS says &lt;em&gt;deny&lt;/em&gt; → the user hits a broken feature. Annoying, but safe — nothing leaks.&lt;/li&gt;
&lt;li&gt;App says &lt;em&gt;deny&lt;/em&gt;, RLS says &lt;em&gt;allow&lt;/em&gt; → 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:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app deny ⇒ RLS deny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl0oelop5h52hwiep383.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl0oelop5h52hwiep383.png" alt="Authorization boundary: request → app check (RBAC/UX) → RLS check (Postgres policy, hard boundary) → database — with the app-deny-implies-RLS-deny invariant marked" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the test count matters more than it sounds like it should
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm not claiming
&lt;/h2&gt;

&lt;p&gt;Dev.to audiences smell overclaiming fast, so let me be specific about what this isn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Edge Functions right now.&lt;/strong&gt; Server-side work is handled by Next.js Route Handlers / Server Actions, Postgres RPCs, and RLS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No feed materialization or fan-out-on-write.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peak throughput isn't the constraint yet.&lt;/strong&gt; 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&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://github.com/Jarroslav/agentic-os" rel="noopener noreferrer"&gt;agentic-os&lt;/a&gt;, and a Playwright CI reporter, &lt;a href="https://github.com/flaketrace/playwright-ai-triage" rel="noopener noreferrer"&gt;playwright-ai-triage&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;If you want the fuller Q&amp;amp;A on the database side — throughput, backups, the RLS discussion in more depth — &lt;a href="https://www.reddit.com/r/Supabase/comments/1v9q4n7/built_and_run_a_community_platform_on_supabase/" rel="noopener noreferrer"&gt;r/Supabase AMA thread&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>nextjs</category>
      <category>postgres</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
