<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Bolvrk blog</title>
  <subtitle>Migrations, locks, credentials, and what changes when an agent writes the SQL.</subtitle>
  <id>https://bolvrk.com/blog</id>
  <link href="https://bolvrk.com/blog" />
  <link rel="self" href="https://bolvrk.com/blog/feed.xml" />
  <updated>2026-09-10T00:00:00Z</updated>
  <author><name>Bolvrk</name><uri>https://bolvrk.com</uri></author>
  <entry>
    <title>Vibe coding without being reckless</title>
    <id>https://bolvrk.com/blog/vibe-coding-without-being-reckless</id>
    <link href="https://bolvrk.com/blog/vibe-coding-without-being-reckless" />
    <published>2026-09-10T00:00:00Z</published>
    <updated>2026-09-10T00:00:00Z</updated>
    <author><name>Bolvrk</name></author>
    <category term="ai-workflows" />
    <category term="vibe-coding" />
    <category term="migrations" />
    <category term="credentials" />
    <summary>Letting the model drive is fine. Letting it drive into production without a gate is not. A short, practical split between the parts of a change you can vibe and the parts that need a deterministic check.</summary>
    <content type="html">&lt;p&gt;
  Vibe coding — describing what you want, accepting what the model writes, running it, and
  iterating on the result rather than the code — is a genuinely good way to build a lot of
  software. Most of a product is UI, glue and business logic that is cheap to be wrong about,
  because being wrong shows up immediately and costs a retry. The reckless part is not the vibe.
  It is applying the same loop to the small set of changes where being wrong shows up later, at
  scale, and is not undone by a retry.
&lt;/p&gt;
&lt;p&gt;
  So the question is not &quot;should I vibe code&quot; but &quot;which parts of this change are safe to vibe&quot;.
  The answer turns out to be short.
&lt;/p&gt;

&lt;h2&gt;The parts you can vibe&lt;/h2&gt;
&lt;p&gt;
  Anything with a fast, honest feedback loop. If a mistake makes a test fail, a page render wrong,
  or an API return the wrong shape, you will see it within seconds and the model will fix it on
  the next turn. Components, handlers, transformations, most refactors, most tests. Let it drive.
  Read the diff the way you would read a colleague's, not the way you would audit a contractor's.
&lt;/p&gt;

&lt;h2&gt;The parts you cannot&lt;/h2&gt;
&lt;p&gt;
  Three kinds of change have no fast feedback loop, because the failure mode does not exist in a
  development environment.
&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;strong&gt;Schema migrations.&lt;/strong&gt; On a dev database with forty rows, every migration is
    instant and every migration works. &lt;code&gt;ALTER TABLE orders ADD COLUMN status text NOT NULL&lt;/code&gt;
    (&lt;a href=&quot;https://bolvrk.com/rules/bv002&quot;&gt;BV002&lt;/a&gt;) succeeds on an empty table and fails on a full one. An index
    built without &lt;code&gt;CONCURRENTLY&lt;/code&gt; (&lt;a href=&quot;https://bolvrk.com/rules/bv003&quot;&gt;BV003&lt;/a&gt;) takes a second locally
    and blocks writes for twenty minutes in production. The model has seen millions of migrations
    and almost none of them ran against a table with a hundred million rows, so it has no reason to
    prefer the safe form. Neither does your test suite.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Credentials.&lt;/strong&gt; A model that needs a database URL to make a script work will
    happily inline the one it found in &lt;code&gt;.env&lt;/code&gt;, and a model that is asked to &quot;make the
    migration self-contained&quot; will do the same. The commit passes every test. It also passes the
    connection string into git history, where it stays. Nothing in the vibe loop flags this,
    because nothing in the vibe loop is looking.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Destructive data operations.&lt;/strong&gt; &lt;code&gt;DROP COLUMN&lt;/code&gt;
    (&lt;a href=&quot;https://bolvrk.com/rules/bv030&quot;&gt;BV030&lt;/a&gt;), &lt;code&gt;DELETE&lt;/code&gt; without a &lt;code&gt;WHERE&lt;/code&gt;, a
    backfill that rewrites every row. These are correct in the sense that they do exactly what
    was asked. They are only wrong in the sense that the previous deploy is still reading the
    column, or that the rows were not yours to delete. That context lives in the deploy window
    and the business, not in the code the model can see.
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  What these have in common is that the person or agent making the change cannot observe the
  consequence before committing to it. The loop is open. Something has to close it.
&lt;/p&gt;

&lt;h2&gt;Close the loop with a rule, not with vigilance&lt;/h2&gt;
&lt;p&gt;
  The tempting answer is &quot;just review migrations carefully&quot;. It does not survive contact with a
  team that ships forty pull requests a day, half of them opened by agents, at eleven at night.
  Vigilance is a budget; it runs out exactly when the migration count goes up.
&lt;/p&gt;
&lt;p&gt;
  The answer that scales is a gate that does not get tired. For the three categories above, the
  dangerous forms are known and enumerable — locks, rewrites, missing defaults, missing timeouts,
  credentials in the diff — which means they can be checked deterministically: the same input, the
  same verdict, every time, with a rule id and a fix attached. That is what Bolvrk is. It sits at
  the three places a generated change passes through, and it does not matter which one catches it.
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;strong&gt;Before the draft leaves the agent&lt;/strong&gt; — the &lt;a href=&quot;https://bolvrk.com/docs/mcp&quot;&gt;MCP server&lt;/a&gt;
    gives the agent a &lt;code&gt;check&lt;/code&gt; tool, and the skills library tells it to call the tool
    before proposing SQL. The agent revises its own migration; you never see the first draft.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Before you commit&lt;/strong&gt; — &lt;code&gt;npx bolvrk check migration.sql&lt;/code&gt; in the
    terminal, and &lt;code&gt;bolvrk secrets&lt;/code&gt; on the diff or on any output the agent pasted.
    No account, exit code 1 on findings.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Before it merges&lt;/strong&gt; — the &lt;a href=&quot;https://bolvrk.com/docs/github-action&quot;&gt;GitHub Action&lt;/a&gt; fails
    the job above the severity your team chose and explains each finding on the pull request. This
    is the one that holds when the first two were skipped.
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;A working rule of thumb&lt;/h2&gt;
&lt;p&gt;
  Vibe everything whose mistakes you would see in the next sixty seconds. Gate everything whose
  mistakes you would see in the next sixty days. The gate should be a program, not a person, and it
  should give the same answer to the agent that it gives to you.
&lt;/p&gt;
&lt;p&gt;
  That is the whole discipline. It costs one command per migration and one Action in the repo, and
  it lets you keep the speed for the ninety percent of changes that deserve it.
&lt;/p&gt;</content>
  </entry>
  <entry>
    <title>Deterministic checks for AI-written migrations</title>
    <id>https://bolvrk.com/blog/deterministic-checks-for-ai-written-migrations</id>
    <link href="https://bolvrk.com/blog/deterministic-checks-for-ai-written-migrations" />
    <published>2026-09-10T00:00:00Z</published>
    <updated>2026-09-10T00:00:00Z</updated>
    <author><name>Bolvrk</name></author>
    <category term="ai-workflows" />
    <category term="postgres" />
    <category term="migrations" />
    <summary>Coding agents write database migrations quickly and confidently, and the mistakes they make are the same ones people make. Why the check that catches them has to be a rule, not another model.</summary>
    <content type="html">&lt;p&gt;
  A coding agent asked to &quot;add a &lt;code&gt;status&lt;/code&gt; column to &lt;code&gt;orders&lt;/code&gt;&quot; will do it in
  seconds. It will also, more often than not, write the version that fails on a table with rows in
  it, or the version that takes an exclusive lock and holds it for the whole rewrite. Not because
  the model is bad at SQL — because the dangerous form and the safe form look almost identical, and
  the difference only matters at production scale, which the model never sees.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- what the agent wrote
ALTER TABLE orders ADD COLUMN status text NOT NULL;

-- what survives contact with a table that has rows
ALTER TABLE orders ADD COLUMN status text;
UPDATE orders SET status = 'open' WHERE status IS NULL;  -- in batches
ALTER TABLE orders ALTER COLUMN status SET NOT NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  That first statement is &lt;a href=&quot;https://bolvrk.com/rules/bv002&quot;&gt;BV002&lt;/a&gt;. It is one of the oldest and
  best-understood migration mistakes there is, and it is exactly the kind of thing an AI reviewer
  will sometimes catch and sometimes wave through, depending on the prompt, the context window,
  and the day.
&lt;/p&gt;

&lt;h2&gt;Why the reviewer cannot be another model&lt;/h2&gt;
&lt;p&gt;
  The obvious fix is to ask a second model to review the first model's migration. It works often
  enough to feel like it works. It does not work in the way a team can build a process on, for
  three reasons.
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;strong&gt;It is not repeatable.&lt;/strong&gt; The same migration reviewed twice can get two verdicts.
    A pull-request check that is red on Tuesday and green on Wednesday for the same diff teaches
    people to click through it.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;It cannot say what it did not check.&lt;/strong&gt; A model that returns &quot;looks fine&quot; gives
    you no list of the properties it verified. A rule corpus is a list by construction: these 104
    patterns were checked, these fired, these did not.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;Its claims are not verifiable.&lt;/strong&gt; When a rule says &quot;this rewrites the table under
    an &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; lock&quot;, that claim was tested against a real Postgres and the
    fixture is in the repository. A model's claim is a sentence.
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  None of this is an argument against using agents to write migrations. It is an argument about
  where the trust boundary goes. The agent proposes; something deterministic disposes.
&lt;/p&gt;

&lt;h2&gt;What a deterministic check looks like in an agent workflow&lt;/h2&gt;
&lt;p&gt;
  The check has to sit where the agent can reach it, and it has to return the same shape every
  time so the agent can act on it without interpretation. Three placements cover most setups.
&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;strong&gt;Inside the agent's loop.&lt;/strong&gt; Bolvrk runs as an
    &lt;a href=&quot;https://bolvrk.com/docs/mcp&quot;&gt;MCP server&lt;/a&gt;. An agent that has just drafted a migration calls the
    &lt;code&gt;check&lt;/code&gt; tool, gets findings back as JSON with a rule id, a severity and the fix, and
    revises before the human ever sees the draft. The skills library in the public repository tells
    the agent when to do this unprompted.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;In the terminal.&lt;/strong&gt; &lt;code&gt;npx bolvrk check migration.sql&lt;/code&gt; — no account, no
    config, exit code 1 on findings. This is the same rule set the MCP server runs, so a person
    double-checking an agent's work sees exactly what the agent saw.
  &lt;/li&gt;
  &lt;li&gt;
    &lt;strong&gt;On the pull request.&lt;/strong&gt; The &lt;a href=&quot;https://bolvrk.com/docs/github-action&quot;&gt;GitHub Action&lt;/a&gt; posts
    one summary comment and fails the job above the severity the team chose. Whoever, or whatever,
    opened the PR, the gate is the same.
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  The point of running the same corpus in all three places is that the verdict does not change as
  the migration moves from the agent's draft to the terminal to CI. The agent cannot talk its way
  past the Action, because the Action is not listening to arguments.
&lt;/p&gt;

&lt;h2&gt;The rules that matter most for generated SQL&lt;/h2&gt;
&lt;p&gt;
  Looking at what agents actually produce, a handful of rules do most of the work.
&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://bolvrk.com/rules/bv002&quot;&gt;BV002&lt;/a&gt; — &lt;code&gt;ADD COLUMN … NOT NULL&lt;/code&gt; without a default.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bolvrk.com/rules/bv003&quot;&gt;BV003&lt;/a&gt; — an index created without &lt;code&gt;CONCURRENTLY&lt;/code&gt;, blocking writes for the whole build.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bolvrk.com/rules/bv034&quot;&gt;BV034&lt;/a&gt; — a lock taken with no &lt;code&gt;lock_timeout&lt;/code&gt;, so one slow transaction turns into a queue behind it.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bolvrk.com/rules/bv030&quot;&gt;BV030&lt;/a&gt; — a &lt;code&gt;DROP COLUMN&lt;/code&gt; while code that reads the column is still deployed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
  Every one of these has a page that says what the rule catches, the SQL it fires on, and the safe
  pattern. That is deliberate: when an agent explains a finding to a person, or a person to an
  agent, the explanation should come from the same place and say the same thing.
&lt;/p&gt;

&lt;h2&gt;What this is not&lt;/h2&gt;
&lt;p&gt;
  A rule corpus does not know your business. It cannot tell you that the backfill will take four
  hours on your data, or that the column you are dropping is read by a report nobody has looked at
  since 2024. Live-schema context narrows that gap — a read-only connection lets the rules see the
  real table sizes and constraints — but judgement stays with the team. The corpus removes the
  category of mistakes that need no judgement at all, so the judgement can go where it is needed.
&lt;/p&gt;
&lt;p&gt;
  If you are letting agents write migrations, the question is not whether to review them. It is
  whether the review is something you can rely on the same way every time. Start with
  &lt;code&gt;npx bolvrk check&lt;/code&gt; on the last migration an agent wrote for you.
&lt;/p&gt;</content>
  </entry>
</feed>
