Quản lý phê duyệt trước khi thay đổi cơ sở dữ liệu
Truy vấn đọc chạy ngay; mỗi thay đổi chờ quản lý xem đúng câu SQL và số dòng dự kiến trên điện thoại rồi phê duyệt, sau đó chỉ chạy một lần. Cơ sở dữ liệu không rời khỏi máy chủ của bạn.
Cho nhân viên, script hay trợ lý tự truy vấn cơ sở dữ liệu giúp tiết kiệm hàng giờ. Cho họ tự sửa dữ liệu mà không ai xem qua thì một yêu cầu "dọn giỏ hàng cũ" có thể xóa luôn đơn hàng tuần trước. Hibira đặt một người quản lý ở giữa: truy vấn đọc chạy ngay, còn mỗi thay đổi phải chờ quản lý xem đúng câu SQL và số dòng bị ảnh hưởng trên điện thoại rồi bấm đồng ý. Cơ sở dữ liệu và thông tin kết nối không bao giờ rời khỏi máy chủ của bạn.
Người dùng nhận được gì
- Câu hỏi chỉ đọc có câu trả lời ngay, không phải chờ ai.
- Mỗi thay đổi có một trang phê duyệt mà quản lý mở từ thông báo đẩy hoặc mã QR: câu lệnh, số dòng dự kiến, kế hoạch thực thi và dấu vân tay, cùng nút Phê duyệt và Từ chối.
- Kết quả hiện trên màn hình người yêu cầu ngay khi quản lý quyết định, không cần tải lại. Nếu bị từ chối thì kèm lý do của quản lý.
- Bảo đảm câu lệnh được chạy đúng là câu đã được phê duyệt, và chỉ chạy một lần.

Xây dựng bằng
- Phê duyệt:
POST /apps/APP_ID/approvals(hoặcrequest_approvalcủa MCP server) tạo trang phê duyệt do Hibira lưu trữ và báo quyết định trên một kênh. - Kênh riêng tư cho mỗi bảng điều khiển hoặc mỗi lần chạy, ví dụ
private-sqlgate-<random>, nơi sự kiệnapprovalđến. Không cần hỏi lại liên tục. - Thông báo đẩy (tùy chọn) để gửi liên kết tới điện thoại của quản lý.
- @hibira-io/approvals/sql:
classifySql(),sqlFingerprint()vàassertApprovedSql()cho Node.js, và các hàm tương tự cho Python.
Thử trên demos.hibira.io: chọn một thay đổi, quét mã QR bằng điện thoại và phê duyệt.
Bước 1: cho truy vấn đọc đi qua, chặn mọi thứ khác
classifySql() chỉ coi một câu là truy vấn đọc khi chắc chắn: một câu lệnh duy nhất, bắt đầu bằng SELECT, WITH, EXPLAIN hoặc SHOW, và không có phần nào sửa dữ liệu. Nhiều câu lệnh, WITH ... DELETE, EXPLAIN ANALYZE, SELECT ... INTO, FOR UPDATE và bất cứ thứ gì không phân tích chắc chắn được đều là thay đổi. Dù vậy vẫn nên chạy truy vấn đọc trong giao dịch chỉ đọc, để câu bị đánh giá sai sẽ thất bại thay vì ghi dữ liệu:
import pg from 'pg';
import { classifySql } from '@hibira-io/approvals/sql';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
export async function runSql(sql, requestedBy) {
if (classifySql(sql) === 'read') return { rows: await readOnly(sql) };
return { approval: await askSupervisor(sql, requestedBy) };
}
async function readOnly(sql) {
const db = await pool.connect();
try {
await db.query('BEGIN READ ONLY');
const { rows } = await db.query(sql);
await db.query('COMMIT');
return rows.slice(0, 200);
} catch (err) {
await db.query('ROLLBACK');
throw err;
} finally {
db.release();
}
}Bước 2: cho quản lý thấy thay đổi sẽ làm gì
Ước tính số dòng trước khi xin phê duyệt. EXPLAIN không chạy câu lệnh; chạy thử trong giao dịch được hoàn tác cho số chính xác nhưng câu lệnh thực sự chạy, nên hãy đặt thời gian chờ. Rồi lấy dấu vân tay của đúng văn bản câu lệnh: phê duyệt chỉ có hiệu lực với câu này.
import { sqlFingerprint } from '@hibira-io/approvals/sql';
async function preview(sql) {
const db = await pool.connect();
try {
await db.query('BEGIN');
await db.query("SET LOCAL statement_timeout = '5s'");
const { rowCount } = await db.query(sql);
return { rows: rowCount, fingerprint: await sqlFingerprint(sql) };
} finally {
await db.query('ROLLBACK');
db.release();
}
}Bước 3: xin phê duyệt
Tạo yêu cầu với câu lệnh và bản xem trước trong details, cùng kênh để báo quyết định. Giữ câu lệnh và dấu vân tay ở phía bạn, gắn với ID phê duyệt. Thứ duy nhất gửi tới Hibira là nội dung yêu cầu: không có thông tin kết nối, không có dòng dữ liệu nào.
import Pusher from 'pusher';
const hibira = new Pusher({ appId: process.env.HIBIRA_APP_ID, key: process.env.HIBIRA_KEY, secret: process.env.HIBIRA_SECRET, host: 'ws.hibira.io', useTLS: true });
async function askSupervisor(sql, requestedBy) {
const { rows, fingerprint } = await preview(sql);
const res = await hibira.post({
path: '/approvals',
body: {
title: 'Run this database change?',
details: `${sql}\n\nEstimated rows: ${rows}\n\nSHA-256: ${fingerprint}`,
options: ['Approve', 'Reject'],
channel: `private-sqlgate-${requestedBy.consoleId}`,
push_users: ['supervisor-42'],
expires_in_minutes: 15,
agent: 'SQL console',
},
});
const approval = await res.json();
await pool.query('INSERT INTO pending_changes (approval_id, sql, fingerprint) VALUES ($1, $2, $3)', [approval.approval_id, sql, fingerprint]);
return { id: approval.approval_id, url: approval.url, rows };
}Quản lý nhận thông báo đẩy kèm liên kết (hoặc quét mã trên màn hình người yêu cầu) và trả lời trên trang hiển thị bằng ngôn ngữ của họ.

Bước 4: chạy một lần khi có câu trả lời
Quyết định đến dưới dạng sự kiện approval trên kênh. Hãy coi nó như chuông cửa: đọc câu trả lời từ API một lần, kiểm tra câu lệnh đã lưu vẫn khớp dấu vân tay, rồi mới chạy. Giữ chỗ dòng đó trước thì câu lệnh chỉ chạy một lần, kể cả khi hai sự kiện hoặc hai worker đến cùng lúc:
import { assertApprovedSql } from '@hibira-io/approvals/sql';
// In the requester's page (pusher-js), or a worker that keeps a connection open:
channel.bind('approval', ({ id, reason }) => {
if (reason === 'decided') fetch(`/api/changes/${id}/run`, { method: 'POST' });
});
// POST /api/changes/:id/run on your server:
export async function runApproved(id) {
const res = await hibira.get({ path: `/approvals/${id}` });
const answer = await res.json();
if (answer.status !== 'approved') return { status: answer.status, comment: answer.comment };
const { rows } = await pool.query('UPDATE pending_changes SET ran_at = now() WHERE approval_id = $1 AND ran_at IS NULL RETURNING sql, fingerprint', [id]);
if (!rows.length) return { status: 'already run' };
await assertApprovedSql(rows[0].sql, rows[0].fingerprint);
const result = await pool.query(rows[0].sql);
return { status: 'ran', rows: result.rowCount };
}
Bạn đã kết nối cơ sở dữ liệu qua một MCP server?
Nếu nhóm của bạn làm việc với cơ sở dữ liệu từ Claude, Cursor, ChatGPT hoặc một ứng dụng MCP khác, thông qua server như DBHub, postgres-mcp hay Supabase MCP, chỉ cần đặt mcp-approval-gate phía trước server đó, không cần sửa mã. Truy vấn đọc đi qua ngay; mỗi lệnh thêm, sửa, xóa hay thay đổi cấu trúc đều dừng ở cùng trang phê duyệt, rồi chạy đúng một lần như đã được duyệt.
npx -y @hibira-io/mcp-approval-gate --label shop-production --push-interests db-approvals \
-- npx -y @bytebase/dbhub --transport stdio --dsn "$DATABASE_URL"Cùng một chốt chặn trong công cụ của bạn
- Vercel AI SDK, Mastra, LangGraph (JavaScript và Python): @hibira-io/approvals có công thức cho từng loại: viết
needsApprovalhoặcrequireApprovalthành hàm xét theo câu lệnh, và hộp thư phê duyệt trong ứng dụng của bạn hiển thị bản xem trước dưới mỗi lệnh gọi. - Laravel: filament-approvals xin phê duyệt ngay trong bảng Filament; README có công cụ
RunSqlcho Laravel AI SDK. - n8n, Make, Activepieces và Dify: một bước mã phân loại câu lệnh, bước phê duyệt của Hibira hỏi quản lý và chờ, còn bước cơ sở dữ liệu chỉ nối vào nhánh phê duyệt. README của từng công cụ có quy trình đầy đủ.
- Claude, Cursor và các ứng dụng MCP khác:
request_approvalcủa MCP server xin phê duyệt theo cùng cách ngay trong cuộc trò chuyện. - Mọi database MCP server: mcp-approval-gate bọc DBHub, postgres-mcp, Supabase MCP và các server khác mà không cần sửa mã.