← All blueprints

Migration Guard Custom

blueprint · 4 agents · 2 MCP servers
1042★ stars
133forks
4.7rating

What it does

A reviewer reads the migration and states what it intends to do. An analyst runs it through the database's explain and lock-inspection tools to see how it behaves under load without applying it. The flow then branches on the analyst's verdict: a safe migration gets a brief approval note, and a risky one gets a report naming the exact hazard and the online-safe rewrite. Nothing is applied to the database.

The cast · 4 agents

Analystanalystreasoning

Run the migration through explain and inspect the locks it would take, without applying it. Decide whether it is risky: a full table rewrite, a blocking lock on a large or hot table, or an unindexed backfill all count. Return an object with a 'risky' boolean and the reasons.

db.explaindb.list_locks
Blockerblockerreasoning

Write a report that names the hazard, explains what would happen under load, and gives the online-safe rewrite (for example: add the column nullable, backfill in batches, then set not-null). Save it to a file.

fs.write_file
Approverapproverfast

Write a short note confirming the migration is safe to run and why. Save it to a file.

fs.write_file
Reviewerreviewerreasoning

Read the migration SQL and state plainly what it does: which tables and columns change, and whether it adds, alters, or backfills. Do not judge safety yet.

no tools · reasoning only

Flow

Reviewerintent
Analystanalysis
⑂ branch
analysis.risky == true
Blockerverdict

Interface

Inputs
migrationSqlstring
The migration SQL to review.
Outputs
verdictstring
The approval note or the blocking report, whichever the review produced.

Depends on · 2 MCP servers

io.github.supabase/postgres-mcpnot in the registry yetdb
io.github.modelcontextprotocol/filesystemnot in the registry yetfs
⤓ Export runnable code
# Generated by @socketcat/compiler for target: langgraph
# blueprint: com.socketcat/migration-guard v1.0.0   schema: socketcat.dev/blueprint/v0
# This code is yours. Edit it freely. The socketcat_runtime helper is optional and can be vendored.

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import socketcat_runtime as _rt

class State(TypedDict, total=False):
    migrationSql: object
    verdict: object
    intent: object
    analysis: object

def node_AgentInvoke_0(state):
    return _rt.run_agent("reviewer", state, {"migrationSql":"migrationSql"}, "intent")

def node_AgentInvoke_1(state):
    return _rt.run_agent("analyst", state, {"intent":"intent","migrationSql":"migrationSql"}, "analysis")

def node_Branch_2(state):
    return {}

def node_Join_3(state):
    return {}

def node_AgentInvoke_4(state):
    return _rt.run_agent("blocker", state, {"analysis":"analysis"}, "verdict")

def node_AgentInvoke_5(state):
    return _rt.run_agent("approver", state, {"analysis":"analysis"}, "verdict")

def route_Branch_2(state):
    if _rt.cond("analysis.risky == true", state): return "AgentInvoke_4"
    return "AgentInvoke_5"

def build():
    b = StateGraph(State)
    b.add_node("AgentInvoke_0", node_AgentInvoke_0)
    b.add_node("AgentInvoke_1", node_AgentInvoke_1)
    b.add_node("Branch_2", node_Branch_2)
    b.add_node("Join_3", node_Join_3)
    b.add_node("AgentInvoke_4", node_AgentInvoke_4)
    b.add_node("AgentInvoke_5", node_AgentInvoke_5)
    b.add_edge(START, "AgentInvoke_0")
    b.add_edge("AgentInvoke_0", "AgentInvoke_1")
    b.add_edge("AgentInvoke_1", "Branch_2")
    b.add_conditional_edges("Branch_2", route_Branch_2, {"AgentInvoke_4": "AgentInvoke_4", "AgentInvoke_5": "AgentInvoke_5"})
    b.add_edge("Join_3", END)
    b.add_edge("AgentInvoke_4", "Join_3")
    b.add_edge("AgentInvoke_5", "Join_3")
    return b.compile()

INPUTS = ["migrationSql"]
OUTPUTS = ["verdict"]

if __name__ == "__main__":
    _rt.main(build, INPUTS, OUTPUTS)
blueprint.json (the portable format)
{
  "id": "com.socketcat/migration-guard",
  "flow": {
    "type": "sequence",
    "blocks": [
      {
        "in": {
          "migrationSql": "migrationSql"
        },
        "out": "intent",
        "use": "reviewer",
        "type": "agent"
      },
      {
        "in": {
          "intent": "intent",
          "migrationSql": "migrationSql"
        },
        "out": "analysis",
        "use": "analyst",
        "type": "agent"
      },
      {
        "else": [
          {
            "in": {
              "analysis": "analysis"
            },
            "out": "verdict",
            "use": "approver",
            "type": "agent"
          }
        ],
        "type": "branch",
        "cases": [
          {
            "when": "analysis.risky == true",
            "blocks": [
              {
                "in": {
                  "analysis": "analysis"
                },
                "out": "verdict",
                "use": "blocker",
                "type": "agent"
              }
            ]
          }
        ]
      }
    ]
  },
  "tags": [
    "databases",
    "migrations",
    "safety",
    "branch"
  ],
  "title": "Migration Guard",
  "agents": {
    "analyst": {
      "model": {
        "hint": "reasoning"
      },
      "title": "Analyst",
      "tools": [
        "db.explain",
        "db.list_locks"
      ],
      "output": {
        "type": "object",
        "required": [
          "risky"
        ],
        "properties": {
          "risky": {
            "type": "boolean"
          },
          "reasons": {
            "type": "string"
          }
        }
      },
      "instructions": "Run the migration through explain and inspect the locks it would take, without applying it. Decide whether it is risky: a full table rewrite, a blocking lock on a large or hot table, or an unindexed backfill all count. Return an object with a 'risky' boolean and the reasons."
    },
    "blocker": {
      "model": {
        "hint": "reasoning"
      },
      "title": "Blocker",
      "tools": [
        "fs.write_file"
      ],
      "instructions": "Write a report that names the hazard, explains what would happen under load, and gives the online-safe rewrite (for example: add the column nullable, backfill in batches, then set not-null). Save it to a file."
    },
    "approver": {
      "model": {
        "hint": "fast"
      },
      "title": "Approver",
      "tools": [
        "fs.write_file"
      ],
      "instructions": "Write a short note confirming the migration is safe to run and why. Save it to a file."
    },
    "reviewer": {
      "model": {
        "hint": "reasoning"
      },
      "title": "Reviewer",
      "instructions": "Read the migration SQL and state plainly what it does: which tables and columns change, and whether it adds, alters, or backfills. Do not judge safety yet."
    }
  },
  "estate": {
    "intent": {
      "type": "string",
      "description": "The reviewer's plain reading of what the migration changes."
    },
    "analysis": {
      "type": "object",
      "description": "The analyst's explain output and lock findings, with a 'risky' flag."
    }
  },
  "$schema": "socketcat.dev/blueprint/v0",
  "authors": [
    {
      "url": "https://socketcat.com",
      "name": "SocketCat"
    }
  ],
  "license": "MIT",
  "servers": [
    {
      "ref": "io.github.supabase/postgres-mcp",
      "alias": "db"
    },
    {
      "ref": "io.github.modelcontextprotocol/filesystem",
      "alias": "fs"
    }
  ],
  "summary": "Reviews a proposed SQL migration for locks and cost, then branches to an approval note or a blocking report.",
  "targets": [
    "langgraph",
    "*"
  ],
  "version": "1.0.0",
  "interface": {
    "inputs": {
      "migrationSql": {
        "type": "string",
        "description": "The migration SQL to review."
      }
    },
    "outputs": {
      "verdict": {
        "type": "string",
        "description": "The approval note or the blocking report, whichever the review produced."
      }
    }
  },
  "extensions": {
    "dev.langgraph": {
      "checkpointer": "memory"
    }
  },
  "description": "A reviewer reads the migration and states what it intends to do. An analyst runs it through the database's explain and lock-inspection tools to see how it behaves under load without applying it. The flow then branches on the analyst's verdict: a safe migration gets a brief approval note, and a risky one gets a report naming the exact hazard and the online-safe rewrite. Nothing is applied to the database."
}