DEV Community

erdonline
erdonline

Posted on

Can I veto the agent's DDL before it hits main?

Two hours before go-live, the DDL lands in the group chat

Thursday, 10 p.m. The release window is 1 a.m. Someone drops alter_orders_v47.sql into the channel: "Agent generated it. CI is green. Can we merge?"

You are the DBA. You are not the Tech Lead who clicked Approve this afternoon — that already happened. You are the last person who can still say no. You open the file: add columns, change types, add indexes, attach foreign keys. The parser is happy. The names look like your team's. You know the things that actually blow up are not in the line numbers: how long this table locks tonight, whether NOT NULL without a default survives old rows, whether down can restore the schema you have now.

You do not have time to stand up a production-sized database and run this ALTER. You also do not have production credentials you are willing to hand an Agent so it can "verify." So the veto shows up two hours before go-live, on a change already sitting in a PR about to hit main — not when the intent was first proposed.

Say no and you look like you are blocking the release. Say yes and the postmortem asks which gate was the gate. Last time it went like this: an add-column on biz_order had no default. Staging was small; it passed. Peak production locked waiters and stopped downstream reconciliation for twenty minutes. That SQL was Approved in the afternoon. You saw it at night. The thing you were supposed to veto arrived two time zones late.

This failure has a name: the veto arrived too late

This is not "the DBA is too slow." It has a name — the veto arrived too late.

Agents write DDL faster; humans still review DDL at the old speed. Intent hides in a SQL file that only enters the group chat after merge is already in motion: you see statements that will execute, not "relative to the last approved version, which three changes did the team actually agree to." By the time you speak, the train is already on the bridge.

A Lead's Approve answers "did I look at this intent?" (that is a different job). The DBA has to answer a different sentence: can I say no to the intent before it hits main? Those are not the same button. Approve is signed on the PR. Veto has to happen before the intent is treated as already human-reviewed.

A red line sits on top of that: production credentials do not belong in the IDE. Banning the Agent from the database does not make intent show up earlier — it just sends the Agent off to invent SQL that "looks runnable," which you first see the night before go-live.

Three things you already tried

Review after merge. Useful. That is not a veto; it is an after-the-fact blessing. If that DDL blows up, the record says "DBA looked."

Hand-diff before deploy. Line up the PR SQL against today's CREATE TABLE. You are still aligning statement noise: VARCHAR(64) to VARCHAR(128) fills half the screen; a missing foreign key hides on line 47. When you finish, you still do not know whether those are the three changes the last approved version actually agreed to.

Ban the Agent from the database. Correct. Credentials in .cursor/mcp.json get committed and screenshotted. After the ban the Agent does not disappear. It reads a stale @schema.sql, a permission-filtered information_schema dump, or it invents. Intent is still invisible before merge.

Each of those does half the job. Together you still get executable DDL two hours before go-live.

A live catalog does not save this half of the gate

Someone will say: wire a live-catalog MCP so column names stop hallucinating and the DDL is "more true." That half of the column problem is another article — permission-filtered information_schema is another hallucination, a 240-table dump blows context, tool lists cache until restart. Even when structure reads correctly, what the DBA needs to veto is not "does this column exist."

The gate asks: is this change the intent the team already approved? Can the statements that will run align with the last approved contract? A live catalog shows what the database looks like now. It does not show what humans agreed to. The database is current state; the contract is a promise. Two hours before go-live, lining SQL up against current state only checks whether the Agent invented against production — you still did not get a window to say no.

Structure is not semantics. status CHAR(1) is correct in the catalog; "only '1' is valid, '9' is dirty" lives in comments and review notes, not in the live DB. An Agent that writes a mechanically plausible ALTER from the catalog is the hardest to veto: every line looks familiar; together you cannot tell what it will do.

So the missing piece is not a faster SQL reader. It is an intent diff you can see before merge, plus an optional DDL draft generated from an approved version — still not executed.

Swap the gate object: intent first, DDL second

Move from "Agent dumps SQL → human signs the night before merge" to "Agent declares intent → save a named version → human diffs before merge, optionally reads a DDL draft → can still veto."

The flow:

  1. The team maintains schema in the designer. Each change that is actually accepted saves as a named version. That projectJSON is the contract: human-reviewed, API layer strips profile.dbs credentials, Chinese names and remarks carry tribal knowledge next to the structure.
  2. When the Agent needs a schema change, the only write path in this copy is create_version: submit a suggestion with a note. Do not treat put_project_json as a tutorial — that overwrites the workspace; API 200 is not human approval.
  3. Before merge, the DBA opens a semantic diff against the last approved version: which table was added, which field removed, which FK moved from A to B. Not an eighty-line character diff of ALTER.
  4. When you need a starting point for the migrator, generate a DDL draft from that approved version. The draft is marked previewOnly: true, executed: false. ERD does not execute SQL, does not connect to your database, and does not replace Atlas / Flyway / your CI.
  5. This gate can still say no. The veto happens before intent enters main — not two hours before the release window.

One sentence vs "read SQL the night before go-live": the gate moves earlier; the veto stays; the Agent never gets production credentials. The Lead signs intent (that article stops at the Approve button). You sign "this intent may enter the merge pipeline." Neither gate is "CI was green, so a human looked." When you veto, you leave a traceable intent diff — which table, which column, which FK — not a chat message that says "don't merge tonight."

Be honest about the draft: today it is a conservative CREATE TABLE from a named version, filtered by dialect and table. It is not the ALTER that will run in production, and it is not a down migration. Lock time, backfill, and canaries stay your CI and your window. The draft lets you ask "after the contract looks like this, what does CREATE look like?" — not let an Agent click execute. Missing indexes, missing FKs, dialect type mismatches sit in warnings. You review them. The tool does not pretend it already did.

What MCP does here (you only want it at this point)

By now you might want MCP — not installed in sentence one.

Reading the contract, aligning two versions, and taking a draft that does not execute is enough to move the gate:

  • diff_versions: semantic diff between two named version snapshots. Tables and columns added, removed, modified; table and field remarks; conservative renameCandidates from structural fingerprints. Rename candidates need a human; they are not facts. API success is not approval.
  • preview_ddl: CREATE TABLE draft from one named version snapshot, MySQL / PostgreSQL / SQL Server / Oracle, optional table filter. Returns previewOnly: true, executed: false, and warnings that nothing connected and nothing ran.
  • create_version: the only write in this copy. The Agent reads the approved version, submits a suggestion; a human diffs in the designer.

diff_versions returns a review object, not an ALTER dump:

{
  "summary": {
    "tablesAdded": 0,
    "tablesRemoved": 0,
    "tablesChanged": 1,
    "columnsAdded": 1,
    "columnsRemoved": 0,
    "columnsModified": 2
  },
  "note": "renameCandidates are structural hints for human review, not confirmed renames. API success is not approval."
}
Enter fullscreen mode Exit fullscreen mode

Three changes sit in the summary. You can point at those three and say no without hunting "which three lines are hallucinated" in eighty lines of SQL.

preview_ddl writes the boundary into the payload:

{
  "source": "saved-version-snapshot",
  "dialect": "mysql",
  "previewOnly": true,
  "executed": false,
  "warnings": [
    "Preview only: ERD Online MCP never connects to a database and never executes SQL."
  ]
}
Enter fullscreen mode Exit fullscreen mode

How to read the contract: Read ER diagrams from Cursor via MCP. How to save a version and open a diff: Save a version and view the diff. When a pager asks which approved change introduced a column — that is another job, table-sliced version history. This article does not pretend that exists yet.

The CTA is not "install our MCP"

The next step is not "go install an MCP," and it is not wiring production into the IDE. It is more basic: save a named version so merge has a last approved baseline to align against.

Open the demo, change one table, save a version, change something else, save again, open the diff — in 30 seconds you see what "field added" and "FK retargeted" look like. After that, the next time an Agent drops DDL before a release window, you can reply: "Call create_version first. Send me the diff against the last approved version. DDL drafts come from the approved version. ERD does not execute."

Without a last approved version, the veto only shows up two hours before go-live. With it, you can say no before merge.

{{CTA}}

MCP #DatabaseDesign #ERD #Agent

Top comments (0)