hejbro · part 1

The Declarative Bridge

8/19/2026 · 17 min · DongHyeon Yu

The Declarative Bridge

These days, most of my code is written by AI agents. As I mentioned in my last reflection, this happens at a pace of 267 merged pull requests a week. Yet, just when I thought my engineering instincts had grown numb to delegating code, they sharpen right back up the moment we reach the database. Watching an agent connect to a production database to alter schemas and drop records feels deeply unsettling, no matter how flawlessly it performs.

This post is a record of tracing that discomfort back to its source.

Why Code Was Safe

Entrusting code to AI became comfortable not because AI never makes mistakes, but because we work on top of a structure where mistakes are acceptable.

No matter how strange the code written by an agent is, nothing actually happens until it gets merged. Changes arrive as reviewable diffs, bad changes get dropped during review, and even if a problem surfaces after merging, Git tracks when, what, and why things changed so we can roll back anytime. I have written before that there is no single right way to use AI, but in the realm of code, one thing is clear: we do not trust the AI, we trust the structure that lowers the cost of its mistakes.

Why Databases Make Us Nervous Again

The moment we attach an agent directly to a database via MCP, that structure vanishes completely.

Execution means immediate application. There is no grace period before merging, no diff as a review unit, and whatever happened is scattered somewhere in a conversation log. So the questions remain: What if the agent drops the wrong table? What if it modifies data incorrectly? What is the policy then?

I looked into how the ecosystem answers this:

The MCP specification lacks the concept of recovery entirely. The only safety mechanisms are recommendations like "humans SHOULD be in the approval loop" and annotations like destructiveHint. But the spec immediately adds that clients MUST NOT rely on these annotations unless the server is trusted. Even the "destructive tool" label is just a hint.

Supabase MCP docs offer read-only mode, project scoping, and repeated warnings: never connect to production data. Recovery instructions are nowhere to be found. An external proposal requiring human cryptographic receipts for destructive SQL was not adopted either.

The official reference Postgres MCP server was strictly read-only and is now archived. It did not solve safe writes; it simply gave up on writing.

Neon takes the most thoughtful approach by running schema changes in temporary branches, but their conclusion is identical: use it for dev and testing, not production.

Google Cloud's MCP security guide states candidly that you must have a recovery strategy when defenses fail. But their examples (backups, PITR, snapshots) are all traditional DB operational tools outside of MCP.

The ecosystem's consensus is simple: "Use read-only," "Do not connect to production," "Approve everything manually." These are all preventative gates, and the policy after an incident is just "hope you have backups." I am not saying it is inherently bad, but handing over a production DB without a clear recovery policy for mistakes is something I still find uncomfortable.

And this discomfort is not just mine. A confession thread about an AI agent wiping a production database drew over a thousand comments, the incident where Supabase MCP leaked entire private tables lit up Hacker News, and people keep shipping guard tools they built because handing an agent direct database access scared them.

I Always Used Supabase Declaratively

To be frank, I did not start thinking about this because of MCP. It started years ago, working with Supabase.

Supabase is great, but its out-of-the-box schema management is loose. The official workflow means writing raw migration SQL by hand, and reading SQL is often harder than writing it. Reviewing hundreds of lines of migration SQL leaves me wondering if I am actually validating it or just pretending to scan it. So I looked for alternatives early on, comparing Prisma and Drizzle, and settled on Drizzle. You declare the desired state in TypeScript, and migration SQL gets generated deterministically. The code becomes the review target; the SQL becomes the build artifact.

When the AI era arrived, only one thing changed in my workflow: the hand writing the TypeScript moved from me to the agent. The agent edits the Drizzle schema, SQL gets generated, and I review the diff. I did not carry it intentionally to dodge the rain, but looking back, the exact structure I was searching for (grace periods, review units, and version history) was already there. It just happened to be an umbrella in my hand.

The Trade-offs I Tolerated

Then why not just keep using Drizzle? That is what I did. But that umbrella had obvious holes, and I tolerated them for quite a while.

Drizzle does not cover all of Supabase. Tables and RLS policies are handled declaratively, but things outside that are not. The biggest pain point is RPC, or Postgres functions.

The benefits of RPCs are real: collapsing multiple network hops into a single call, ensuring transaction atomicity, and keeping sensitive logic inside the DB. It is hard to give up. But the cost is steep. Moving logic into plpgsql makes debugging and testing difficult, adds a learning curve, and pushes things outside the declarative world. The Supabase CLI generates types for signatures, but the function body remains an unvalidated SQL string without type checking or diff tools.

The same goes for grants. Drizzle's official guidance is to write permissions manually as raw SQL inside empty migration files. My repositories still have folders dedicated to appending raw SQL strings for things Drizzle cannot express. Declarative code and raw strings uncomfortably cohabit the same codebase, and review quality drops sharply at that boundary. Working with agents makes this even more obvious: declarative diffs make sense, while raw string changes force me to read SQL from scratch again.

I could tolerate it. I did, for years.

So I Started Building a Bridge

Instead of tolerating it further, I decided to build a solution: hejbro. It lets you declare your entire database (tables, RLS, functions, triggers, views, and grants) in TypeScript, deterministically generating migration SQL from the diff. The name comes from Swedish ("hello, bridge"), and the purpose is just that: a bridge bringing the fragmented parts of database engineering fully into the declarative world.

The core piece is functions. RPCs that once lived as raw SQL strings inside migrations become fully typed TypeScript:

export const publishPost = defineFunction("ddland", "publish_post", {
	args: { postId: uuid() },
	returns: posts,
	security: "definer",
	grants: ["authenticated"],
}, (ctx, { postId }) => {
	const post = ctx.row(select(posts).where(eq(posts.id, postId)));

	ctx.if(isNotNull(post.publishedAt), () => {
		ctx.raise("already published: %", postId);
	});

	ctx.return(
		update(posts)
			.set({ publishedAt: now() })
			.where(eq(posts.id, postId))
			.returning()
	);
});

This code compiles directly to plpgsql. The AI agent only writes TypeScript, only the generated SQL touches the DB, and humans only review the diff. If an agent deletes something wrong, we catch it in review and do not merge. Even after merging, Git retains the full history of changes. The recovery policy is not a bolt-on feature; it is the default behavior of the architecture.

To be transparent: hejbro is pre-alpha, and nothing has been published to npm yet. The core is designed for generic Postgres, with Supabase as the first preset (Neon and Nile are planned). The roadmap and design decisions are open on GitHub.

One more thing: this project is being built by an AI agent team. High-level brainstorming and specs happen with me, while implementation follows a plan, TDD, review, and PR cycle. On day one, we went from an empty scaffold to a working plpgsql compiler. We are using AI agents to build a tool that asks whether we can trust AI with databases. I do not mind that irony at all, because this project proves every day that code can be safely delegated when the underlying structure is sound.