pg-admin-mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +608 -2
  2. package/dist/config/env.js +67 -0
  3. package/dist/config/env.js.map +1 -0
  4. package/dist/config/profiles.js +257 -0
  5. package/dist/config/profiles.js.map +1 -0
  6. package/dist/core/audit-context.js +21 -0
  7. package/dist/core/audit-context.js.map +1 -0
  8. package/dist/core/audit.js +91 -0
  9. package/dist/core/audit.js.map +1 -0
  10. package/dist/core/brands.js +24 -0
  11. package/dist/core/brands.js.map +1 -0
  12. package/dist/core/classifier.js +279 -0
  13. package/dist/core/classifier.js.map +1 -0
  14. package/dist/core/errors.js +197 -0
  15. package/dist/core/errors.js.map +1 -0
  16. package/dist/core/format.js +258 -0
  17. package/dist/core/format.js.map +1 -0
  18. package/dist/core/guards.js +513 -0
  19. package/dist/core/guards.js.map +1 -0
  20. package/dist/core/identifiers.js +431 -0
  21. package/dist/core/identifiers.js.map +1 -0
  22. package/dist/core/limits.js +79 -0
  23. package/dist/core/limits.js.map +1 -0
  24. package/dist/core/logger.js +34 -0
  25. package/dist/core/logger.js.map +1 -0
  26. package/dist/core/paginate.js +195 -0
  27. package/dist/core/paginate.js.map +1 -0
  28. package/dist/core/paths.js +392 -0
  29. package/dist/core/paths.js.map +1 -0
  30. package/dist/core/policy.js +98 -0
  31. package/dist/core/policy.js.map +1 -0
  32. package/dist/core/shutdown.js +10 -0
  33. package/dist/core/shutdown.js.map +1 -0
  34. package/dist/core/stdout-guard.js +25 -0
  35. package/dist/core/stdout-guard.js.map +1 -0
  36. package/dist/db/catalog.js +1006 -0
  37. package/dist/db/catalog.js.map +1 -0
  38. package/dist/db/pool.js +158 -0
  39. package/dist/db/pool.js.map +1 -0
  40. package/dist/db/session.js +258 -0
  41. package/dist/db/session.js.map +1 -0
  42. package/dist/db/types.js +2 -0
  43. package/dist/db/types.js.map +1 -0
  44. package/dist/index.js +91 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/resources/index.js +88 -0
  47. package/dist/resources/index.js.map +1 -0
  48. package/dist/runtime/binaries.js +356 -0
  49. package/dist/runtime/binaries.js.map +1 -0
  50. package/dist/runtime/env-file.js +62 -0
  51. package/dist/runtime/env-file.js.map +1 -0
  52. package/dist/runtime/environment.js +34 -0
  53. package/dist/runtime/environment.js.map +1 -0
  54. package/dist/server.js +106 -0
  55. package/dist/server.js.map +1 -0
  56. package/dist/snapshot/capture.js +189 -0
  57. package/dist/snapshot/capture.js.map +1 -0
  58. package/dist/snapshot/diff.js +254 -0
  59. package/dist/snapshot/diff.js.map +1 -0
  60. package/dist/snapshot/migrate.js +1596 -0
  61. package/dist/snapshot/migrate.js.map +1 -0
  62. package/dist/snapshot/model.js +80 -0
  63. package/dist/snapshot/model.js.map +1 -0
  64. package/dist/tools/_archive_meta.js +138 -0
  65. package/dist/tools/_archive_meta.js.map +1 -0
  66. package/dist/tools/_batch_shared.js +73 -0
  67. package/dist/tools/_batch_shared.js.map +1 -0
  68. package/dist/tools/_export_format.js +107 -0
  69. package/dist/tools/_export_format.js.map +1 -0
  70. package/dist/tools/_object_kinds.js +97 -0
  71. package/dist/tools/_object_kinds.js.map +1 -0
  72. package/dist/tools/_relation_detail.js +55 -0
  73. package/dist/tools/_relation_detail.js.map +1 -0
  74. package/dist/tools/_shared.js +17 -0
  75. package/dist/tools/_shared.js.map +1 -0
  76. package/dist/tools/_snapshot_io.js +238 -0
  77. package/dist/tools/_snapshot_io.js.map +1 -0
  78. package/dist/tools/admin/_shared.js +18 -0
  79. package/dist/tools/admin/_shared.js.map +1 -0
  80. package/dist/tools/admin/extensions.js +80 -0
  81. package/dist/tools/admin/extensions.js.map +1 -0
  82. package/dist/tools/admin/grants.js +207 -0
  83. package/dist/tools/admin/grants.js.map +1 -0
  84. package/dist/tools/admin/index.js +138 -0
  85. package/dist/tools/admin/index.js.map +1 -0
  86. package/dist/tools/admin/maintenance.js +106 -0
  87. package/dist/tools/admin/maintenance.js.map +1 -0
  88. package/dist/tools/admin/roles.js +144 -0
  89. package/dist/tools/admin/roles.js.map +1 -0
  90. package/dist/tools/admin/sessions.js +104 -0
  91. package/dist/tools/admin/sessions.js.map +1 -0
  92. package/dist/tools/admin/stats.js +63 -0
  93. package/dist/tools/admin/stats.js.map +1 -0
  94. package/dist/tools/backup.js +288 -0
  95. package/dist/tools/backup.js.map +1 -0
  96. package/dist/tools/connection_status.js +138 -0
  97. package/dist/tools/connection_status.js.map +1 -0
  98. package/dist/tools/copy.js +355 -0
  99. package/dist/tools/copy.js.map +1 -0
  100. package/dist/tools/delete_batch.js +73 -0
  101. package/dist/tools/delete_batch.js.map +1 -0
  102. package/dist/tools/describe_object.js +87 -0
  103. package/dist/tools/describe_object.js.map +1 -0
  104. package/dist/tools/execute_sql.js +395 -0
  105. package/dist/tools/execute_sql.js.map +1 -0
  106. package/dist/tools/explain.js +134 -0
  107. package/dist/tools/explain.js.map +1 -0
  108. package/dist/tools/export.js +215 -0
  109. package/dist/tools/export.js.map +1 -0
  110. package/dist/tools/get_schema.js +148 -0
  111. package/dist/tools/get_schema.js.map +1 -0
  112. package/dist/tools/index.js +40 -0
  113. package/dist/tools/index.js.map +1 -0
  114. package/dist/tools/insert_batch.js +212 -0
  115. package/dist/tools/insert_batch.js.map +1 -0
  116. package/dist/tools/list_objects.js +160 -0
  117. package/dist/tools/list_objects.js.map +1 -0
  118. package/dist/tools/prepare_migration.js +112 -0
  119. package/dist/tools/prepare_migration.js.map +1 -0
  120. package/dist/tools/restore.js +301 -0
  121. package/dist/tools/restore.js.map +1 -0
  122. package/dist/tools/schema_diff.js +79 -0
  123. package/dist/tools/schema_diff.js.map +1 -0
  124. package/dist/tools/schema_snapshot.js +57 -0
  125. package/dist/tools/schema_snapshot.js.map +1 -0
  126. package/dist/tools/select.js +162 -0
  127. package/dist/tools/select.js.map +1 -0
  128. package/dist/tools/update_batch.js +154 -0
  129. package/dist/tools/update_batch.js.map +1 -0
  130. package/package.json +47 -5
@@ -0,0 +1,513 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { withTx } from '../db/session.js';
3
+ import { noteAudit } from './audit-context.js';
4
+ import { scanSegments } from './classifier.js';
5
+ import { PgMcpError } from './errors.js';
6
+ import { confirmPolicyFor, effectiveRowDrift, requireCapability } from './policy.js';
7
+ function missingWhere(message, hint, ctx, details = {}) {
8
+ throw new PgMcpError({
9
+ code: 'GUARD_MISSING_WHERE',
10
+ message,
11
+ hint,
12
+ details: {
13
+ profile: ctx.profile,
14
+ operation: ctx.operation,
15
+ ...(ctx.target === undefined ? {} : { target: ctx.target }),
16
+ ...details,
17
+ },
18
+ });
19
+ }
20
+ /** A structured `where` must carry at least one predicate — an empty array is an unfiltered statement written the polite way. */
21
+ export function assertStructuredWhere(predicateCount, ctx) {
22
+ if (predicateCount > 0)
23
+ return;
24
+ missingWhere(`${ctx.operation} was called with no filter, which would affect every row of ${ctx.target ?? 'the target relation'}`, 'add at least one predicate to `where`; to empty a table deliberately, use postgres_execute_sql with TRUNCATE ' +
25
+ 'on a profile that allows DDL', ctx);
26
+ }
27
+ /**
28
+ * Words that carry no row-dependence. A WHERE clause built **only** out of
29
+ * these plus literals and operators matches every row, so it is refused.
30
+ * Anything else — a column, a function call, a subquery — counts as a real
31
+ * reference and passes.
32
+ */
33
+ const CONSTANT_WORDS = new Set([
34
+ 'and',
35
+ 'or',
36
+ 'not',
37
+ 'is',
38
+ 'isnull',
39
+ 'notnull',
40
+ 'true',
41
+ 'false',
42
+ 'null',
43
+ 'unknown',
44
+ 'between',
45
+ 'symmetric',
46
+ 'asymmetric',
47
+ 'in',
48
+ 'like',
49
+ 'ilike',
50
+ 'similar',
51
+ 'to',
52
+ 'escape',
53
+ 'distinct',
54
+ 'from',
55
+ 'as',
56
+ 'collate',
57
+ 'cast',
58
+ 'uescape',
59
+ ]);
60
+ /**
61
+ * The letters PostgreSQL allows in front of a string literal (`E'…'`, `B'…'`,
62
+ * `X'…'`, `N'…'`, `U&'…'`). The scanner hands the literal back as a literal but
63
+ * the prefix back as *code*, where a bare `E` reads exactly like a column name —
64
+ * which is how `WHERE E'a' = E'a'` used to pass the check below.
65
+ */
66
+ const LITERAL_PREFIX_RE = /(?:[EeBbXxNn]|[Uu]&)$/;
67
+ /**
68
+ * A word that could be a column or a function name. The non-ASCII range is
69
+ * spelled with escapes so this file stays pure ASCII, same rule as
70
+ * `core/classifier.ts` and for the same reason: an invisible character in a
71
+ * security-relevant scanner is what nobody spots in review.
72
+ */
73
+ const WORD_RE = /[A-Za-z_\u0080-\uFFFF][A-Za-z0-9_$\u0080-\uFFFF]*/g;
74
+ /**
75
+ * A cast's type name (`'a'::text`, `id::bigint[]`) is code, but it is no more a
76
+ * column reference than the `text` in `CAST('a' AS text)` is. Struck out before
77
+ * the word scan so `WHERE 'a'::text = 'a'` is refused like `WHERE 'a' = 'a'`.
78
+ * Built from {@link WORD_RE} so the two agree on what a word is.
79
+ */
80
+ const CAST_TYPE_RE = new RegExp(`::\\s*${WORD_RE.source}(?:\\s*\\[\\s*\\])*`, 'g');
81
+ /**
82
+ * Blanks every literal, comment and terminator, preserving offsets, so a
83
+ * keyword search sees only real SQL code. `WHERE` inside `'…'`, `"…"`,
84
+ * `$tag$…$tag$`, `-- …` or a nested `/* … *\/` is invisible here, exactly as it
85
+ * is to PostgreSQL. The scanner is `core/classifier.ts`'s — the same one that
86
+ * backs multi-statement rejection, so the two cannot disagree about where a
87
+ * literal ends.
88
+ */
89
+ function codeMask(sql) {
90
+ let out = '';
91
+ for (const seg of scanSegments(sql)) {
92
+ const text = sql.slice(seg.start, seg.end);
93
+ if (seg.kind === 'code') {
94
+ out += text;
95
+ continue;
96
+ }
97
+ // The `E`/`B`/`X`/`N`/`U&` in front of a literal is part of the literal's
98
+ // syntax, not a reference to anything — blank it with the literal it opens.
99
+ // Length-preserving, so offsets still line up.
100
+ out = out.replace(LITERAL_PREFIX_RE, (prefix) => ' '.repeat(prefix.length));
101
+ out += ' '.repeat(text.length);
102
+ }
103
+ // `segments` partitions the whole input, so offsets are preserved exactly.
104
+ return out;
105
+ }
106
+ /** Index just past a depth-0 occurrence of `keyword`, or -1. Parenthesised occurrences (a subquery's own WHERE) are skipped. */
107
+ function findTopLevelKeyword(mask, keyword) {
108
+ const re = new RegExp(`\\b${keyword}\\b`, 'gi');
109
+ let depth = 0;
110
+ let scanned = 0;
111
+ for (let match = re.exec(mask); match !== null; match = re.exec(mask)) {
112
+ for (; scanned < match.index; scanned++) {
113
+ const ch = mask.charAt(scanned);
114
+ if (ch === '(')
115
+ depth++;
116
+ else if (ch === ')')
117
+ depth--;
118
+ }
119
+ if (depth === 0)
120
+ return match.index + match[0].length;
121
+ }
122
+ return -1;
123
+ }
124
+ /**
125
+ * Rejects a raw `UPDATE`/`DELETE` whose WHERE clause is absent or matches
126
+ * everything.
127
+ *
128
+ * **What this cannot do, stated plainly.** It is a lexical check, not a
129
+ * planner. `WHERE id = id`, `WHERE x IS NOT NULL` on a `NOT NULL` column,
130
+ * `WHERE coalesce(x, x) = x`, `WHERE id > -1` — every one of those references a
131
+ * column, passes here, and still matches every row. Enumerating tautologies is
132
+ * a losing game, and a check that appeared to win it would be trusted as a
133
+ * guarantee it cannot provide.
134
+ *
135
+ * So this guard catches exactly one class: the clause that is *obviously*
136
+ * unconditional because it mentions no column at all — no WHERE, `WHERE true`,
137
+ * `WHERE 1=1`, `WHERE 'a' = 'a'`, `WHERE E'a' = E'a'`, `WHERE 'a'::text = 'a'`,
138
+ * `WHERE (1=1) AND true`, `WHERE NOT false`. It is the cheap guard, and its
139
+ * value is a fast, readable refusal.
140
+ *
141
+ * Even inside that one class it is lexical, so a spelling that hides a constant
142
+ * behind something word-shaped still slips past: `CAST('a' AS text) = 'a'` (the
143
+ * type name after `AS` is indistinguishable from a column here),
144
+ * `'a'::pg_catalog.text = 'a'`, `length('a') > 0`. Those are refusals not made,
145
+ * never wrong commits — and the row count below is what actually stops one.
146
+ *
147
+ * **The row count is the real guard.** The dry run runs the actual mutation and
148
+ * reports the actual number of rows it touched; an agent that asked to delete
149
+ * one row and is shown 4,000,000 has the information the WHERE check could
150
+ * never have derived. Do not oversell this function.
151
+ */
152
+ export function assertRawWhere(sql, ctx) {
153
+ const mask = codeMask(sql);
154
+ const whereEnd = findTopLevelKeyword(mask, 'WHERE');
155
+ if (whereEnd === -1) {
156
+ missingWhere(`${ctx.operation} received a statement with no top-level WHERE clause, which would affect every row`, 'add a WHERE clause that selects only the rows you mean; to empty a table deliberately, use TRUNCATE on a ' +
157
+ 'profile that allows DDL', ctx);
158
+ }
159
+ // `DELETE … WHERE … RETURNING *` — the RETURNING list is not part of the filter.
160
+ const returningStart = findTopLevelKeyword(mask.slice(whereEnd), 'RETURNING');
161
+ const clause = returningStart === -1 ? mask.slice(whereEnd) : mask.slice(whereEnd, whereEnd + returningStart - 'RETURNING'.length);
162
+ const words = clause.replace(CAST_TYPE_RE, '').match(WORD_RE) ?? [];
163
+ if (words.some((word) => !CONSTANT_WORDS.has(word.toLowerCase())))
164
+ return;
165
+ missingWhere(`${ctx.operation} received a WHERE clause that references no column (${clause.trim() === '' ? 'empty' : clause.trim()}), so it matches every row`, 'add a predicate on a real column; to empty a table deliberately, use TRUNCATE on a profile that allows DDL', ctx, { clause: clause.trim() });
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // The hash
169
+ // ---------------------------------------------------------------------------
170
+ /**
171
+ * Canonicalizes SQL text for hashing. **Exactly two things change, and nothing
172
+ * else:**
173
+ *
174
+ * 1. **Whitespace runs outside literals and comments collapse.** A run
175
+ * containing a newline becomes a single `\n`; any other run becomes a single
176
+ * space. Respacing within a line is free; **introducing or removing a line
177
+ * break is not** and costs one extra dry run.
178
+ *
179
+ * That asymmetry is the point, not an oversight. A line comment is
180
+ * terminated by a newline, so collapsing newlines into spaces would give
181
+ * `SELECT 1 -- c\nFROM t` and `SELECT 1 -- c FROM t` — two statements that
182
+ * mean entirely different things — the same digest, and a confirming call
183
+ * could then execute something the dry run never previewed. Every
184
+ * normalization decision in this function fails in the *mismatch* direction:
185
+ * the worst case is a re-run of the dry run, never a wrong commit.
186
+ * Whitespace inside a string, a quoted identifier or a dollar-quoted body is
187
+ * part of the value and is left alone.
188
+ * 2. **Trailing `;` and trailing whitespace are dropped**, and internal
189
+ * terminators normalize to a bare `;`. `DELETE …` and `DELETE …;` are the
190
+ * same statement.
191
+ *
192
+ * Deliberately **not** normalized:
193
+ *
194
+ * - **Case.** `"Col"` and `"col"` are different columns; `$Tag$` and `$tag$`
195
+ * are different dollar-quote tags. Folding buys nothing (an agent replaying a
196
+ * statement does not re-case it) and could merge two statements that differ.
197
+ * - **Comments.** Stripping them would buy full reformat-insensitivity, and it
198
+ * was rejected: it would make a *security* decision depend on the scanner
199
+ * agreeing with PostgreSQL about where a comment ends. Keeping them means a
200
+ * scanner disagreement can only produce a false mismatch (a re-run), never a
201
+ * false match (a wrong commit). The hash is the state; it covers what was
202
+ * actually sent.
203
+ * - **Parameter values.** They are not in the SQL at all — they are hashed
204
+ * separately as `JSON(params)`, so `$1 = 7` and `$1 = 8` differ.
205
+ */
206
+ export function normalizeSql(sql) {
207
+ let out = '';
208
+ for (const seg of scanSegments(sql)) {
209
+ const text = sql.slice(seg.start, seg.end);
210
+ if (seg.kind === 'code')
211
+ out += text.replace(/\s+/g, (ws) => (ws.includes('\n') ? '\n' : ' '));
212
+ else if (seg.kind === 'terminator')
213
+ out += ';';
214
+ else
215
+ out += text;
216
+ }
217
+ return out.replace(/[\s;]+$/, '').trim();
218
+ }
219
+ /**
220
+ * `sha256(profile ∥ database ∥ normalizedSql ∥ JSON(params))`, per design.md.
221
+ * The four parts go through `JSON.stringify` of a tuple rather than string
222
+ * concatenation, so the separators are unambiguous and no combination of a
223
+ * profile alias and a statement can be re-spelled as a different pair.
224
+ *
225
+ * **Deliberately not the cursor shape hash.** `core/paginate.ts#computeShapeHash`
226
+ * is 64 bits of sha256 and exists to stop a cursor being replayed against a
227
+ * differently-shaped query — a *correctness* construct where a collision costs
228
+ * a rejected page. This one is a *security* construct: it is what stands
229
+ * between "the agent previewed X" and "the agent committed Y", so it keeps the
230
+ * full 256-bit digest and lives in a different module. Do not merge them.
231
+ */
232
+ export function computeSqlHash(input) {
233
+ return createHash('sha256')
234
+ .update(JSON.stringify([input.profile, input.database, normalizeSql(input.sql), input.params]))
235
+ .digest('hex');
236
+ }
237
+ // ---------------------------------------------------------------------------
238
+ // The two-call protocol
239
+ // ---------------------------------------------------------------------------
240
+ /** Named rather than random so it is greppable in a server log. */
241
+ const TARGET_CTE = 'pg_admin_mcp_target';
242
+ const KEYS_CTE = 'pg_admin_mcp_keys';
243
+ /** design.md: "SELECT up to 20 affected primary keys for the preview". */
244
+ export const PREVIEW_LIMIT = 20;
245
+ /**
246
+ * Wraps the mutation so PostgreSQL returns the row count, the key checksum and
247
+ * the preview in **one** round trip.
248
+ *
249
+ * A data-modifying CTE executes exactly once, so this is the same single
250
+ * mutation either way — but the affected keys never cross the wire as
251
+ * individual rows, which matters when the statement touches millions of them.
252
+ *
253
+ * Computing the checksum from the mutation's own `RETURNING` rather than from a
254
+ * `SELECT` issued just before it is a strengthening, not a shortcut: there is
255
+ * no window at all between "the rows I measured" and "the rows I changed".
256
+ *
257
+ * `COLLATE "C"` pins the sort to byte order, so the checksum does not depend on
258
+ * the database's collation or on any lc_collate change between the two calls.
259
+ */
260
+ function buildProbedSql(sql, keyColumns) {
261
+ return [
262
+ `WITH ${TARGET_CTE} AS (`,
263
+ sql,
264
+ // `::text` on every key column, deliberately: `to_jsonb` renders a bigint
265
+ // as a JSON *number*, and a bigint past 2^53 loses precision the moment the
266
+ // driver parses it — the preview would then show a key that does not exist.
267
+ // Text also makes the sort, and therefore the checksum, independent of the
268
+ // column's type.
269
+ `RETURNING ${keyColumns.map((column) => `${column}::text AS ${column}`).join(', ')}`,
270
+ `), ${KEYS_CTE} AS (`,
271
+ `SELECT ${TARGET_CTE}::text AS key_text, to_jsonb(${TARGET_CTE}) AS key_json FROM ${TARGET_CTE}`,
272
+ ')',
273
+ 'SELECT count(*)::text AS row_count,',
274
+ `md5(coalesce(string_agg(key_text, E'\\n' ORDER BY key_text COLLATE "C"), '')) AS key_checksum,`,
275
+ `to_jsonb((array_agg(key_json ORDER BY key_text COLLATE "C"))[1:${PREVIEW_LIMIT}]) AS preview`,
276
+ `FROM ${KEYS_CTE}`,
277
+ ].join('\n');
278
+ }
279
+ /**
280
+ * Runs the mutation. `queryMode: 'extended'` is not decoration — see
281
+ * `tools/execute_sql.ts`'s header: without it, `pg` uses simple-query mode
282
+ * whenever `params` is empty, which would run a multi-statement string as one
283
+ * implicit transaction. That is the structural fence behind
284
+ * `planStatements`.
285
+ */
286
+ async function runMutation(client, text, params, probed) {
287
+ if (!probed) {
288
+ const res = await client.query({ text, values: [...params], queryMode: 'extended' });
289
+ return { rowCount: res.rowCount ?? 0, keyChecksum: undefined, preview: [] };
290
+ }
291
+ const res = await client.query({ text, values: [...params], queryMode: 'extended' });
292
+ const row = res.rows[0];
293
+ // `to_jsonb(NULL::jsonb[])` is the JSON scalar `null`, which is what a
294
+ // zero-row mutation produces — hence the shape check rather than `?? []`.
295
+ const preview = row !== undefined && Array.isArray(row.preview) ? row.preview : [];
296
+ return {
297
+ rowCount: Number(row?.row_count ?? '0'),
298
+ keyChecksum: row?.key_checksum,
299
+ preview,
300
+ };
301
+ }
302
+ /**
303
+ * Paste into every guarded tool's `description` so the honest limitation
304
+ * reaches the agent, not just this file.
305
+ *
306
+ * The limitation is stated as it actually behaves, which is **not** what
307
+ * design.md's one-line summary claims. `keyChecksum` is `md5` over the sorted
308
+ * list of affected **primary keys**, so it pins *which* rows a statement
309
+ * touches — it says nothing about their contents. A row whose non-key columns
310
+ * changed between the two calls, leaving the primary-key set and the row count
311
+ * identical, is invisible to both checks. What `keyChecksum` does close, and
312
+ * `rowCount` alone does not, is the substitution: one matching row disappearing
313
+ * while a different one starts matching keeps the count identical and changes
314
+ * the key set. That is why production requires it.
315
+ */
316
+ export const CONFIRM_PROTOCOL_DESCRIPTION = 'Two calls. Call it once WITHOUT confirm to get a dry run: the real mutation runs inside a transaction that is ' +
317
+ 'always rolled back, and you get back the true row count, up to 20 affected primary keys, and an `expect` token. ' +
318
+ 'Read the row count — it is the guard that matters. Then call again with confirm:true and `expect` passed back ' +
319
+ 'verbatim; the server recomputes the hash from your arguments (nothing is stored between calls), re-verifies the ' +
320
+ 'row count and key checksum INSIDE the committing transaction, and rolls back instead of committing if anything ' +
321
+ 'moved. LIMITATION: keyChecksum is md5 over the sorted primary keys of the affected rows, so it detects a ' +
322
+ 'different SET of rows (including a substitution that keeps the count identical) but NOT a change to a row\'s ' +
323
+ 'contents when its primary key and the total row count both stayed the same. envClass=production requires ' +
324
+ '`expect` including keyChecksum and forces allowRowDrift to 0 — the one exception is INSERT, which creates the rows ' +
325
+ 'it affects instead of selecting pre-existing ones, so there production verifies the row count alone (still with ' +
326
+ '`expect` and the hash). development accepts a bare confirm:true.';
327
+ function confirmRequired(message, hint, profile, details = {}) {
328
+ throw new PgMcpError({
329
+ code: 'GUARD_CONFIRM_REQUIRED',
330
+ message,
331
+ hint,
332
+ details: { profile: profile.alias, envClass: profile.envClass, ...details },
333
+ });
334
+ }
335
+ /**
336
+ * The dry-run / confirm protocol, end to end.
337
+ *
338
+ * Call 1 (`confirm` absent or false) runs the mutation under
339
+ * `withTx({ rollbackAlways: true })` and returns a `dryRun` outcome carrying the
340
+ * `expect` token. Call 2 (`confirm: true`) recomputes the hash from the
341
+ * incoming arguments, then re-runs the mutation in a real transaction and
342
+ * commits only if the checksum and row count still hold.
343
+ *
344
+ * Callers must have resolved identifiers **before** calling this: it opens a
345
+ * transaction and takes a pool checkout, and taking a second checkout inside
346
+ * one is the deadlock the Phase 2 review found (`resolveRelation` opens its own
347
+ * session). Nothing inside this function acquires a client.
348
+ */
349
+ export async function runGuardedMutation(req) {
350
+ requireCapability(req.capability, req.profile);
351
+ const policy = confirmPolicyFor(req.profile.envClass);
352
+ const executor = req.executor;
353
+ const wrapped = executor === undefined && req.keyColumns.length > 0;
354
+ const probed = executor === undefined ? wrapped : executor.providesKeyChecksum;
355
+ // See `noPreexistingRows`: an INSERT's affected rows are its own input, so
356
+ // `sqlHash` + `rowCount` is the whole receipt and production accepts it.
357
+ const checksumRequired = policy.requireKeyChecksum && req.noPreexistingRows !== true;
358
+ if (checksumRequired && !probed) {
359
+ // Refused here rather than after a dry run the agent could never confirm.
360
+ // The two ways to get here are different situations and the caller can act
361
+ // on exactly one of them, so they do not share a message.
362
+ if (executor !== undefined) {
363
+ confirmRequired(`profile "${req.profile.alias}" is envClass ${req.profile.envClass}, which requires expect.keyChecksum, but ` +
364
+ `${req.operation} issues its statement through an executor that reports no checksum`, 'use a profile whose PG_<ALIAS>_ENV is development or staging — this operation cannot pin which rows it ' +
365
+ 'wrote, so there is nothing a production confirm could verify', req.profile, { operation: req.operation });
366
+ }
367
+ confirmRequired(`profile "${req.profile.alias}" is envClass ${req.profile.envClass}, which requires expect.keyChecksum, but ` +
368
+ `${req.operation} has no primary key to compute one from`, 'run this against a relation with a primary key, or use a profile whose PG_<ALIAS>_ENV is development or staging', req.profile, { operation: req.operation });
369
+ }
370
+ const text = wrapped ? buildProbedSql(req.sql, req.keyColumns) : req.sql;
371
+ // The hash covers the statement that actually executes, wrapper included —
372
+ // so the key columns are folded in for free and the digest is provably the
373
+ // text PostgreSQL received, not an approximation of it.
374
+ const sqlHash = computeSqlHash({
375
+ profile: req.profile.alias,
376
+ database: req.profile.database,
377
+ sql: text,
378
+ params: req.params,
379
+ });
380
+ const execute = async (client) => {
381
+ if (executor === undefined)
382
+ return runMutation(client, text, req.params, wrapped);
383
+ const done = await executor.run(client);
384
+ // The presence half of `providesKeyChecksum` — the only half this module can
385
+ // check. Without it the claim is what unlocked a production profile up
386
+ // front, and the missing checksum surfaces two calls later as a
387
+ // GUARD_CONFIRM_REQUIRED aimed at the caller for a server-side bug.
388
+ if (executor.providesKeyChecksum && done.keyChecksum === undefined) {
389
+ throw new PgMcpError({
390
+ code: 'PG_ERROR',
391
+ message: `${req.operation} declares providesKeyChecksum but returned no keyChecksum — that claim is what let this ` +
392
+ 'call past the envClass check, so the guard refuses rather than hand out a token it cannot verify',
393
+ hint: 'this is a bug in the tool, not in your call — report it; nothing was committed',
394
+ details: { profile: req.profile.alias, operation: req.operation },
395
+ });
396
+ }
397
+ return { rowCount: done.rowCount, keyChecksum: done.keyChecksum, preview: done.preview ?? [] };
398
+ };
399
+ const txOpts = {
400
+ operation: req.operation,
401
+ ...(req.statementTimeoutMs === undefined ? {} : { statementTimeoutMs: req.statementTimeoutMs }),
402
+ ...(req.lockTimeoutMs === undefined ? {} : { lockTimeoutMs: req.lockTimeoutMs }),
403
+ };
404
+ // Both branches below produce a `GuardedOutcome` and fall through to the
405
+ // single `noteAudit` call at the very end of this function instead of
406
+ // returning directly, so there is exactly one call site regardless of
407
+ // which branch ran.
408
+ let outcome;
409
+ if (req.confirm !== true) {
410
+ const probe = await withTx(req.profile, execute, {
411
+ ...txOpts,
412
+ rollbackAlways: true,
413
+ });
414
+ outcome = {
415
+ kind: 'dryRun',
416
+ rowCount: probe.rowCount,
417
+ preview: probe.preview,
418
+ expect: {
419
+ sqlHash,
420
+ rowCount: probe.rowCount,
421
+ ...(probe.keyChecksum === undefined ? {} : { keyChecksum: probe.keyChecksum }),
422
+ },
423
+ };
424
+ }
425
+ else {
426
+ const expect = req.expect;
427
+ if (policy.requireExpect && expect === undefined) {
428
+ confirmRequired(`profile "${req.profile.alias}" is envClass ${req.profile.envClass}, which does not accept a bare confirm:true`, 'call this tool once without confirm to get a dry run, then re-run with confirm:true and the returned expect ' +
429
+ 'object passed back verbatim', req.profile, { operation: req.operation });
430
+ }
431
+ if (expect !== undefined && expect.sqlHash !== sqlHash) {
432
+ throw new PgMcpError({
433
+ code: 'GUARD_HASH_MISMATCH',
434
+ message: `the expect.sqlHash you passed back does not match the statement this call would run — the arguments changed ` +
435
+ 'between the dry run and the confirm, so confirming would commit something you never previewed',
436
+ hint: 're-run the dry run with the arguments you actually want, then confirm with the expect object it returns',
437
+ details: {
438
+ profile: req.profile.alias,
439
+ operation: req.operation,
440
+ expected: sqlHash,
441
+ received: expect.sqlHash,
442
+ },
443
+ });
444
+ }
445
+ // `Math.abs(NaN - n) > drift` is `false`, so a non-numeric `expect.rowCount`
446
+ // would sail through the drift check below and COMMIT — measured: `NaN`,
447
+ // `undefined` and `{}` all committed, only `'2'` and `null` were refused. No
448
+ // tool can send those today (`tools/_batch_shared.ts#expectTokenSchema` is
449
+ // `z.number().int().min(0)`), but this module's whole claim is that it never
450
+ // fails open, and it is the seam Phase 5's destructive sub-ops reuse. The
451
+ // comparison below is *also* inverted to `!(… <= …)` so a value that somehow
452
+ // reaches it fails closed rather than silently passing.
453
+ if (expect !== undefined && !Number.isInteger(expect.rowCount)) {
454
+ confirmRequired(`expect.rowCount is ${typeof expect.rowCount === 'number' ? String(expect.rowCount) : typeof expect.rowCount}, ` +
455
+ 'not a whole number, so there is nothing to compare the committing statement against', 'pass back the whole expect object the dry run returned, verbatim — do not hand-build it', req.profile, { operation: req.operation });
456
+ }
457
+ if (checksumRequired && expect?.keyChecksum === undefined) {
458
+ confirmRequired(`profile "${req.profile.alias}" is envClass ${req.profile.envClass}, which requires expect.keyChecksum`, 'pass back the whole expect object the dry run returned, including keyChecksum — do not hand-build it', req.profile, { operation: req.operation });
459
+ }
460
+ const allowedDrift = effectiveRowDrift(policy, req.allowRowDrift, req.profile.alias);
461
+ // Everything below runs INSIDE the committing transaction. A throw here makes
462
+ // `withTx` ROLLBACK, so a concurrent change between the two calls aborts the
463
+ // commit rather than silently widening the blast radius.
464
+ const applied = await withTx(req.profile, async (client) => {
465
+ const result = await execute(client);
466
+ if (expect?.keyChecksum !== undefined && result.keyChecksum !== expect.keyChecksum) {
467
+ throw new PgMcpError({
468
+ code: 'GUARD_ROW_DRIFT',
469
+ message: `the set of rows this statement affects changed since the dry run: expected key checksum ` +
470
+ `${expect.keyChecksum}, got ${result.keyChecksum ?? '(none)'} — the row count alone would not have ` +
471
+ 'caught this, so a different set of rows was about to be committed',
472
+ hint: 're-run the dry run to see the current target rows, then confirm with the new expect object',
473
+ details: {
474
+ profile: req.profile.alias,
475
+ operation: req.operation,
476
+ expectedKeyChecksum: expect.keyChecksum,
477
+ actualKeyChecksum: result.keyChecksum ?? null,
478
+ expectedRowCount: expect.rowCount,
479
+ actualRowCount: result.rowCount,
480
+ // Reported even though this check ignores it: an agent that passed
481
+ // allowRowDrift needs to see the value that actually applied, or a
482
+ // refusal on a production profile looks like the tolerance was lost.
483
+ allowRowDrift: allowedDrift,
484
+ },
485
+ });
486
+ }
487
+ // `!(… <= …)`, never `… > …`: the negated form is the one that refuses a
488
+ // NaN instead of passing it. `expect.rowCount` is already validated above;
489
+ // this is the second lock on the same door.
490
+ if (expect !== undefined && !(Math.abs(result.rowCount - expect.rowCount) <= allowedDrift)) {
491
+ throw new PgMcpError({
492
+ code: 'GUARD_ROW_DRIFT',
493
+ message: `this statement affected ${result.rowCount} row(s) but the dry run previewed ${expect.rowCount}` +
494
+ `${allowedDrift > 0 ? ` (allowRowDrift ${allowedDrift})` : ''} — the data changed between the two calls, ` +
495
+ 'so the transaction was rolled back instead of committed',
496
+ hint: 're-run the dry run to see the current row count, then confirm with the new expect object',
497
+ details: {
498
+ profile: req.profile.alias,
499
+ operation: req.operation,
500
+ expectedRowCount: expect.rowCount,
501
+ actualRowCount: result.rowCount,
502
+ allowRowDrift: allowedDrift,
503
+ },
504
+ });
505
+ }
506
+ return result;
507
+ }, txOpts);
508
+ outcome = { kind: 'mutation', rowCount: applied.rowCount, committed: true };
509
+ }
510
+ noteAudit({ sql: text, sqlHash, rowCount: outcome.rowCount });
511
+ return outcome;
512
+ }
513
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/core/guards.ts"],"names":[],"mappings":"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"}