@carllee1983/dbcli 1.58.0 → 2.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.
- package/.cursor/rules/dbcli.mdc +19 -0
- package/.cursor/skills/dbcli/reference.md +146 -2
- package/.github/skills/dbcli/SKILL.md +19 -0
- package/.github/skills/dbcli/reference.md +146 -2
- package/CHANGELOG.md +77 -0
- package/assets/SKILL.md +19 -0
- package/assets/SKILL.zh-TW.md +17 -0
- package/assets/reference.md +146 -2
- package/dist/cli-runtime.mjs +1134 -227
- package/dist/cli.mjs +2 -1
- package/dist/core.mjs +68 -2
- package/package.json +2 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +19 -0
- package/plugins/dbcli-agent/skills/dbcli/reference.md +146 -2
- package/skills/dbcli/SKILL.md +19 -0
- package/skills/dbcli/reference.md +146 -2
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "1.
|
|
54
|
+
version: "2.1.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -134,6 +134,7 @@ var init_package = __esm(() => {
|
|
|
134
134
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
135
135
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
136
136
|
typecheck: "tsc --noEmit --pretty false",
|
|
137
|
+
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
137
138
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
138
139
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
139
140
|
"format:check": "prettier --check .",
|
|
@@ -8903,6 +8904,15 @@ var init_messages = __esm(() => {
|
|
|
8903
8904
|
health: {
|
|
8904
8905
|
description: "Show AuditLogger health snapshot (size, entries, lock state, last write)"
|
|
8905
8906
|
},
|
|
8907
|
+
write_gate: {
|
|
8908
|
+
description: "Summarize tier-two write-gate decisions (ADR 0010's measurement)",
|
|
8909
|
+
header: "Write-gate summary \u2014 connection: {conn}",
|
|
8910
|
+
window: "Audit entries scanned: {scanned}, {from} \u2192 {to}",
|
|
8911
|
+
decisions: "Tier-two evaluations: {count}, {from} \u2192 {to}",
|
|
8912
|
+
tier_one: "Tier one, counted apart: {count} ({outcomes})",
|
|
8913
|
+
no_decisions: "Tier two was never reached in this window. Per ADR 0010 that falsifies the criterion, not the gate: the shapes it looks for are not the shapes people write.",
|
|
8914
|
+
no_entries: "The audit log is empty, so this says nothing about the gate either way. Run some writes, or check `dbcli audit health`."
|
|
8915
|
+
},
|
|
8906
8916
|
disabled_hint: "Audit is disabled (audit.enabled = false in .dbcli). Use 'dbcli audit health' for details.",
|
|
8907
8917
|
no_entries: "No audit entries.",
|
|
8908
8918
|
show_no_match: "No audit entry matches '{prefix}'.",
|
|
@@ -9211,6 +9221,15 @@ var init_messages2 = __esm(() => {
|
|
|
9211
9221
|
health: {
|
|
9212
9222
|
description: "\u986F\u793A AuditLogger \u5065\u5EB7\u72C0\u614B\uFF08\u6A94\u6848\u5927\u5C0F\u3001\u7B46\u6578\u3001lock \u72C0\u614B\u3001\u6700\u5F8C\u5BEB\u5165\u7D50\u679C\uFF09"
|
|
9213
9223
|
},
|
|
9224
|
+
write_gate: {
|
|
9225
|
+
description: "\u7D71\u8A08\u7B2C\u4E8C\u7D1A\u5BEB\u5165\u9598\u9580\u7684\u5224\u5B9A\uFF08ADR 0010 \u62BC\u6CE8\u7684\u90A3\u500B\u91CF\u6E2C\uFF09",
|
|
9226
|
+
header: "\u5BEB\u5165\u9598\u9580\u6458\u8981 \u2014 \u9023\u7DDA\uFF1A{conn}",
|
|
9227
|
+
window: "\u6383\u904E\u7684\u7A3D\u6838\u7D00\u9304\uFF1A{scanned} \u7B46\uFF0C{from} \u2192 {to}",
|
|
9228
|
+
decisions: "\u7B2C\u4E8C\u7D1A\u5224\u5B9A\uFF1A{count} \u6B21\uFF0C{from} \u2192 {to}",
|
|
9229
|
+
tier_one: "\u7B2C\u4E00\u7D1A\uFF0C\u5206\u958B\u8A08\u7B97\uFF1A{count} \u6B21\uFF08{outcomes}\uFF09",
|
|
9230
|
+
no_decisions: "\u9019\u6BB5\u5340\u9593\u5167\u7B2C\u4E8C\u7D1A\u4E00\u6B21\u90FD\u6C92\u88AB\u89F8\u53CA\u3002\u4F9D ADR 0010\uFF0C\u9019\u63A8\u7FFB\u7684\u662F\u5224\u6E96\u800C\u4E0D\u662F\u9598\u9580\uFF1A\u5B83\u5728\u627E\u7684\u5F62\u72C0\u4E0D\u662F\u5927\u5BB6\u5BE6\u969B\u6703\u5BEB\u7684\u5F62\u72C0\u3002",
|
|
9231
|
+
no_entries: "\u7A3D\u6838\u7D00\u9304\u662F\u7A7A\u7684\uFF0C\u6240\u4EE5\u9019\u5C0D\u9598\u9580\u6C92\u6709\u4EFB\u4F55\u7D50\u8AD6\u3002\u5148\u8DD1\u4E00\u4E9B\u5BEB\u5165\uFF0C\u6216\u7528 `dbcli audit health` \u6AA2\u67E5\u3002"
|
|
9232
|
+
},
|
|
9214
9233
|
disabled_hint: "Audit \u5DF2\u505C\u7528\uFF08.dbcli \u4E2D audit.enabled = false\uFF09\u3002\u57F7\u884C `dbcli audit health` \u67E5\u770B\u8A73\u60C5\u3002",
|
|
9215
9234
|
no_entries: "\u6C92\u6709 audit \u7D00\u9304\u3002",
|
|
9216
9235
|
show_no_match: "\u627E\u4E0D\u5230\u7B26\u5408 '{prefix}' \u7684 audit \u7D00\u9304\u3002",
|
|
@@ -9422,7 +9441,8 @@ var init_shell = __esm(() => {
|
|
|
9422
9441
|
multiline_cancelled: "Multiline input cancelled.",
|
|
9423
9442
|
timing_display: "Time: {ms}ms",
|
|
9424
9443
|
rows_display: "{count} row(s)",
|
|
9425
|
-
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands."
|
|
9444
|
+
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
|
|
9445
|
+
subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels."
|
|
9426
9446
|
};
|
|
9427
9447
|
});
|
|
9428
9448
|
|
|
@@ -9448,7 +9468,8 @@ var init_shell2 = __esm(() => {
|
|
|
9448
9468
|
multiline_cancelled: "\u5DF2\u53D6\u6D88\u591A\u884C\u8F38\u5165\u3002",
|
|
9449
9469
|
timing_display: "\u8017\u6642\uFF1A{ms}ms",
|
|
9450
9470
|
rows_display: "{count} \u7B46\u8CC7\u6599",
|
|
9451
|
-
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002"
|
|
9471
|
+
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
|
|
9472
|
+
subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002"
|
|
9452
9473
|
};
|
|
9453
9474
|
});
|
|
9454
9475
|
|
|
@@ -9473,6 +9494,29 @@ var init_ceremony = __esm(() => {
|
|
|
9473
9494
|
confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
|
|
9474
9495
|
confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
|
|
9475
9496
|
confirm_prompt: "Proceed with this operation?",
|
|
9497
|
+
gate_summary: "This will {operation} rows in {table}.",
|
|
9498
|
+
gate_summary_untargeted: "This will run a {operation} statement.",
|
|
9499
|
+
gate_statement: "Statement:",
|
|
9500
|
+
gate_cancelled: "Cancelled. Nothing was run.",
|
|
9501
|
+
gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
|
|
9502
|
+
gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
|
|
9503
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
|
|
9504
|
+
gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
|
|
9505
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
|
|
9506
|
+
gate_multi_table_warning: "This write joins another table. How many rows of the target it touches depends on the data, not on the statement, so it cannot be checked before it runs.",
|
|
9507
|
+
gate_nested_write_warning: "\u26A0\uFE0F This statement carries a second write inside it, and dbcli will not read the tier of that write off this one.",
|
|
9508
|
+
gate_typed_prompt: "Type {phrase} to run it",
|
|
9509
|
+
gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
|
|
9510
|
+
gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
|
|
9511
|
+
gate_refused_in_shell: "Refused: a dbcli subcommand started from the shell has no way to ask you, even though you are right here (reason={reason}).",
|
|
9512
|
+
gate_remedy_no_where: "Add a WHERE clause \u2014 WHERE 1=1 if you really do mean every row \u2014 or a LIMIT, then run it again.",
|
|
9513
|
+
gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
|
|
9514
|
+
gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
|
|
9515
|
+
gate_remedy_multiple_statements: "Run one statement per command.",
|
|
9516
|
+
gate_remedy_multi_table: "Rewrite it as a single-table statement \u2014 move the other table into a subquery in the WHERE \u2014 or run it where someone can confirm it.",
|
|
9517
|
+
gate_remedy_nested_write: "Run the nested write as its own statement, or run this one where someone can confirm it.",
|
|
9518
|
+
gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
|
|
9519
|
+
gate_remedy_in_shell: "Type the statement at the dbcli> prompt instead \u2014 there the shell asks you to type the target table name.",
|
|
9476
9520
|
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9477
9521
|
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9478
9522
|
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
@@ -9500,6 +9544,29 @@ var init_ceremony2 = __esm(() => {
|
|
|
9500
9544
|
confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
|
|
9501
9545
|
confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9502
9546
|
confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
|
|
9547
|
+
gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
|
|
9548
|
+
gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
|
|
9549
|
+
gate_statement: "\u8A9E\u53E5\uFF1A",
|
|
9550
|
+
gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9551
|
+
gate_full_table_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u5F71\u97FF {table} \u7684\u6BCF\u4E00\u5217\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9552
|
+
gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9553
|
+
gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9554
|
+
gate_unparseable_warning: "\u26A0\uFE0F dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u56E0\u6B64\u4E00\u5F8B\u8996\u70BA\u6703\u5F71\u97FF\u5168\u90E8\u8CC7\u6599\u3002",
|
|
9555
|
+
gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
|
|
9556
|
+
gate_multi_table_warning: "\u9019\u500B\u5BEB\u5165\u63A5\u4E86\u53E6\u4E00\u5F35\u8868\u3002\u5B83\u6703\u52D5\u5230\u76EE\u6A19\u8868\u5E7E\u5217\u53D6\u6C7A\u65BC\u8CC7\u6599\u800C\u4E0D\u662F\u8A9E\u53E5\u672C\u8EAB\uFF0C\u57F7\u884C\u524D\u7121\u6CD5\u6AA2\u67E5\u3002",
|
|
9557
|
+
gate_nested_write_warning: "\u26A0\uFE0F \u9019\u53E5\u88E1\u9762\u9084\u593E\u8457\u53E6\u4E00\u500B\u5BEB\u5165\uFF0Cdbcli \u4E0D\u6703\u62FF\u9019\u53E5\u7684\u5C64\u7D1A\u4EE3\u66FF\u90A3\u500B\u5BEB\u5165\u7684\u5C64\u7D1A\u3002",
|
|
9558
|
+
gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
|
|
9559
|
+
gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9560
|
+
gate_refused: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u53E5\u6C92\u6709\u9650\u5B9A\u8981\u52D5\u54EA\u4E9B\u5217\uFF0C\u800C\u73FE\u5728\u6C92\u6709\u4EBA\u80FD\u78BA\u8A8D\uFF08reason={reason}\uFF09\u3002",
|
|
9561
|
+
gate_refused_in_shell: "\u5DF2\u62D2\u7D55\uFF1A\u5F9E shell \u555F\u52D5\u7684 dbcli \u5B50\u6307\u4EE4\u6C92\u6709\u8FA6\u6CD5\u5411\u4F60\u63D0\u554F\uFF0C\u5373\u4F7F\u4F60\u4EBA\u5C31\u5728\u9019\u88E1\uFF08reason={reason}\uFF09\u3002",
|
|
9562
|
+
gate_remedy_no_where: "\u88DC\u4E0A WHERE\uFF08\u771F\u7684\u8981\u5168\u8868\u5C31\u5BEB WHERE 1=1\uFF09\u6216 LIMIT\uFF0C\u7136\u5F8C\u91CD\u8DD1\u3002",
|
|
9563
|
+
gate_remedy_ddl_destruction: "\u6C92\u6709\u4EFB\u4F55\u65D7\u6A19\u53EF\u4EE5\u8DF3\u904E\u9019\u4E00\u95DC\u3002\u8ACB\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\uFF0C\u6216\u6539\u8D70\u7D93\u904E\u5BE9\u67E5\u7684 migration\u3002",
|
|
9564
|
+
gate_remedy_unparseable: "dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u4E5F\u4E0D\u6703\u7528\u731C\u7684\u3002\u8ACB\u7C21\u5316\u8A9E\u53E5\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
|
|
9565
|
+
gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
|
|
9566
|
+
gate_remedy_multi_table: "\u6539\u5BEB\u6210\u55AE\u8868\u8A9E\u53E5\u2014\u2014\u628A\u53E6\u4E00\u5F35\u8868\u79FB\u9032 WHERE \u7684\u5B50\u67E5\u8A62\u88E1\u2014\u2014\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
|
|
9567
|
+
gate_remedy_nested_write: "\u628A\u88E1\u9762\u90A3\u500B\u5BEB\u5165\u62C6\u6210\u7368\u7ACB\u7684\u4E00\u53E5\u4F86\u57F7\u884C\uFF0C\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
|
|
9568
|
+
gate_remedy_non_unique_where: "\u6539\u7528\u4E3B\u9375\u6216\u552F\u4E00\u7D22\u5F15\u6B04\u4F4D\u7576\u689D\u4EF6\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
|
|
9569
|
+
gate_remedy_in_shell: "\u8ACB\u76F4\u63A5\u5728 dbcli> \u63D0\u793A\u7B26\u6253\u9019\u53E5\u8A9E\u53E5\u2014\u2014\u5728\u90A3\u88E1 dbcli \u6703\u8ACB\u4F60\u6253\u51FA\u76EE\u6A19\u8CC7\u6599\u8868\u7684\u540D\u7A31\u4F86\u78BA\u8A8D\u3002",
|
|
9503
9570
|
recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
|
|
9504
9571
|
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9505
9572
|
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
@@ -9730,7 +9797,8 @@ async function readLineFromStdin(prompt = "") {
|
|
|
9730
9797
|
}
|
|
9731
9798
|
async function textFallback(message, defaultValue) {
|
|
9732
9799
|
const displayMessage = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
9733
|
-
|
|
9800
|
+
process.stderr.write(displayMessage);
|
|
9801
|
+
const answer = await readLineFromStdin();
|
|
9734
9802
|
return answer.trim() || defaultValue || "";
|
|
9735
9803
|
}
|
|
9736
9804
|
async function selectFallback(message, choices) {
|
|
@@ -9756,7 +9824,7 @@ async function text(message, defaultValue) {
|
|
|
9756
9824
|
const inquirer = await loadInquirer();
|
|
9757
9825
|
if (!inquirer)
|
|
9758
9826
|
return textFallback(message, defaultValue);
|
|
9759
|
-
return await inquirer.input({ message, default: defaultValue });
|
|
9827
|
+
return await inquirer.input({ message, default: defaultValue }, { output: process.stderr });
|
|
9760
9828
|
}
|
|
9761
9829
|
async function select(message, choices) {
|
|
9762
9830
|
if (!process.stdin.isTTY)
|
|
@@ -17972,7 +18040,7 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
17972
18040
|
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
17973
18041
|
const engine = config.connection?.system || "postgresql";
|
|
17974
18042
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
17975
|
-
let tier = getEngineCapability(engine, commandName).tier;
|
|
18043
|
+
let tier = outcome.sideEffectTier ?? getEngineCapability(engine, commandName).tier;
|
|
17976
18044
|
if (options.dryRun || options.plan) {
|
|
17977
18045
|
tier = "dry-run";
|
|
17978
18046
|
}
|
|
@@ -18905,6 +18973,7 @@ __export(exports_permission_guard, {
|
|
|
18905
18973
|
checkPermissionForClassification: () => checkPermissionForClassification,
|
|
18906
18974
|
checkPermission: () => checkPermission,
|
|
18907
18975
|
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18976
|
+
SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
|
|
18908
18977
|
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18909
18978
|
PermissionError: () => PermissionError
|
|
18910
18979
|
});
|
|
@@ -22202,6 +22271,504 @@ var init_elasticsearch = __esm(() => {
|
|
|
22202
22271
|
ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
|
|
22203
22272
|
});
|
|
22204
22273
|
|
|
22274
|
+
// src/commands/mutation-outcome.ts
|
|
22275
|
+
var exports_mutation_outcome = {};
|
|
22276
|
+
__export(exports_mutation_outcome, {
|
|
22277
|
+
shouldRenderForHuman: () => shouldRenderForHuman,
|
|
22278
|
+
renderMutationOutcome: () => renderMutationOutcome,
|
|
22279
|
+
printMutationOutcome: () => printMutationOutcome,
|
|
22280
|
+
printMutationFailure: () => printMutationFailure,
|
|
22281
|
+
humanOutputContext: () => humanOutputContext,
|
|
22282
|
+
cancelledOutcome: () => cancelledOutcome
|
|
22283
|
+
});
|
|
22284
|
+
function shouldRenderForHuman(context) {
|
|
22285
|
+
if (context.format === "json")
|
|
22286
|
+
return false;
|
|
22287
|
+
return context.isTTY;
|
|
22288
|
+
}
|
|
22289
|
+
function humanOutputContext(options) {
|
|
22290
|
+
return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
|
|
22291
|
+
}
|
|
22292
|
+
function renderMutationOutcome(result, table, elapsedMs) {
|
|
22293
|
+
if (result.status === "error") {
|
|
22294
|
+
return [
|
|
22295
|
+
t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
|
|
22296
|
+
t("ceremony.recovery_retry")
|
|
22297
|
+
];
|
|
22298
|
+
}
|
|
22299
|
+
if (result.status === "cancelled") {
|
|
22300
|
+
return [t_vars("ceremony.cancelled", { table })];
|
|
22301
|
+
}
|
|
22302
|
+
if (result.status === "dry_run") {
|
|
22303
|
+
return [t_vars("ceremony.dry_run", { table })];
|
|
22304
|
+
}
|
|
22305
|
+
const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
|
|
22306
|
+
if (result.rows_affected === 0) {
|
|
22307
|
+
return [t_vars("ceremony.matched_nothing", { table }), elapsed];
|
|
22308
|
+
}
|
|
22309
|
+
return [
|
|
22310
|
+
t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
|
|
22311
|
+
elapsed,
|
|
22312
|
+
t("ceremony.recovery_none")
|
|
22313
|
+
];
|
|
22314
|
+
}
|
|
22315
|
+
function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
|
|
22316
|
+
if (shouldRenderForHuman(context)) {
|
|
22317
|
+
const writeLine = result.status === "error" ? console.error : console.log;
|
|
22318
|
+
for (const line of renderMutationOutcome(result, table, elapsedMs))
|
|
22319
|
+
writeLine(line);
|
|
22320
|
+
return;
|
|
22321
|
+
}
|
|
22322
|
+
console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
|
|
22323
|
+
}
|
|
22324
|
+
function printMutationFailure(error, operation, context) {
|
|
22325
|
+
if (shouldRenderForHuman(context)) {
|
|
22326
|
+
console.error(t_vars("ceremony.failed", { reason: error.message }));
|
|
22327
|
+
if (error instanceof BlacklistError) {
|
|
22328
|
+
console.error(t("ceremony.blacklist_hint"));
|
|
22329
|
+
}
|
|
22330
|
+
return;
|
|
22331
|
+
}
|
|
22332
|
+
console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
|
|
22333
|
+
}
|
|
22334
|
+
function cancelledOutcome(operation, preview) {
|
|
22335
|
+
return {
|
|
22336
|
+
status: "cancelled",
|
|
22337
|
+
operation,
|
|
22338
|
+
rows_affected: 0,
|
|
22339
|
+
sql: preview,
|
|
22340
|
+
timestamp: new Date().toISOString()
|
|
22341
|
+
};
|
|
22342
|
+
}
|
|
22343
|
+
function mutationEnvelope(result) {
|
|
22344
|
+
return {
|
|
22345
|
+
status: result.status,
|
|
22346
|
+
operation: result.operation,
|
|
22347
|
+
rows_affected: result.rows_affected,
|
|
22348
|
+
...result.timestamp && { timestamp: result.timestamp },
|
|
22349
|
+
...result.sql && { sql: result.sql },
|
|
22350
|
+
...result.error && { error: result.error }
|
|
22351
|
+
};
|
|
22352
|
+
}
|
|
22353
|
+
var VERB_KEY;
|
|
22354
|
+
var init_mutation_outcome = __esm(() => {
|
|
22355
|
+
init_message_loader();
|
|
22356
|
+
init_blacklist();
|
|
22357
|
+
VERB_KEY = {
|
|
22358
|
+
insert: "ceremony.inserted",
|
|
22359
|
+
update: "ceremony.updated",
|
|
22360
|
+
delete: "ceremony.deleted"
|
|
22361
|
+
};
|
|
22362
|
+
});
|
|
22363
|
+
|
|
22364
|
+
// src/commands/write-gate-prompt.ts
|
|
22365
|
+
var exports_write_gate_prompt = {};
|
|
22366
|
+
__export(exports_write_gate_prompt, {
|
|
22367
|
+
enforceWriteGate: () => enforceWriteGate,
|
|
22368
|
+
WriteGateRefusal: () => WriteGateRefusal
|
|
22369
|
+
});
|
|
22370
|
+
async function enforceWriteGate(request) {
|
|
22371
|
+
const { verdict, human } = request;
|
|
22372
|
+
if (verdict.tier === "none")
|
|
22373
|
+
return true;
|
|
22374
|
+
const attended = isAttended(human);
|
|
22375
|
+
if (verdict.tier === "two")
|
|
22376
|
+
return await enforceTierTwo(request, attended);
|
|
22377
|
+
if (request.yes === true)
|
|
22378
|
+
return true;
|
|
22379
|
+
if (!attended)
|
|
22380
|
+
return true;
|
|
22381
|
+
writeSummary(request);
|
|
22382
|
+
const proceed = await promptUser.confirm(t("ceremony.confirm_prompt"));
|
|
22383
|
+
if (!proceed)
|
|
22384
|
+
process.stderr.write(`${t("ceremony.gate_cancelled")}
|
|
22385
|
+
`);
|
|
22386
|
+
return proceed;
|
|
22387
|
+
}
|
|
22388
|
+
function isAttended(human) {
|
|
22389
|
+
return shouldRenderForHuman(human) && process.stdin.isTTY === true;
|
|
22390
|
+
}
|
|
22391
|
+
async function enforceTierTwo(request, attended) {
|
|
22392
|
+
const { verdict } = request;
|
|
22393
|
+
const reason = verdict.reason ?? "no_where";
|
|
22394
|
+
if (!attended) {
|
|
22395
|
+
const inShell = process.env.DBCLI_SHELL_SUBCOMMAND === "1";
|
|
22396
|
+
throw new WriteGateRefusal(inShell ? `${t_vars("ceremony.gate_refused_in_shell", { reason })} ${t("ceremony.gate_remedy_in_shell")}` : `${t_vars("ceremony.gate_refused", { reason })} ${t(REMEDY_KEY[reason])}`, reason, verdict.table);
|
|
22397
|
+
}
|
|
22398
|
+
writeSummary(request);
|
|
22399
|
+
const warning = verdict.table === undefined && reason === "ddl_destruction" ? t("ceremony.gate_ddl_warning_untargeted") : t_vars(WARNING_KEY[reason], { table: verdict.table ?? "" });
|
|
22400
|
+
process.stderr.write(`
|
|
22401
|
+
${warning}
|
|
22402
|
+
${t(REMEDY_KEY[reason])}
|
|
22403
|
+
`);
|
|
22404
|
+
const ask = request.ask ?? promptUser.text;
|
|
22405
|
+
const typed = await ask(t_vars("ceremony.gate_typed_prompt", { phrase: verdict.confirmationPhrase }));
|
|
22406
|
+
if (typed === null) {
|
|
22407
|
+
process.stderr.write(`${t("ceremony.gate_cancelled")}
|
|
22408
|
+
`);
|
|
22409
|
+
return false;
|
|
22410
|
+
}
|
|
22411
|
+
const matched = typed.trim().toLowerCase() === verdict.confirmationPhrase.toLowerCase();
|
|
22412
|
+
if (!matched) {
|
|
22413
|
+
process.stderr.write(`${t_vars("ceremony.gate_typed_mismatch", { phrase: verdict.confirmationPhrase })}
|
|
22414
|
+
`);
|
|
22415
|
+
}
|
|
22416
|
+
return matched;
|
|
22417
|
+
}
|
|
22418
|
+
function writeSummary(request) {
|
|
22419
|
+
const { verdict, operation, statement } = request;
|
|
22420
|
+
const summary = verdict.table ? t_vars("ceremony.gate_summary", { operation, table: verdict.table }) : t_vars("ceremony.gate_summary_untargeted", { operation });
|
|
22421
|
+
process.stderr.write(`
|
|
22422
|
+
${summary}
|
|
22423
|
+
${t("ceremony.gate_statement")}
|
|
22424
|
+
${statement}
|
|
22425
|
+
`);
|
|
22426
|
+
}
|
|
22427
|
+
var WriteGateRefusal, REMEDY_KEY, WARNING_KEY;
|
|
22428
|
+
var init_write_gate_prompt = __esm(() => {
|
|
22429
|
+
init_message_loader();
|
|
22430
|
+
init_prompts();
|
|
22431
|
+
init_mutation_outcome();
|
|
22432
|
+
WriteGateRefusal = class WriteGateRefusal extends Error {
|
|
22433
|
+
code = "WRITE_GATE_REFUSED";
|
|
22434
|
+
reason;
|
|
22435
|
+
table;
|
|
22436
|
+
constructor(message, reason, table) {
|
|
22437
|
+
super(message);
|
|
22438
|
+
this.name = "WriteGateRefusal";
|
|
22439
|
+
this.reason = reason;
|
|
22440
|
+
this.table = table;
|
|
22441
|
+
}
|
|
22442
|
+
};
|
|
22443
|
+
REMEDY_KEY = {
|
|
22444
|
+
no_where: "ceremony.gate_remedy_no_where",
|
|
22445
|
+
ddl_destruction: "ceremony.gate_remedy_ddl_destruction",
|
|
22446
|
+
unparseable: "ceremony.gate_remedy_unparseable",
|
|
22447
|
+
non_unique_where: "ceremony.gate_remedy_non_unique_where",
|
|
22448
|
+
multiple_statements: "ceremony.gate_remedy_multiple_statements",
|
|
22449
|
+
multi_table: "ceremony.gate_remedy_multi_table",
|
|
22450
|
+
nested_write: "ceremony.gate_remedy_nested_write"
|
|
22451
|
+
};
|
|
22452
|
+
WARNING_KEY = {
|
|
22453
|
+
no_where: "ceremony.gate_full_table_warning",
|
|
22454
|
+
ddl_destruction: "ceremony.gate_ddl_warning",
|
|
22455
|
+
unparseable: "ceremony.gate_unparseable_warning",
|
|
22456
|
+
non_unique_where: "ceremony.gate_full_table_warning",
|
|
22457
|
+
multiple_statements: "ceremony.gate_multiple_statements_warning",
|
|
22458
|
+
multi_table: "ceremony.gate_multi_table_warning",
|
|
22459
|
+
nested_write: "ceremony.gate_nested_write_warning"
|
|
22460
|
+
};
|
|
22461
|
+
});
|
|
22462
|
+
|
|
22463
|
+
// src/core/sql-parser.ts
|
|
22464
|
+
var exports_sql_parser = {};
|
|
22465
|
+
__export(exports_sql_parser, {
|
|
22466
|
+
sqlParser: () => sqlParser
|
|
22467
|
+
});
|
|
22468
|
+
function sqlParser() {
|
|
22469
|
+
if (!cached2) {
|
|
22470
|
+
const { Parser } = __require("node-sql-parser");
|
|
22471
|
+
cached2 = new Parser;
|
|
22472
|
+
}
|
|
22473
|
+
return cached2;
|
|
22474
|
+
}
|
|
22475
|
+
var cached2 = null;
|
|
22476
|
+
|
|
22477
|
+
// src/commands/write-gate.ts
|
|
22478
|
+
var exports_write_gate = {};
|
|
22479
|
+
__export(exports_write_gate, {
|
|
22480
|
+
classifyStructuredWriteGate: () => classifyStructuredWriteGate,
|
|
22481
|
+
classifySqlWriteGate: () => classifySqlWriteGate,
|
|
22482
|
+
WRITE_GATE_FALLBACK_PHRASE: () => WRITE_GATE_FALLBACK_PHRASE
|
|
22483
|
+
});
|
|
22484
|
+
async function classifySqlWriteGate(sql, options = {}) {
|
|
22485
|
+
const { dialect } = options;
|
|
22486
|
+
const classification = classifyStatement(sql);
|
|
22487
|
+
const hiddenWrite = findWriteKeyword(sql, dialect ? [dialect] : undefined);
|
|
22488
|
+
const type = WRITE_TYPES.has(classification.type) ? classification.type : hiddenWrite ? keywordToType(hiddenWrite) : undefined;
|
|
22489
|
+
if (type === undefined)
|
|
22490
|
+
return { tier: "none", confirmationPhrase: WRITE_GATE_FALLBACK_PHRASE };
|
|
22491
|
+
if (containsMultipleStatements(sql, dialect)) {
|
|
22492
|
+
return verdict("two", "multiple_statements", undefined, type);
|
|
22493
|
+
}
|
|
22494
|
+
const nested = nestedWrite(sql, dialect);
|
|
22495
|
+
if (nested)
|
|
22496
|
+
return verdict("two", "nested_write", undefined, nested);
|
|
22497
|
+
const merge = classifyMerge(sql, dialect);
|
|
22498
|
+
if (merge)
|
|
22499
|
+
return merge;
|
|
22500
|
+
if (type === "DROP" || type === "TRUNCATE") {
|
|
22501
|
+
const matched = DDL_TARGET.exec(sql)?.[1];
|
|
22502
|
+
const table = matched ? matched.replace(/[`"[\]]/g, "") : undefined;
|
|
22503
|
+
return verdict("two", "ddl_destruction", table, type);
|
|
22504
|
+
}
|
|
22505
|
+
if (type !== "UPDATE" && type !== "DELETE")
|
|
22506
|
+
return verdict("one", undefined, undefined, type);
|
|
22507
|
+
return await classifyQualification(sql, dialect, type);
|
|
22508
|
+
}
|
|
22509
|
+
async function classifyQualification(sql, dialect, type) {
|
|
22510
|
+
const { sqlParser: sqlParser2 } = await Promise.resolve().then(() => exports_sql_parser);
|
|
22511
|
+
let ast;
|
|
22512
|
+
try {
|
|
22513
|
+
ast = sqlParser2().astify(sql, { database: PARSER_DIALECTS[dialect ?? "mysql"] });
|
|
22514
|
+
} catch {
|
|
22515
|
+
return unparseable(sql, dialect, type);
|
|
22516
|
+
}
|
|
22517
|
+
const statement = Array.isArray(ast) ? ast[0] : ast;
|
|
22518
|
+
if (!statement || statement.type !== "update" && statement.type !== "delete") {
|
|
22519
|
+
return unparseable(sql, dialect, type);
|
|
22520
|
+
}
|
|
22521
|
+
const scope = [...statement.table ?? [], ...statement.from ?? []];
|
|
22522
|
+
const sources = new Set(scope.map((ref, index) => refIdentity(ref) ?? `#unnamed-${index}`));
|
|
22523
|
+
if (sources.size > 1) {
|
|
22524
|
+
return verdict("two", "multi_table", undefined, type);
|
|
22525
|
+
}
|
|
22526
|
+
const target = normaliseRef(statement.table?.[0] ?? scope[0]);
|
|
22527
|
+
const table = tableName(target);
|
|
22528
|
+
if (statement.limit != null)
|
|
22529
|
+
return verdict("one", undefined, table, type);
|
|
22530
|
+
if (statement.where != null && narrowsTarget(statement.where, refNames(target))) {
|
|
22531
|
+
return verdict("one", undefined, table, type);
|
|
22532
|
+
}
|
|
22533
|
+
return verdict("two", "no_where", table, type);
|
|
22534
|
+
}
|
|
22535
|
+
function normaliseRef(ref) {
|
|
22536
|
+
if (ref?.table?.toUpperCase() !== "ONLY")
|
|
22537
|
+
return ref;
|
|
22538
|
+
if (typeof ref.as !== "string" || ref.as.length === 0)
|
|
22539
|
+
return ref;
|
|
22540
|
+
return {
|
|
22541
|
+
db: ref.db,
|
|
22542
|
+
on: ref.on,
|
|
22543
|
+
as: null,
|
|
22544
|
+
table: typeof ref.as === "string" ? ref.as : undefined
|
|
22545
|
+
};
|
|
22546
|
+
}
|
|
22547
|
+
function refIdentity(reference) {
|
|
22548
|
+
const ref = normaliseRef(reference);
|
|
22549
|
+
if (typeof ref?.table !== "string")
|
|
22550
|
+
return;
|
|
22551
|
+
return (ref.db ? `${ref.db}.${ref.table}` : ref.table).toLowerCase();
|
|
22552
|
+
}
|
|
22553
|
+
function refNames(reference) {
|
|
22554
|
+
const ref = normaliseRef(reference);
|
|
22555
|
+
const name = typeof ref?.as === "string" && ref.as.length > 0 ? ref.as : ref?.table;
|
|
22556
|
+
return typeof name === "string" ? [name.toLowerCase()] : [];
|
|
22557
|
+
}
|
|
22558
|
+
function narrowsTarget(where, targetNames) {
|
|
22559
|
+
const names = new Set(targetNames);
|
|
22560
|
+
const { owners, correlated, skippedSubquery } = scanCondition(where);
|
|
22561
|
+
if (owners.length === 0 && correlated.length === 0)
|
|
22562
|
+
return !skippedSubquery;
|
|
22563
|
+
return owners.some((owner) => owner === null || names.has(owner)) || correlated.some((owner) => names.has(owner));
|
|
22564
|
+
}
|
|
22565
|
+
function mergeScans(scans) {
|
|
22566
|
+
return {
|
|
22567
|
+
owners: scans.flatMap((scan) => scan.owners),
|
|
22568
|
+
correlated: scans.flatMap((scan) => scan.correlated),
|
|
22569
|
+
skippedSubquery: scans.some((scan) => scan.skippedSubquery)
|
|
22570
|
+
};
|
|
22571
|
+
}
|
|
22572
|
+
function scanCondition(node, bound) {
|
|
22573
|
+
if (node === null || typeof node !== "object")
|
|
22574
|
+
return EMPTY_SCAN;
|
|
22575
|
+
const record = node;
|
|
22576
|
+
const inner = record.ast ?? (record.type === "select" ? record : undefined);
|
|
22577
|
+
const nested = inner !== undefined;
|
|
22578
|
+
if (record.type === "column_ref") {
|
|
22579
|
+
const owner = record.table;
|
|
22580
|
+
const name = owner == null ? null : String(owner).toLowerCase();
|
|
22581
|
+
if (bound === undefined)
|
|
22582
|
+
return { owners: [name], correlated: [], skippedSubquery: false };
|
|
22583
|
+
if (name === null || bound.has(name))
|
|
22584
|
+
return EMPTY_SCAN;
|
|
22585
|
+
return { owners: [], correlated: [name], skippedSubquery: false };
|
|
22586
|
+
}
|
|
22587
|
+
const scope = nested ? new Set([...bound ?? [], ...boundNames(inner)]) : bound;
|
|
22588
|
+
const scans = Object.values(record).map((value) => Array.isArray(value) ? mergeScans(value.map((item) => scanCondition(item, scope))) : scanCondition(value, scope));
|
|
22589
|
+
const merged = mergeScans(scans);
|
|
22590
|
+
return nested ? { ...merged, skippedSubquery: true } : merged;
|
|
22591
|
+
}
|
|
22592
|
+
function boundNames(select2) {
|
|
22593
|
+
const from = select2?.from ?? [];
|
|
22594
|
+
return Array.isArray(from) ? from.flatMap((ref) => refNames(ref)) : [];
|
|
22595
|
+
}
|
|
22596
|
+
function stripParenthesised(text2) {
|
|
22597
|
+
let previous = text2;
|
|
22598
|
+
for (;; ) {
|
|
22599
|
+
const next = previous.replace(/\([^()]*\)/g, " ");
|
|
22600
|
+
if (next === previous)
|
|
22601
|
+
return next;
|
|
22602
|
+
previous = next;
|
|
22603
|
+
}
|
|
22604
|
+
}
|
|
22605
|
+
function unparseable(sql, dialect, type) {
|
|
22606
|
+
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {});
|
|
22607
|
+
const statementLevel = stripParenthesised(executable);
|
|
22608
|
+
const singleTable = SINGLE_TARGET.test(executable) && !SECOND_SOURCE_ANYWHERE.test(executable) && !SECOND_SOURCE_AT_STATEMENT_LEVEL.some((pattern) => pattern.test(statementLevel));
|
|
22609
|
+
if (!singleTable)
|
|
22610
|
+
return verdict("two", "unparseable", undefined, type);
|
|
22611
|
+
if (/\bWHERE\b|\bLIMIT\b/i.test(executable))
|
|
22612
|
+
return verdict("one", undefined, undefined, type);
|
|
22613
|
+
return verdict("two", "unparseable", undefined, type);
|
|
22614
|
+
}
|
|
22615
|
+
function classifyMerge(sql, dialect) {
|
|
22616
|
+
const executable = stripParenthesised(stripCommentsAndStrings(sql, dialect ? { dialect } : {}));
|
|
22617
|
+
if (!MERGE_STATEMENT.test(executable))
|
|
22618
|
+
return;
|
|
22619
|
+
const matched = MERGE_TARGET.exec(executable)?.[1];
|
|
22620
|
+
const table = matched === undefined || matched.endsWith(".") || /^USING$/i.test(matched) ? undefined : matched;
|
|
22621
|
+
const destructive = /\bTHEN\s+DELETE\b/i.test(executable) ? "DELETE" : /\bTHEN\s+UPDATE\b/i.test(executable) ? "UPDATE" : undefined;
|
|
22622
|
+
if (!destructive)
|
|
22623
|
+
return verdict("one", undefined, table, "INSERT");
|
|
22624
|
+
return verdict("two", "multi_table", table, destructive);
|
|
22625
|
+
}
|
|
22626
|
+
function nestedWrite(sql, dialect) {
|
|
22627
|
+
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {}).replace(SQL_LOCK_CLAUSE, " ").replace(REFERENTIAL_ACTION, " ");
|
|
22628
|
+
const match = /\b(INSERT|UPDATE|DELETE)\b|\bMERGE\s+INTO\b/i.exec(insideParentheses(executable));
|
|
22629
|
+
if (match === null)
|
|
22630
|
+
return;
|
|
22631
|
+
return keywordToType(match[1] ?? "MERGE");
|
|
22632
|
+
}
|
|
22633
|
+
function insideParentheses(text2) {
|
|
22634
|
+
let depth = 0;
|
|
22635
|
+
let result = "";
|
|
22636
|
+
for (const char of text2) {
|
|
22637
|
+
if (char === "(") {
|
|
22638
|
+
depth += 1;
|
|
22639
|
+
result += " ";
|
|
22640
|
+
continue;
|
|
22641
|
+
}
|
|
22642
|
+
if (char === ")") {
|
|
22643
|
+
depth = Math.max(0, depth - 1);
|
|
22644
|
+
result += " ";
|
|
22645
|
+
continue;
|
|
22646
|
+
}
|
|
22647
|
+
result += depth > 0 ? char : " ";
|
|
22648
|
+
}
|
|
22649
|
+
return result;
|
|
22650
|
+
}
|
|
22651
|
+
function classifyStructuredWriteGate(request) {
|
|
22652
|
+
const conditions = new Set(Object.keys(request.where).map((key) => key.toLowerCase()));
|
|
22653
|
+
const identifies = uniqueSelectors(request.schema).some((selector) => selector.length > 0 && selector.every((column) => conditions.has(column.toLowerCase())));
|
|
22654
|
+
if (identifies)
|
|
22655
|
+
return verdict("one", undefined, request.table);
|
|
22656
|
+
return verdict("two", "non_unique_where", request.table);
|
|
22657
|
+
}
|
|
22658
|
+
function uniqueSelectors(schema) {
|
|
22659
|
+
const declared = schema.primaryKey;
|
|
22660
|
+
const primaryKey = Array.isArray(declared) ? declared : typeof declared === "string" ? [declared] : (schema.columns ?? []).filter((column) => column.primaryKey).map((column) => column.name);
|
|
22661
|
+
const unique = (schema.indexes ?? []).filter((index) => index.unique).map((index) => index.columns);
|
|
22662
|
+
return [primaryKey, ...unique];
|
|
22663
|
+
}
|
|
22664
|
+
function tableName(ref) {
|
|
22665
|
+
if (!ref?.table)
|
|
22666
|
+
return;
|
|
22667
|
+
return ref.db ? `${ref.db}.${ref.table}` : ref.table;
|
|
22668
|
+
}
|
|
22669
|
+
function keywordToType(keyword) {
|
|
22670
|
+
const upper = keyword.toUpperCase();
|
|
22671
|
+
if (WRITE_TYPES.has(upper))
|
|
22672
|
+
return upper;
|
|
22673
|
+
return "INSERT";
|
|
22674
|
+
}
|
|
22675
|
+
function verdict(tier, reason, table, operation) {
|
|
22676
|
+
return {
|
|
22677
|
+
tier,
|
|
22678
|
+
...reason && { reason },
|
|
22679
|
+
...table && { table },
|
|
22680
|
+
...operation && { operation },
|
|
22681
|
+
confirmationPhrase: table ?? WRITE_GATE_FALLBACK_PHRASE
|
|
22682
|
+
};
|
|
22683
|
+
}
|
|
22684
|
+
var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET, EMPTY_SCAN, SINGLE_TARGET, SECOND_SOURCE_ANYWHERE, SECOND_SOURCE_AT_STATEMENT_LEVEL, MERGE_STATEMENT, MERGE_TARGET, REFERENTIAL_ACTION;
|
|
22685
|
+
var init_write_gate = __esm(() => {
|
|
22686
|
+
init_permission_guard();
|
|
22687
|
+
init_sql_analysis();
|
|
22688
|
+
PARSER_DIALECTS = {
|
|
22689
|
+
mysql: "MySQL",
|
|
22690
|
+
mariadb: "MariaDB",
|
|
22691
|
+
postgresql: "Postgresql"
|
|
22692
|
+
};
|
|
22693
|
+
WRITE_TYPES = new Set([
|
|
22694
|
+
"INSERT",
|
|
22695
|
+
"UPDATE",
|
|
22696
|
+
"DELETE",
|
|
22697
|
+
"TRUNCATE",
|
|
22698
|
+
"DROP",
|
|
22699
|
+
"ALTER",
|
|
22700
|
+
"CREATE"
|
|
22701
|
+
]);
|
|
22702
|
+
DDL_TARGET = /\b(?:DROP\s+TABLE|TRUNCATE(?:\s+TABLE)?)\s+(?:IF\s+EXISTS\s+)?([`"[\]\w$.]+)\s*;?\s*$/i;
|
|
22703
|
+
EMPTY_SCAN = { owners: [], correlated: [], skippedSubquery: false };
|
|
22704
|
+
SINGLE_TARGET = /^\s*(?:UPDATE\s+(?:ONLY\s+)?[\w`"[\]$.]+\s+SET\b|DELETE\s+FROM\s+(?:ONLY\s+)?[\w`"[\]$.]+(?!\s*,)(?:\s|$))/i;
|
|
22705
|
+
SECOND_SOURCE_ANYWHERE = /\b(?:SELECT|TABLE|VALUES|JOIN|USING)\b/i;
|
|
22706
|
+
SECOND_SOURCE_AT_STATEMENT_LEVEL = [/\bWITH\b/i, /\bUPDATE\b[\s\S]*?\bFROM\b/i];
|
|
22707
|
+
MERGE_STATEMENT = /\bMERGE\s+INTO\b/i;
|
|
22708
|
+
MERGE_TARGET = /\bMERGE\s+INTO\s+(?:ONLY\s+)?([\w$.]+)/i;
|
|
22709
|
+
REFERENTIAL_ACTION = /\bON\s+(?:DELETE|UPDATE)\b/gi;
|
|
22710
|
+
});
|
|
22711
|
+
|
|
22712
|
+
// src/commands/write-gate-guard.ts
|
|
22713
|
+
var exports_write_gate_guard = {};
|
|
22714
|
+
__export(exports_write_gate_guard, {
|
|
22715
|
+
recordGateDecision: () => recordGateDecision,
|
|
22716
|
+
guardStructuredWrite: () => guardStructuredWrite
|
|
22717
|
+
});
|
|
22718
|
+
async function recordGateDecision(request) {
|
|
22719
|
+
await writeAuditEntry(request.config, request.command, request.options, {
|
|
22720
|
+
success: request.outcome === "allowed",
|
|
22721
|
+
sideEffectTier: "db-write",
|
|
22722
|
+
...request.sql && { sql: request.sql },
|
|
22723
|
+
...request.verdict.table && { target: request.verdict.table },
|
|
22724
|
+
metadata: {
|
|
22725
|
+
write_gate_tier: request.tier,
|
|
22726
|
+
write_gate_outcome: request.outcome,
|
|
22727
|
+
write_gate_reason: request.verdict.reason ?? "no_where"
|
|
22728
|
+
}
|
|
22729
|
+
});
|
|
22730
|
+
}
|
|
22731
|
+
async function guardStructuredWrite(request) {
|
|
22732
|
+
const verdict2 = classifyStructuredWriteGate({
|
|
22733
|
+
table: request.table,
|
|
22734
|
+
where: request.where,
|
|
22735
|
+
schema: request.schema
|
|
22736
|
+
});
|
|
22737
|
+
if (verdict2.tier !== "two")
|
|
22738
|
+
return true;
|
|
22739
|
+
const operation = request.operation.toUpperCase();
|
|
22740
|
+
const preview = `${operation} ${request.table} WHERE ${Object.keys(request.where).join(", ")}`;
|
|
22741
|
+
const record = (outcome) => recordGateDecision({
|
|
22742
|
+
config: request.config,
|
|
22743
|
+
command: request.operation,
|
|
22744
|
+
options: request.options,
|
|
22745
|
+
verdict: verdict2,
|
|
22746
|
+
outcome,
|
|
22747
|
+
tier: "two",
|
|
22748
|
+
sql: preview
|
|
22749
|
+
});
|
|
22750
|
+
try {
|
|
22751
|
+
const proceed = await enforceWriteGate({
|
|
22752
|
+
verdict: verdict2,
|
|
22753
|
+
statement: preview,
|
|
22754
|
+
operation,
|
|
22755
|
+
human: humanOutputContext(request.options)
|
|
22756
|
+
});
|
|
22757
|
+
await record(proceed ? "allowed" : "declined");
|
|
22758
|
+
return proceed;
|
|
22759
|
+
} catch (error) {
|
|
22760
|
+
if (error instanceof WriteGateRefusal)
|
|
22761
|
+
await record("refused");
|
|
22762
|
+
throw error;
|
|
22763
|
+
}
|
|
22764
|
+
}
|
|
22765
|
+
var init_write_gate_guard = __esm(() => {
|
|
22766
|
+
init_integration_helper();
|
|
22767
|
+
init_mutation_outcome();
|
|
22768
|
+
init_write_gate();
|
|
22769
|
+
init_write_gate_prompt();
|
|
22770
|
+
});
|
|
22771
|
+
|
|
22205
22772
|
// src/core/permission/redis.ts
|
|
22206
22773
|
var exports_redis = {};
|
|
22207
22774
|
__export(exports_redis, {
|
|
@@ -22528,10 +23095,24 @@ async function executeQueryCommand(sql, options, command, runtimeOverrides = {})
|
|
|
22528
23095
|
connectionName = connectionNames[0];
|
|
22529
23096
|
config = await runtime.loadConfig(configPath, connectionName);
|
|
22530
23097
|
const context = { config, configPath, connectionName };
|
|
23098
|
+
if (!await runtime.enforceGate(resolvedSql, options, context))
|
|
23099
|
+
return;
|
|
22531
23100
|
await runtime.preflight(resolvedSql, options, context, fieldSelection, false);
|
|
22532
23101
|
const execution = await runtime.executeConnection(resolvedSql, options, context, fieldSelection);
|
|
22533
23102
|
await runtime.presentResult(resolvedSql, options, execution, tableCellLimit);
|
|
22534
23103
|
} catch (error) {
|
|
23104
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
23105
|
+
if (error instanceof WriteGateRefusal2) {
|
|
23106
|
+
if (options.format === "json") {
|
|
23107
|
+
console.log(JSON.stringify({
|
|
23108
|
+
status: "refused",
|
|
23109
|
+
code: error.code,
|
|
23110
|
+
reason: error.reason,
|
|
23111
|
+
...error.table && { table: error.table }
|
|
23112
|
+
}, null, 2));
|
|
23113
|
+
}
|
|
23114
|
+
throw error;
|
|
23115
|
+
}
|
|
22535
23116
|
let auditId = null;
|
|
22536
23117
|
let envelopeId;
|
|
22537
23118
|
if (options.recovery === true && !multiConnectionRequest) {
|
|
@@ -22618,6 +23199,48 @@ function scopeMultiConnectionNotices(execution) {
|
|
|
22618
23199
|
notices: []
|
|
22619
23200
|
};
|
|
22620
23201
|
}
|
|
23202
|
+
async function enforceQueryWriteGate(query, options, context) {
|
|
23203
|
+
const { config } = context;
|
|
23204
|
+
const { toSqlDialect: toSqlDialect2, enforcePermission: enforcePermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
23205
|
+
const dialect = toSqlDialect2(config.connection?.system);
|
|
23206
|
+
if (!dialect)
|
|
23207
|
+
return true;
|
|
23208
|
+
const { classifySqlWriteGate: classifySqlWriteGate2 } = await Promise.resolve().then(() => (init_write_gate(), exports_write_gate));
|
|
23209
|
+
const verdict2 = await classifySqlWriteGate2(query, { dialect });
|
|
23210
|
+
if (verdict2.tier === "none")
|
|
23211
|
+
return true;
|
|
23212
|
+
enforcePermission2(query, config.permission, dialect);
|
|
23213
|
+
const { enforceWriteGate: enforceWriteGate2, WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
23214
|
+
const { humanOutputContext: humanOutputContext2 } = await Promise.resolve().then(() => (init_mutation_outcome(), exports_mutation_outcome));
|
|
23215
|
+
const { recordGateDecision: recordGateDecision2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
23216
|
+
const record = (outcome) => recordGateDecision2({
|
|
23217
|
+
config,
|
|
23218
|
+
command: "query",
|
|
23219
|
+
options: { ...options, config: context.configPath, connectionName: context.connectionName },
|
|
23220
|
+
verdict: verdict2,
|
|
23221
|
+
outcome,
|
|
23222
|
+
tier: verdict2.tier === "two" ? "two" : "one",
|
|
23223
|
+
sql: query
|
|
23224
|
+
});
|
|
23225
|
+
try {
|
|
23226
|
+
const proceed = await enforceWriteGate2({
|
|
23227
|
+
verdict: verdict2,
|
|
23228
|
+
statement: query,
|
|
23229
|
+
operation: verdict2.operation ?? "WRITE",
|
|
23230
|
+
human: humanOutputContext2(options),
|
|
23231
|
+
...options.yes !== undefined && { yes: options.yes }
|
|
23232
|
+
});
|
|
23233
|
+
if (verdict2.tier === "two")
|
|
23234
|
+
await record(proceed ? "allowed" : "declined");
|
|
23235
|
+
else if (!proceed)
|
|
23236
|
+
await record("declined");
|
|
23237
|
+
return proceed;
|
|
23238
|
+
} catch (error) {
|
|
23239
|
+
if (error instanceof WriteGateRefusal2)
|
|
23240
|
+
await record("refused");
|
|
23241
|
+
throw error;
|
|
23242
|
+
}
|
|
23243
|
+
}
|
|
22621
23244
|
async function preflightQuery(query, options, context, fieldSelection, multiConnection) {
|
|
22622
23245
|
const { config } = context;
|
|
22623
23246
|
if (!config.connection)
|
|
@@ -23061,6 +23684,7 @@ var init_query = __esm(() => {
|
|
|
23061
23684
|
executeConnection: executeConnectionQuery,
|
|
23062
23685
|
presentResult: presentSingleResult,
|
|
23063
23686
|
writeAudit: writeAuditEntry,
|
|
23687
|
+
enforceGate: enforceQueryWriteGate,
|
|
23064
23688
|
randomUUID: () => crypto3.randomUUID(),
|
|
23065
23689
|
emitRecovery: async (error, context, options) => {
|
|
23066
23690
|
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
@@ -23085,7 +23709,7 @@ __export(exports_inline_registrars, {
|
|
|
23085
23709
|
registerDeleteCommand: () => registerDeleteCommand
|
|
23086
23710
|
});
|
|
23087
23711
|
function registerQueryCommand(program2, run) {
|
|
23088
|
-
program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
|
|
23712
|
+
program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--yes", "Skip the confirmation shown before an ordinary write; statements that are not limited to specific rows ignore it", false).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
|
|
23089
23713
|
const rawArgs = command.parent?.rawArgs ?? [];
|
|
23090
23714
|
const rawTruncate = findLongOptionValue(rawArgs, "--truncate");
|
|
23091
23715
|
const rootUse = command.parent?.opts().use;
|
|
@@ -23401,8 +24025,8 @@ function applyBlacklistRules(facts, blacklist, factors) {
|
|
|
23401
24025
|
}
|
|
23402
24026
|
function findBlacklistedColumnsForTable(blacklist, table) {
|
|
23403
24027
|
const columns = blacklist.columns ?? {};
|
|
23404
|
-
for (const [
|
|
23405
|
-
if (
|
|
24028
|
+
for (const [tableName2, columnNames] of Object.entries(columns)) {
|
|
24029
|
+
if (tableName2.toLowerCase() === table.toLowerCase()) {
|
|
23406
24030
|
return new Set(columnNames);
|
|
23407
24031
|
}
|
|
23408
24032
|
}
|
|
@@ -25045,9 +25669,12 @@ async function qCommand(name, options, command) {
|
|
|
25045
25669
|
console.error(`\u26A0 ${w2}`);
|
|
25046
25670
|
if (prepared.warnings.length > 0)
|
|
25047
25671
|
console.error("");
|
|
25672
|
+
const family = engineFamily(engine);
|
|
25673
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25674
|
+
const classification = family === "sql" ? enforcePermission(prepared.rewrittenSql, config.permission, sqlDialect) : undefined;
|
|
25048
25675
|
if (options.dryRun) {
|
|
25049
25676
|
console.log(formatDryRun({
|
|
25050
|
-
family
|
|
25677
|
+
family,
|
|
25051
25678
|
driverSql: prepared.driver.sql,
|
|
25052
25679
|
values: prepared.driver.values,
|
|
25053
25680
|
execHints: prepared.execHints,
|
|
@@ -25068,8 +25695,6 @@ async function qCommand(name, options, command) {
|
|
|
25068
25695
|
}
|
|
25069
25696
|
const blacklistManager = new BlacklistManager(config);
|
|
25070
25697
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
25071
|
-
const family = engineFamily(engine);
|
|
25072
|
-
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25073
25698
|
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
25074
25699
|
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
25075
25700
|
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
@@ -25077,8 +25702,8 @@ async function qCommand(name, options, command) {
|
|
|
25077
25702
|
targetNameForAudit = targetName || name;
|
|
25078
25703
|
if (family === "es") {
|
|
25079
25704
|
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
25080
|
-
} else if (
|
|
25081
|
-
blacklistValidator.checkTablesBlacklist(
|
|
25705
|
+
} else if (classification && targets.length > 0) {
|
|
25706
|
+
blacklistValidator.checkTablesBlacklist(classification.type, targets);
|
|
25082
25707
|
}
|
|
25083
25708
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
25084
25709
|
await adapter.connect();
|
|
@@ -25366,12 +25991,12 @@ class DataExecutor {
|
|
|
25366
25991
|
this.dbSystem = dbSystem;
|
|
25367
25992
|
this.blacklistValidator = blacklistValidator;
|
|
25368
25993
|
}
|
|
25369
|
-
buildInsertSql(
|
|
25994
|
+
buildInsertSql(tableName2, data, schema) {
|
|
25370
25995
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25371
25996
|
const dataKeys = Object.keys(data);
|
|
25372
25997
|
for (const key of dataKeys) {
|
|
25373
25998
|
if (!columnNames.includes(key)) {
|
|
25374
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
25999
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}". Valid columns: ${columnNames.join(", ")}`);
|
|
25375
26000
|
}
|
|
25376
26001
|
}
|
|
25377
26002
|
const columns = dataKeys;
|
|
@@ -25379,38 +26004,38 @@ class DataExecutor {
|
|
|
25379
26004
|
const systemType = this.getSystemType();
|
|
25380
26005
|
const placeholders = systemType === "postgresql" ? dataKeys.map((_, index) => `$${index + 1}`).join(", ") : dataKeys.map(() => "?").join(", ");
|
|
25381
26006
|
const quote = this.getQuoteChar();
|
|
25382
|
-
const sql = `INSERT INTO ${quote}${
|
|
26007
|
+
const sql = `INSERT INTO ${quote}${tableName2}${quote} (${columns.map((col) => `${quote}${col}${quote}`).join(", ")}) VALUES (${placeholders})`;
|
|
25383
26008
|
return {
|
|
25384
26009
|
sql,
|
|
25385
26010
|
params: values
|
|
25386
26011
|
};
|
|
25387
26012
|
}
|
|
25388
|
-
async executeInsert(
|
|
26013
|
+
async executeInsert(tableName2, data, schema, options) {
|
|
25389
26014
|
const timestamp = new Date().toISOString();
|
|
25390
26015
|
try {
|
|
25391
|
-
this.checkBlacklist("INSERT",
|
|
26016
|
+
this.checkBlacklist("INSERT", tableName2);
|
|
25392
26017
|
enforcePermissionForType("INSERT", this.permission);
|
|
25393
|
-
return await this.executeMutation("insert", this.buildInsertSql(
|
|
26018
|
+
return await this.executeMutation("insert", this.buildInsertSql(tableName2, data, schema), timestamp, options, { destructive: false });
|
|
25394
26019
|
} catch (error) {
|
|
25395
26020
|
return this.handleMutationError("insert", timestamp, error);
|
|
25396
26021
|
}
|
|
25397
26022
|
}
|
|
25398
|
-
async executeUpdate(
|
|
26023
|
+
async executeUpdate(tableName2, data, where, schema, options) {
|
|
25399
26024
|
const timestamp = new Date().toISOString();
|
|
25400
26025
|
try {
|
|
25401
|
-
this.checkBlacklist("UPDATE",
|
|
26026
|
+
this.checkBlacklist("UPDATE", tableName2);
|
|
25402
26027
|
enforcePermissionForType("UPDATE", this.permission);
|
|
25403
|
-
return await this.executeMutation("update", this.buildUpdateSql(
|
|
26028
|
+
return await this.executeMutation("update", this.buildUpdateSql(tableName2, data, where, schema), timestamp, options, { destructive: false });
|
|
25404
26029
|
} catch (error) {
|
|
25405
26030
|
return this.handleMutationError("update", timestamp, error);
|
|
25406
26031
|
}
|
|
25407
26032
|
}
|
|
25408
|
-
async executeDelete(
|
|
26033
|
+
async executeDelete(tableName2, where, schema, options) {
|
|
25409
26034
|
const timestamp = new Date().toISOString();
|
|
25410
26035
|
try {
|
|
25411
|
-
this.checkBlacklist("DELETE",
|
|
26036
|
+
this.checkBlacklist("DELETE", tableName2);
|
|
25412
26037
|
enforcePermissionForType("DELETE", this.permission);
|
|
25413
|
-
return await this.executeMutation("delete", this.buildDeleteSql(
|
|
26038
|
+
return await this.executeMutation("delete", this.buildDeleteSql(tableName2, where, schema), timestamp, options, { destructive: true });
|
|
25414
26039
|
} catch (error) {
|
|
25415
26040
|
return this.handleMutationError("delete", timestamp, error);
|
|
25416
26041
|
}
|
|
@@ -25421,8 +26046,8 @@ class DataExecutor {
|
|
|
25421
26046
|
getQuoteChar() {
|
|
25422
26047
|
return this.dbSystem === "mysql" ? "`" : '"';
|
|
25423
26048
|
}
|
|
25424
|
-
checkBlacklist(operation,
|
|
25425
|
-
this.blacklistValidator?.checkTableBlacklist(operation,
|
|
26049
|
+
checkBlacklist(operation, tableName2) {
|
|
26050
|
+
this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
|
|
25426
26051
|
}
|
|
25427
26052
|
async executeMutation(operation, statement, timestamp, options, { destructive }) {
|
|
25428
26053
|
if (options?.dryRun) {
|
|
@@ -25477,13 +26102,13 @@ class DataExecutor {
|
|
|
25477
26102
|
error: `${operation.toUpperCase()} failed: ${errorMessage2}`
|
|
25478
26103
|
};
|
|
25479
26104
|
}
|
|
25480
|
-
buildUpdateSql(
|
|
26105
|
+
buildUpdateSql(tableName2, data, where, schema) {
|
|
25481
26106
|
const dataKeys = Object.keys(data);
|
|
25482
26107
|
const whereKeys = Object.keys(where);
|
|
25483
26108
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25484
26109
|
for (const key of [...dataKeys, ...whereKeys]) {
|
|
25485
26110
|
if (!columnNames.includes(key)) {
|
|
25486
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
26111
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25487
26112
|
}
|
|
25488
26113
|
}
|
|
25489
26114
|
const systemType = this.getSystemType();
|
|
@@ -25497,16 +26122,16 @@ class DataExecutor {
|
|
|
25497
26122
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25498
26123
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25499
26124
|
}).join(" AND ");
|
|
25500
|
-
const sql = `UPDATE ${quote}${
|
|
26125
|
+
const sql = `UPDATE ${quote}${tableName2}${quote} SET ${setClause} WHERE ${whereClause}`;
|
|
25501
26126
|
const params = [...dataKeys.map((key) => data[key]), ...whereKeys.map((key) => where[key])];
|
|
25502
26127
|
return { sql, params };
|
|
25503
26128
|
}
|
|
25504
|
-
buildDeleteSql(
|
|
26129
|
+
buildDeleteSql(tableName2, where, schema) {
|
|
25505
26130
|
const whereKeys = Object.keys(where);
|
|
25506
26131
|
const columnNames = schema.columns.map((col) => col.name);
|
|
25507
26132
|
for (const key of whereKeys) {
|
|
25508
26133
|
if (!columnNames.includes(key)) {
|
|
25509
|
-
throw new Error(`Column "${key}" not found in table "${
|
|
26134
|
+
throw new Error(`Column "${key}" not found in table "${tableName2}"`);
|
|
25510
26135
|
}
|
|
25511
26136
|
}
|
|
25512
26137
|
const systemType = this.getSystemType();
|
|
@@ -25516,7 +26141,7 @@ class DataExecutor {
|
|
|
25516
26141
|
const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
|
|
25517
26142
|
return `${quote}${key}${quote} = ${placeholder}`;
|
|
25518
26143
|
}).join(" AND ");
|
|
25519
|
-
const sql = `DELETE FROM ${quote}${
|
|
26144
|
+
const sql = `DELETE FROM ${quote}${tableName2}${quote} WHERE ${whereClause}`;
|
|
25520
26145
|
const params = whereKeys.map((key) => where[key]);
|
|
25521
26146
|
return { sql, params };
|
|
25522
26147
|
}
|
|
@@ -25590,87 +26215,6 @@ function auditOutcomeForMutation(result, table, recoveryRef) {
|
|
|
25590
26215
|
};
|
|
25591
26216
|
}
|
|
25592
26217
|
|
|
25593
|
-
// src/commands/mutation-outcome.ts
|
|
25594
|
-
function shouldRenderForHuman(context) {
|
|
25595
|
-
if (context.format === "json")
|
|
25596
|
-
return false;
|
|
25597
|
-
return context.isTTY;
|
|
25598
|
-
}
|
|
25599
|
-
function humanOutputContext(options) {
|
|
25600
|
-
return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
|
|
25601
|
-
}
|
|
25602
|
-
function renderMutationOutcome(result, table, elapsedMs) {
|
|
25603
|
-
if (result.status === "error") {
|
|
25604
|
-
return [
|
|
25605
|
-
t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
|
|
25606
|
-
t("ceremony.recovery_retry")
|
|
25607
|
-
];
|
|
25608
|
-
}
|
|
25609
|
-
if (result.status === "cancelled") {
|
|
25610
|
-
return [t_vars("ceremony.cancelled", { table })];
|
|
25611
|
-
}
|
|
25612
|
-
if (result.status === "dry_run") {
|
|
25613
|
-
return [t_vars("ceremony.dry_run", { table })];
|
|
25614
|
-
}
|
|
25615
|
-
const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
|
|
25616
|
-
if (result.rows_affected === 0) {
|
|
25617
|
-
return [t_vars("ceremony.matched_nothing", { table }), elapsed];
|
|
25618
|
-
}
|
|
25619
|
-
return [
|
|
25620
|
-
t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
|
|
25621
|
-
elapsed,
|
|
25622
|
-
t("ceremony.recovery_none")
|
|
25623
|
-
];
|
|
25624
|
-
}
|
|
25625
|
-
function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
|
|
25626
|
-
if (shouldRenderForHuman(context)) {
|
|
25627
|
-
const writeLine = result.status === "error" ? console.error : console.log;
|
|
25628
|
-
for (const line of renderMutationOutcome(result, table, elapsedMs))
|
|
25629
|
-
writeLine(line);
|
|
25630
|
-
return;
|
|
25631
|
-
}
|
|
25632
|
-
console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
|
|
25633
|
-
}
|
|
25634
|
-
function printMutationFailure(error, operation, context) {
|
|
25635
|
-
if (shouldRenderForHuman(context)) {
|
|
25636
|
-
console.error(t_vars("ceremony.failed", { reason: error.message }));
|
|
25637
|
-
if (error instanceof BlacklistError) {
|
|
25638
|
-
console.error(t("ceremony.blacklist_hint"));
|
|
25639
|
-
}
|
|
25640
|
-
return;
|
|
25641
|
-
}
|
|
25642
|
-
console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
|
|
25643
|
-
}
|
|
25644
|
-
function cancelledOutcome(operation, preview) {
|
|
25645
|
-
return {
|
|
25646
|
-
status: "cancelled",
|
|
25647
|
-
operation,
|
|
25648
|
-
rows_affected: 0,
|
|
25649
|
-
sql: preview,
|
|
25650
|
-
timestamp: new Date().toISOString()
|
|
25651
|
-
};
|
|
25652
|
-
}
|
|
25653
|
-
function mutationEnvelope(result) {
|
|
25654
|
-
return {
|
|
25655
|
-
status: result.status,
|
|
25656
|
-
operation: result.operation,
|
|
25657
|
-
rows_affected: result.rows_affected,
|
|
25658
|
-
...result.timestamp && { timestamp: result.timestamp },
|
|
25659
|
-
...result.sql && { sql: result.sql },
|
|
25660
|
-
...result.error && { error: result.error }
|
|
25661
|
-
};
|
|
25662
|
-
}
|
|
25663
|
-
var VERB_KEY;
|
|
25664
|
-
var init_mutation_outcome = __esm(() => {
|
|
25665
|
-
init_message_loader();
|
|
25666
|
-
init_blacklist();
|
|
25667
|
-
VERB_KEY = {
|
|
25668
|
-
insert: "ceremony.inserted",
|
|
25669
|
-
update: "ceremony.updated",
|
|
25670
|
-
delete: "ceremony.deleted"
|
|
25671
|
-
};
|
|
25672
|
-
});
|
|
25673
|
-
|
|
25674
26218
|
// src/core/mongo/dry-run-formatter.ts
|
|
25675
26219
|
function pretty(value) {
|
|
25676
26220
|
return JSON.stringify(value, null, INDENT);
|
|
@@ -26974,6 +27518,20 @@ async function updateCommand(table, options, command) {
|
|
|
26974
27518
|
await adapter.connect();
|
|
26975
27519
|
try {
|
|
26976
27520
|
const schema = await adapter.getTableSchema(table);
|
|
27521
|
+
const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
27522
|
+
if (options.dryRun !== true && !await guardStructuredWrite2({
|
|
27523
|
+
operation: "update",
|
|
27524
|
+
table,
|
|
27525
|
+
where: whereConditions,
|
|
27526
|
+
schema,
|
|
27527
|
+
config,
|
|
27528
|
+
options
|
|
27529
|
+
})) {
|
|
27530
|
+
const cancelled = cancelledOutcome("update", `UPDATE ${table}`);
|
|
27531
|
+
printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
|
|
27532
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
|
|
27533
|
+
return;
|
|
27534
|
+
}
|
|
26977
27535
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
26978
27536
|
const blacklistManager = new BlacklistManager(config);
|
|
26979
27537
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -26999,6 +27557,11 @@ async function updateCommand(table, options, command) {
|
|
|
26999
27557
|
await adapter.disconnect();
|
|
27000
27558
|
}
|
|
27001
27559
|
} catch (error) {
|
|
27560
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
27561
|
+
if (error instanceof WriteGateRefusal2) {
|
|
27562
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
27563
|
+
process.exit(1);
|
|
27564
|
+
}
|
|
27002
27565
|
let auditId = null;
|
|
27003
27566
|
let envelopeId;
|
|
27004
27567
|
if (options.recovery === true) {
|
|
@@ -27254,6 +27817,20 @@ async function deleteCommand(table, options, command) {
|
|
|
27254
27817
|
await adapter.connect();
|
|
27255
27818
|
try {
|
|
27256
27819
|
const schema = await adapter.getTableSchema(table);
|
|
27820
|
+
const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
|
|
27821
|
+
if (options.dryRun !== true && !await guardStructuredWrite2({
|
|
27822
|
+
operation: "delete",
|
|
27823
|
+
table,
|
|
27824
|
+
where: whereConditions,
|
|
27825
|
+
schema,
|
|
27826
|
+
config,
|
|
27827
|
+
options
|
|
27828
|
+
})) {
|
|
27829
|
+
const cancelled = cancelledOutcome("delete", `DELETE FROM ${table}`);
|
|
27830
|
+
printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
|
|
27831
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
|
|
27832
|
+
return;
|
|
27833
|
+
}
|
|
27257
27834
|
const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
|
|
27258
27835
|
const blacklistManager = new BlacklistManager(config);
|
|
27259
27836
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -27279,6 +27856,11 @@ async function deleteCommand(table, options, command) {
|
|
|
27279
27856
|
await adapter.disconnect();
|
|
27280
27857
|
}
|
|
27281
27858
|
} catch (error) {
|
|
27859
|
+
const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
|
|
27860
|
+
if (error instanceof WriteGateRefusal2) {
|
|
27861
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27862
|
+
process.exit(1);
|
|
27863
|
+
}
|
|
27282
27864
|
let auditId = null;
|
|
27283
27865
|
let envelopeId;
|
|
27284
27866
|
if (options.recovery === true) {
|
|
@@ -27777,16 +28359,6 @@ var init_export = __esm(() => {
|
|
|
27777
28359
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
27778
28360
|
});
|
|
27779
28361
|
|
|
27780
|
-
// src/core/sql-parser.ts
|
|
27781
|
-
function sqlParser() {
|
|
27782
|
-
if (!cached2) {
|
|
27783
|
-
const { Parser } = __require("node-sql-parser");
|
|
27784
|
-
cached2 = new Parser;
|
|
27785
|
-
}
|
|
27786
|
-
return cached2;
|
|
27787
|
-
}
|
|
27788
|
-
var cached2 = null;
|
|
27789
|
-
|
|
27790
28362
|
// src/core/explain/read-only.ts
|
|
27791
28363
|
function containsAnalyzeUnsafeNode(node) {
|
|
27792
28364
|
if (!node || typeof node !== "object")
|
|
@@ -28891,12 +29463,12 @@ function compactVisibleSchema(config) {
|
|
|
28891
29463
|
const blacklistManager = new BlacklistManager(config);
|
|
28892
29464
|
const compactSchema = {};
|
|
28893
29465
|
const rawSchema = config.schema ?? {};
|
|
28894
|
-
for (const [
|
|
28895
|
-
if (blacklistManager.isTableBlacklisted(
|
|
29466
|
+
for (const [tableName2, tableSchema] of Object.entries(rawSchema)) {
|
|
29467
|
+
if (blacklistManager.isTableBlacklisted(tableName2))
|
|
28896
29468
|
continue;
|
|
28897
29469
|
const columns = [];
|
|
28898
29470
|
for (const col of tableSchema.columns || []) {
|
|
28899
|
-
if (blacklistManager.isColumnBlacklisted(
|
|
29471
|
+
if (blacklistManager.isColumnBlacklisted(tableName2, col.name))
|
|
28900
29472
|
continue;
|
|
28901
29473
|
const compactCol = {
|
|
28902
29474
|
name: col.name,
|
|
@@ -28916,7 +29488,7 @@ function compactVisibleSchema(config) {
|
|
|
28916
29488
|
columns.push(compactCol);
|
|
28917
29489
|
}
|
|
28918
29490
|
const compactTable = {
|
|
28919
|
-
name: tableSchema.name ||
|
|
29491
|
+
name: tableSchema.name || tableName2,
|
|
28920
29492
|
columns
|
|
28921
29493
|
};
|
|
28922
29494
|
const rowCount = tableSchema.estimatedRowCount ?? tableSchema.rowCount;
|
|
@@ -28932,7 +29504,7 @@ function compactVisibleSchema(config) {
|
|
|
28932
29504
|
refColumns: fk.refColumns
|
|
28933
29505
|
}));
|
|
28934
29506
|
}
|
|
28935
|
-
compactSchema[
|
|
29507
|
+
compactSchema[tableName2] = compactTable;
|
|
28936
29508
|
}
|
|
28937
29509
|
return compactSchema;
|
|
28938
29510
|
}
|
|
@@ -29040,10 +29612,10 @@ function serializeXml(payload) {
|
|
|
29040
29612
|
}
|
|
29041
29613
|
if (Object.keys(payload.schema).length > 0) {
|
|
29042
29614
|
parts.push(" <tables>");
|
|
29043
|
-
for (const [
|
|
29615
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
29044
29616
|
const rowsAttr = table.rowCount !== undefined ? ` rows="${table.rowCount}"` : "";
|
|
29045
29617
|
const pkAttr = table.primaryKey ? ` primary_key="${table.primaryKey.join(",")}"` : "";
|
|
29046
|
-
parts.push(` <table name="${escapeXml(
|
|
29618
|
+
parts.push(` <table name="${escapeXml(tableName2)}"${rowsAttr}${pkAttr}>`);
|
|
29047
29619
|
for (const col of table.columns) {
|
|
29048
29620
|
const pk = col.primaryKey ? ' primary_key="true"' : "";
|
|
29049
29621
|
const nulls = col.nullable ? ' nullable="true"' : ' nullable="false"';
|
|
@@ -29154,9 +29726,9 @@ function serializeMarkdown(payload) {
|
|
|
29154
29726
|
parts.push(`*No table schemas cached or active.*`);
|
|
29155
29727
|
parts.push(``);
|
|
29156
29728
|
} else {
|
|
29157
|
-
for (const [
|
|
29729
|
+
for (const [tableName2, table] of Object.entries(payload.schema)) {
|
|
29158
29730
|
const rows = table.rowCount !== undefined ? ` (~${table.rowCount.toLocaleString()} rows)` : "";
|
|
29159
|
-
parts.push(`### \`${
|
|
29731
|
+
parts.push(`### \`${tableName2}\`${rows}`);
|
|
29160
29732
|
parts.push(``);
|
|
29161
29733
|
parts.push(`| Column | Type | Nullable | Primary Key | Default | References |`);
|
|
29162
29734
|
parts.push(`| :--- | :--- | :---: | :---: | :--- | :--- |`);
|
|
@@ -30289,37 +30861,37 @@ async function blacklistList(configPath, format = "text") {
|
|
|
30289
30861
|
console.error(`\u26A0 blacklist.columns["${w2.collection}"]: '${w2.raw}' is ignored on mongo connections (${w2.reason}).`);
|
|
30290
30862
|
}
|
|
30291
30863
|
}
|
|
30292
|
-
async function blacklistTableAdd(
|
|
30293
|
-
if (!isValidTableName(
|
|
30294
|
-
throw new Error(t_vars("errors.invalid_table_name", { table:
|
|
30864
|
+
async function blacklistTableAdd(tableName2, configPath) {
|
|
30865
|
+
if (!isValidTableName(tableName2)) {
|
|
30866
|
+
throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
|
|
30295
30867
|
}
|
|
30296
30868
|
const config = await configModule.read(configPath);
|
|
30297
30869
|
const blacklist = getOrInitBlacklist(config);
|
|
30298
|
-
if (blacklist.tables.includes(
|
|
30299
|
-
throw new Error(t_vars("errors.table_already_blacklisted", { table:
|
|
30870
|
+
if (blacklist.tables.includes(tableName2)) {
|
|
30871
|
+
throw new Error(t_vars("errors.table_already_blacklisted", { table: tableName2 }));
|
|
30300
30872
|
}
|
|
30301
30873
|
const newBlacklist = {
|
|
30302
30874
|
...blacklist,
|
|
30303
|
-
tables: [...blacklist.tables,
|
|
30875
|
+
tables: [...blacklist.tables, tableName2]
|
|
30304
30876
|
};
|
|
30305
30877
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30306
|
-
console.log(t_vars("blacklist.table_added", { table:
|
|
30878
|
+
console.log(t_vars("blacklist.table_added", { table: tableName2 }));
|
|
30307
30879
|
}
|
|
30308
|
-
async function blacklistTableRemove(
|
|
30309
|
-
if (!isValidTableName(
|
|
30310
|
-
throw new Error(t_vars("errors.invalid_table_name", { table:
|
|
30880
|
+
async function blacklistTableRemove(tableName2, configPath) {
|
|
30881
|
+
if (!isValidTableName(tableName2)) {
|
|
30882
|
+
throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
|
|
30311
30883
|
}
|
|
30312
30884
|
const config = await configModule.read(configPath);
|
|
30313
30885
|
const blacklist = getOrInitBlacklist(config);
|
|
30314
|
-
if (!blacklist.tables.includes(
|
|
30315
|
-
throw new Error(t_vars("errors.table_not_in_blacklist", { table:
|
|
30886
|
+
if (!blacklist.tables.includes(tableName2)) {
|
|
30887
|
+
throw new Error(t_vars("errors.table_not_in_blacklist", { table: tableName2 }));
|
|
30316
30888
|
}
|
|
30317
30889
|
const newBlacklist = {
|
|
30318
30890
|
...blacklist,
|
|
30319
|
-
tables: blacklist.tables.filter((t2) => t2 !==
|
|
30891
|
+
tables: blacklist.tables.filter((t2) => t2 !== tableName2)
|
|
30320
30892
|
};
|
|
30321
30893
|
await configModule.write(configPath, { ...config, blacklist: newBlacklist });
|
|
30322
|
-
console.log(t_vars("blacklist.table_removed", { table:
|
|
30894
|
+
console.log(t_vars("blacklist.table_removed", { table: tableName2 }));
|
|
30323
30895
|
}
|
|
30324
30896
|
async function blacklistColumnAdd(identifier, configPath) {
|
|
30325
30897
|
const parsed = parseColumnIdentifier(identifier);
|
|
@@ -30389,17 +30961,17 @@ var init_blacklist2 = __esm(() => {
|
|
|
30389
30961
|
}
|
|
30390
30962
|
});
|
|
30391
30963
|
tableCmd = blacklistCommand.command("table").description(t("blacklist.tables_label"));
|
|
30392
|
-
tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (
|
|
30964
|
+
tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
|
|
30393
30965
|
try {
|
|
30394
|
-
await blacklistTableAdd(
|
|
30966
|
+
await blacklistTableAdd(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30395
30967
|
} catch (error) {
|
|
30396
30968
|
console.error(error.message);
|
|
30397
30969
|
process.exit(1);
|
|
30398
30970
|
}
|
|
30399
30971
|
});
|
|
30400
|
-
tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (
|
|
30972
|
+
tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
|
|
30401
30973
|
try {
|
|
30402
|
-
await blacklistTableRemove(
|
|
30974
|
+
await blacklistTableRemove(tableName2, options.config || DEFAULT_CONFIG_PATH);
|
|
30403
30975
|
} catch (error) {
|
|
30404
30976
|
console.error(error.message);
|
|
30405
30977
|
process.exit(1);
|
|
@@ -31033,7 +31605,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31033
31605
|
return;
|
|
31034
31606
|
}
|
|
31035
31607
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
31036
|
-
const
|
|
31608
|
+
const tableName2 = qualifiedTableName(identity);
|
|
31037
31609
|
const unsupportedTableField = firstMeaningfulUnsupportedField(node, [
|
|
31038
31610
|
"type",
|
|
31039
31611
|
"keyword",
|
|
@@ -31043,14 +31615,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31043
31615
|
]);
|
|
31044
31616
|
if (unsupportedTableField) {
|
|
31045
31617
|
context.unparsed.push({
|
|
31046
|
-
location:
|
|
31618
|
+
location: tableName2,
|
|
31047
31619
|
reason: `blocked: unsupported CREATE TABLE ${unsupportedTableField}`
|
|
31048
31620
|
});
|
|
31049
31621
|
return;
|
|
31050
31622
|
}
|
|
31051
31623
|
if (!Array.isArray(node.create_definitions)) {
|
|
31052
31624
|
context.unparsed.push({
|
|
31053
|
-
location:
|
|
31625
|
+
location: tableName2,
|
|
31054
31626
|
reason: "blocked: unsupported CREATE TABLE without column definitions"
|
|
31055
31627
|
});
|
|
31056
31628
|
return;
|
|
@@ -31065,7 +31637,7 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31065
31637
|
const definitions = [];
|
|
31066
31638
|
for (const value of node.create_definitions) {
|
|
31067
31639
|
if (!isAst(value)) {
|
|
31068
|
-
pushUnsupportedDefinition(
|
|
31640
|
+
pushUnsupportedDefinition(tableName2, "malformed", context.unparsed);
|
|
31069
31641
|
continue;
|
|
31070
31642
|
}
|
|
31071
31643
|
definitions.push(value);
|
|
@@ -31091,14 +31663,14 @@ function consumeCreateTable(node, statement, context) {
|
|
|
31091
31663
|
consumeTablePrimaryKey(definition, table, context.unparsed);
|
|
31092
31664
|
continue;
|
|
31093
31665
|
}
|
|
31094
|
-
pushUnsupportedDefinition(
|
|
31666
|
+
pushUnsupportedDefinition(tableName2, stringValue2(definition.constraint_type) ?? stringValue2(definition.resource) ?? "malformed", context.unparsed);
|
|
31095
31667
|
}
|
|
31096
31668
|
const key = tableIdentityKey(identity);
|
|
31097
31669
|
if (context.tablesByIdentity.has(key)) {
|
|
31098
|
-
context.duplicateTableIdentity ??=
|
|
31670
|
+
context.duplicateTableIdentity ??= tableName2;
|
|
31099
31671
|
context.unparsed.push({
|
|
31100
|
-
location:
|
|
31101
|
-
reason: `blocked: duplicate table identity '${
|
|
31672
|
+
location: tableName2,
|
|
31673
|
+
reason: `blocked: duplicate table identity '${tableName2}'`
|
|
31102
31674
|
});
|
|
31103
31675
|
return;
|
|
31104
31676
|
}
|
|
@@ -31241,12 +31813,12 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31241
31813
|
return;
|
|
31242
31814
|
}
|
|
31243
31815
|
const identity = resolveTableIdentifier(parsedIdentifier);
|
|
31244
|
-
const
|
|
31816
|
+
const tableName2 = qualifiedTableName(identity);
|
|
31245
31817
|
const target = context.tablesByIdentity.get(tableIdentityKey(identity));
|
|
31246
31818
|
if (!target) {
|
|
31247
31819
|
context.unparsed.push({
|
|
31248
31820
|
location: indexName ?? "index",
|
|
31249
|
-
reason: `blocked: CREATE INDEX targets unknown table '${
|
|
31821
|
+
reason: `blocked: CREATE INDEX targets unknown table '${tableName2}'`
|
|
31250
31822
|
});
|
|
31251
31823
|
return;
|
|
31252
31824
|
}
|
|
@@ -31279,7 +31851,7 @@ function consumeCreateIndex(node, statement, context) {
|
|
|
31279
31851
|
}
|
|
31280
31852
|
if (columns.value.length === 0) {
|
|
31281
31853
|
context.unparsed.push({
|
|
31282
|
-
location: indexName ?? `${
|
|
31854
|
+
location: indexName ?? `${tableName2} index`,
|
|
31283
31855
|
reason: "blocked: malformed CREATE INDEX without columns"
|
|
31284
31856
|
});
|
|
31285
31857
|
return;
|
|
@@ -31727,9 +32299,9 @@ function firstMeaningfulUnsupportedField(value, allowed) {
|
|
|
31727
32299
|
}
|
|
31728
32300
|
return null;
|
|
31729
32301
|
}
|
|
31730
|
-
function pushUnsupportedDefinition(
|
|
32302
|
+
function pushUnsupportedDefinition(tableName2, definition, unparsed) {
|
|
31731
32303
|
unparsed.push({
|
|
31732
|
-
location: `${
|
|
32304
|
+
location: `${tableName2} (${definition})`,
|
|
31733
32305
|
reason: `blocked: unsupported table definition '${definition}'`
|
|
31734
32306
|
});
|
|
31735
32307
|
}
|
|
@@ -31855,7 +32427,7 @@ function collectBlocks(text3, unparsed) {
|
|
|
31855
32427
|
function parseModel2(block, unparsed) {
|
|
31856
32428
|
const fields = [];
|
|
31857
32429
|
const pendingIndexes = [];
|
|
31858
|
-
let
|
|
32430
|
+
let tableName2 = block.name;
|
|
31859
32431
|
let hasTableMap = false;
|
|
31860
32432
|
for (const sourceLine of block.lines) {
|
|
31861
32433
|
const line = stripLineComment(sourceLine.text).trim();
|
|
@@ -31874,7 +32446,7 @@ function parseModel2(block, unparsed) {
|
|
|
31874
32446
|
addBlocked2(unparsed, block.name, `malformed or duplicate '@@map' attribute`);
|
|
31875
32447
|
continue;
|
|
31876
32448
|
}
|
|
31877
|
-
|
|
32449
|
+
tableName2 = mapped;
|
|
31878
32450
|
hasTableMap = true;
|
|
31879
32451
|
continue;
|
|
31880
32452
|
}
|
|
@@ -31900,12 +32472,12 @@ function parseModel2(block, unparsed) {
|
|
|
31900
32472
|
}
|
|
31901
32473
|
return {
|
|
31902
32474
|
name: block.name,
|
|
31903
|
-
tableName,
|
|
32475
|
+
tableName: tableName2,
|
|
31904
32476
|
fields,
|
|
31905
32477
|
pendingIndexes,
|
|
31906
32478
|
pendingRelations: [],
|
|
31907
32479
|
columnNames: new Map,
|
|
31908
|
-
table: { identity: { table:
|
|
32480
|
+
table: { identity: { table: tableName2 }, columns: [], indexes: [], foreignKeys: [] }
|
|
31909
32481
|
};
|
|
31910
32482
|
}
|
|
31911
32483
|
function parseField2(line, modelName, lineNumber, unparsed) {
|
|
@@ -32964,31 +33536,31 @@ function compareSnapshots(before, after) {
|
|
|
32964
33536
|
const removedColumns = [];
|
|
32965
33537
|
const modifiedColumns = [];
|
|
32966
33538
|
const indexChanges = [];
|
|
32967
|
-
for (const
|
|
32968
|
-
const added = after.tables[
|
|
33539
|
+
for (const tableName2 of addedTables) {
|
|
33540
|
+
const added = after.tables[tableName2];
|
|
32969
33541
|
if (!added)
|
|
32970
33542
|
continue;
|
|
32971
33543
|
for (const col of added.columns) {
|
|
32972
33544
|
addedColumns.push({
|
|
32973
|
-
table:
|
|
33545
|
+
table: tableName2,
|
|
32974
33546
|
column: col.name,
|
|
32975
33547
|
type: col.type,
|
|
32976
33548
|
nullable: col.nullable
|
|
32977
33549
|
});
|
|
32978
33550
|
}
|
|
32979
33551
|
}
|
|
32980
|
-
for (const
|
|
32981
|
-
const removed = before.tables[
|
|
33552
|
+
for (const tableName2 of removedTables) {
|
|
33553
|
+
const removed = before.tables[tableName2];
|
|
32982
33554
|
if (!removed)
|
|
32983
33555
|
continue;
|
|
32984
33556
|
for (const col of removed.columns) {
|
|
32985
|
-
removedColumns.push({ table:
|
|
33557
|
+
removedColumns.push({ table: tableName2, column: col.name, type: col.type });
|
|
32986
33558
|
}
|
|
32987
33559
|
}
|
|
32988
33560
|
const commonTables = Array.from(afterTables).filter((t2) => beforeTables.has(t2));
|
|
32989
|
-
for (const
|
|
32990
|
-
const beforeTable = before.tables[
|
|
32991
|
-
const afterTable = after.tables[
|
|
33561
|
+
for (const tableName2 of commonTables) {
|
|
33562
|
+
const beforeTable = before.tables[tableName2];
|
|
33563
|
+
const afterTable = after.tables[tableName2];
|
|
32992
33564
|
if (!beforeTable || !afterTable)
|
|
32993
33565
|
continue;
|
|
32994
33566
|
const beforeColMap = new Map(beforeTable.columns.map((c) => [c.name, c]));
|
|
@@ -32996,7 +33568,7 @@ function compareSnapshots(before, after) {
|
|
|
32996
33568
|
for (const [name, col] of afterColMap) {
|
|
32997
33569
|
if (!beforeColMap.has(name)) {
|
|
32998
33570
|
addedColumns.push({
|
|
32999
|
-
table:
|
|
33571
|
+
table: tableName2,
|
|
33000
33572
|
column: name,
|
|
33001
33573
|
type: col.type,
|
|
33002
33574
|
nullable: col.nullable
|
|
@@ -33005,7 +33577,7 @@ function compareSnapshots(before, after) {
|
|
|
33005
33577
|
}
|
|
33006
33578
|
for (const [name, col] of beforeColMap) {
|
|
33007
33579
|
if (!afterColMap.has(name)) {
|
|
33008
|
-
removedColumns.push({ table:
|
|
33580
|
+
removedColumns.push({ table: tableName2, column: name, type: col.type });
|
|
33009
33581
|
}
|
|
33010
33582
|
}
|
|
33011
33583
|
for (const [name, afterCol] of afterColMap) {
|
|
@@ -33014,7 +33586,7 @@ function compareSnapshots(before, after) {
|
|
|
33014
33586
|
continue;
|
|
33015
33587
|
if (beforeCol.type.toLowerCase() !== afterCol.type.toLowerCase() || beforeCol.nullable !== afterCol.nullable) {
|
|
33016
33588
|
modifiedColumns.push({
|
|
33017
|
-
table:
|
|
33589
|
+
table: tableName2,
|
|
33018
33590
|
column: name,
|
|
33019
33591
|
before: { type: beforeCol.type, nullable: beforeCol.nullable },
|
|
33020
33592
|
after: { type: afterCol.type, nullable: afterCol.nullable }
|
|
@@ -33025,12 +33597,12 @@ function compareSnapshots(before, after) {
|
|
|
33025
33597
|
const afterIndexes = new Map((afterTable.indexes || []).map((i) => [i.name, i]));
|
|
33026
33598
|
for (const name of afterIndexes.keys()) {
|
|
33027
33599
|
if (!beforeIndexes.has(name)) {
|
|
33028
|
-
indexChanges.push({ table:
|
|
33600
|
+
indexChanges.push({ table: tableName2, name, change: "added" });
|
|
33029
33601
|
}
|
|
33030
33602
|
}
|
|
33031
33603
|
for (const name of beforeIndexes.keys()) {
|
|
33032
33604
|
if (!afterIndexes.has(name)) {
|
|
33033
|
-
indexChanges.push({ table:
|
|
33605
|
+
indexChanges.push({ table: tableName2, name, change: "removed" });
|
|
33034
33606
|
}
|
|
33035
33607
|
}
|
|
33036
33608
|
}
|
|
@@ -36148,6 +36720,81 @@ Verification artifact: ${verificationArtifactPath}`);
|
|
|
36148
36720
|
});
|
|
36149
36721
|
});
|
|
36150
36722
|
|
|
36723
|
+
// src/core/audit/write-gate-summary.ts
|
|
36724
|
+
function metadataOf(entry) {
|
|
36725
|
+
const metadata = entry.metadata;
|
|
36726
|
+
return metadata && typeof metadata === "object" ? metadata : {};
|
|
36727
|
+
}
|
|
36728
|
+
function readString(source, key) {
|
|
36729
|
+
const value = source[key];
|
|
36730
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
36731
|
+
}
|
|
36732
|
+
|
|
36733
|
+
class Span {
|
|
36734
|
+
from;
|
|
36735
|
+
to;
|
|
36736
|
+
add(ts) {
|
|
36737
|
+
if (this.from === undefined || ts < this.from)
|
|
36738
|
+
this.from = ts;
|
|
36739
|
+
if (this.to === undefined || ts > this.to)
|
|
36740
|
+
this.to = ts;
|
|
36741
|
+
}
|
|
36742
|
+
value() {
|
|
36743
|
+
return this.from === undefined || this.to === undefined ? null : { from: this.from, to: this.to };
|
|
36744
|
+
}
|
|
36745
|
+
}
|
|
36746
|
+
function summarizeWriteGate(entries) {
|
|
36747
|
+
const outcomes = Object.fromEntries(GATE_OUTCOMES.map((o) => [o, 0]));
|
|
36748
|
+
const reasons = Object.fromEntries(GATE_REASONS.map((r) => [r, 0]));
|
|
36749
|
+
const tierOneOutcomes = {};
|
|
36750
|
+
const window = new Span;
|
|
36751
|
+
const range = new Span;
|
|
36752
|
+
let total = 0;
|
|
36753
|
+
let tierOneTotal = 0;
|
|
36754
|
+
const tally = (into, key) => {
|
|
36755
|
+
into[key] = (into[key] ?? 0) + 1;
|
|
36756
|
+
};
|
|
36757
|
+
for (const entry of entries) {
|
|
36758
|
+
window.add(entry.ts);
|
|
36759
|
+
const metadata = metadataOf(entry);
|
|
36760
|
+
const tier = readString(metadata, "write_gate_tier");
|
|
36761
|
+
if (!tier)
|
|
36762
|
+
continue;
|
|
36763
|
+
const outcome = readString(metadata, "write_gate_outcome") ?? "unknown";
|
|
36764
|
+
if (tier !== "two") {
|
|
36765
|
+
tierOneTotal += 1;
|
|
36766
|
+
tally(tierOneOutcomes, outcome);
|
|
36767
|
+
continue;
|
|
36768
|
+
}
|
|
36769
|
+
total += 1;
|
|
36770
|
+
range.add(entry.ts);
|
|
36771
|
+
tally(outcomes, outcome);
|
|
36772
|
+
tally(reasons, readString(metadata, "write_gate_reason") ?? "unknown");
|
|
36773
|
+
}
|
|
36774
|
+
return {
|
|
36775
|
+
scanned: entries.length,
|
|
36776
|
+
window: window.value(),
|
|
36777
|
+
total,
|
|
36778
|
+
outcomes,
|
|
36779
|
+
reasons,
|
|
36780
|
+
tierOne: { total: tierOneTotal, outcomes: tierOneOutcomes },
|
|
36781
|
+
range: range.value()
|
|
36782
|
+
};
|
|
36783
|
+
}
|
|
36784
|
+
var GATE_OUTCOMES, GATE_REASONS;
|
|
36785
|
+
var init_write_gate_summary = __esm(() => {
|
|
36786
|
+
GATE_OUTCOMES = ["allowed", "declined", "refused"];
|
|
36787
|
+
GATE_REASONS = [
|
|
36788
|
+
"no_where",
|
|
36789
|
+
"ddl_destruction",
|
|
36790
|
+
"unparseable",
|
|
36791
|
+
"multiple_statements",
|
|
36792
|
+
"non_unique_where",
|
|
36793
|
+
"multi_table",
|
|
36794
|
+
"nested_write"
|
|
36795
|
+
];
|
|
36796
|
+
});
|
|
36797
|
+
|
|
36151
36798
|
// src/commands/audit.ts
|
|
36152
36799
|
var exports_audit = {};
|
|
36153
36800
|
__export(exports_audit, {
|
|
@@ -36333,6 +36980,53 @@ function readLineFromStdinWithStderrPrompt(prompt) {
|
|
|
36333
36980
|
process.stdin.resume();
|
|
36334
36981
|
});
|
|
36335
36982
|
}
|
|
36983
|
+
function formatOutcomeCounts(outcomes) {
|
|
36984
|
+
const seen = Object.entries(outcomes).filter(([, count]) => count > 0);
|
|
36985
|
+
if (seen.length === 0)
|
|
36986
|
+
return MISSING_PLACEHOLDER;
|
|
36987
|
+
return seen.map(([name, count]) => `${name} ${count}`).join(", ");
|
|
36988
|
+
}
|
|
36989
|
+
function renderWriteGateSummary(summary, connections) {
|
|
36990
|
+
const lines = [];
|
|
36991
|
+
lines.push(t_vars("audit.write_gate.header", {
|
|
36992
|
+
conn: connections.length > 0 ? connections.join(", ") : MISSING_PLACEHOLDER
|
|
36993
|
+
}));
|
|
36994
|
+
if (summary.window) {
|
|
36995
|
+
lines.push(t_vars("audit.write_gate.window", {
|
|
36996
|
+
scanned: String(summary.scanned),
|
|
36997
|
+
from: summary.window.from,
|
|
36998
|
+
to: summary.window.to
|
|
36999
|
+
}));
|
|
37000
|
+
}
|
|
37001
|
+
if (summary.total === 0) {
|
|
37002
|
+
lines.push("");
|
|
37003
|
+
lines.push(summary.scanned === 0 ? t("audit.write_gate.no_entries") : t("audit.write_gate.no_decisions"));
|
|
37004
|
+
if (summary.tierOne.total > 0) {
|
|
37005
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37006
|
+
count: String(summary.tierOne.total),
|
|
37007
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37008
|
+
}));
|
|
37009
|
+
}
|
|
37010
|
+
return lines.join(`
|
|
37011
|
+
`);
|
|
37012
|
+
}
|
|
37013
|
+
lines.push(t_vars("audit.write_gate.decisions", {
|
|
37014
|
+
count: String(summary.total),
|
|
37015
|
+
from: summary.range?.from ?? MISSING_PLACEHOLDER,
|
|
37016
|
+
to: summary.range?.to ?? MISSING_PLACEHOLDER
|
|
37017
|
+
}));
|
|
37018
|
+
lines.push("");
|
|
37019
|
+
lines.push(renderTable(Object.entries(summary.outcomes).map(([name, count]) => [name, String(count)]), ["outcome", "count"]));
|
|
37020
|
+
lines.push("");
|
|
37021
|
+
lines.push(renderTable(Object.entries(summary.reasons).map(([name, count]) => [name, String(count)]), ["reason", "count"]));
|
|
37022
|
+
lines.push("");
|
|
37023
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37024
|
+
count: String(summary.tierOne.total),
|
|
37025
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37026
|
+
}));
|
|
37027
|
+
return lines.join(`
|
|
37028
|
+
`);
|
|
37029
|
+
}
|
|
36336
37030
|
async function statAuditFile(file) {
|
|
36337
37031
|
try {
|
|
36338
37032
|
const s = await stat7(file);
|
|
@@ -36352,6 +37046,7 @@ var init_audit = __esm(() => {
|
|
|
36352
37046
|
init_config_binding();
|
|
36353
37047
|
init_reader2();
|
|
36354
37048
|
init_integration_helper();
|
|
37049
|
+
init_write_gate_summary();
|
|
36355
37050
|
ALLOWED_FORMATS14 = ["table", "json"];
|
|
36356
37051
|
auditCommand = new Command("audit").description(t("audit.description"));
|
|
36357
37052
|
auditCommand.command("tail").description(t("audit.tail.description")).option("--n <number>", `Number of recent entries to show (1..${MAX_TAIL_N})`, String(DEFAULT_TAIL_N)).option("--all", "Merge entries across all connections", false).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--brief", "Trim each entry to ts/command/target/success", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (options, command) => {
|
|
@@ -36559,6 +37254,33 @@ var init_audit = __esm(() => {
|
|
|
36559
37254
|
`);
|
|
36560
37255
|
process.exit(0);
|
|
36561
37256
|
});
|
|
37257
|
+
auditCommand.command("write-gate").description(t("audit.write_gate.description")).option("--all", "Summarize across all connections", false).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--for-agent", "Shortcut for --format json", false).action(async (options, command) => {
|
|
37258
|
+
const format = options.forAgent === true ? "json" : options.format;
|
|
37259
|
+
validateFormat(format, ALLOWED_FORMATS14, "audit write-gate");
|
|
37260
|
+
const configPath = resolveConfigPath(command, options);
|
|
37261
|
+
const config = await configModule.read(configPath);
|
|
37262
|
+
if (isAuditDisabled(config))
|
|
37263
|
+
emitDisabledAndExit0();
|
|
37264
|
+
const { auditDir, auditFile, connectionName } = await resolveAuditPaths(configPath, config);
|
|
37265
|
+
let entries = [];
|
|
37266
|
+
let connections = [connectionName];
|
|
37267
|
+
if (options.all === true) {
|
|
37268
|
+
const discovered = await discoverConnections(auditDir);
|
|
37269
|
+
connections = discovered.map((c) => c.connection);
|
|
37270
|
+
for (const c of discovered) {
|
|
37271
|
+
for (const file of c.files)
|
|
37272
|
+
entries.push(...await readEntries(file));
|
|
37273
|
+
}
|
|
37274
|
+
} else {
|
|
37275
|
+
entries = await readEntries(auditFile, { include_rotated: true });
|
|
37276
|
+
}
|
|
37277
|
+
const summary = summarizeWriteGate(entries);
|
|
37278
|
+
if (format === "json") {
|
|
37279
|
+
console.log(JSON.stringify({ connections, summary }, null, 2));
|
|
37280
|
+
return;
|
|
37281
|
+
}
|
|
37282
|
+
console.log(renderWriteGateSummary(summary, connections));
|
|
37283
|
+
});
|
|
36562
37284
|
auditCommand.command("health").description(t("audit.health.description")).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--brief", "Trim to enabled / lastWrite / rotationUsage", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (options, command) => {
|
|
36563
37285
|
const forAgent = options.forAgent === true;
|
|
36564
37286
|
const format = forAgent ? "json" : options.format;
|
|
@@ -38623,10 +39345,10 @@ async function loadSchemaContext(dbcliPath, connectionName) {
|
|
|
38623
39345
|
const loader3 = new SchemaLayeredLoader(dbcliPath, { connectionName });
|
|
38624
39346
|
const { cache, index } = await loader3.initialize();
|
|
38625
39347
|
const schema = {};
|
|
38626
|
-
for (const
|
|
38627
|
-
const table = await cache.getTableSchema(
|
|
39348
|
+
for (const tableName2 of Object.keys(index?.tables ?? {})) {
|
|
39349
|
+
const table = await cache.getTableSchema(tableName2);
|
|
38628
39350
|
if (table) {
|
|
38629
|
-
schema[
|
|
39351
|
+
schema[tableName2] = table;
|
|
38630
39352
|
}
|
|
38631
39353
|
}
|
|
38632
39354
|
return buildSchemaContext(schema);
|
|
@@ -40062,9 +40784,9 @@ var init_select_star = __esm(() => {
|
|
|
40062
40784
|
span: findingSpan(ctx.sql, "select *"),
|
|
40063
40785
|
schemaVerified: false
|
|
40064
40786
|
};
|
|
40065
|
-
const
|
|
40066
|
-
if (ctx.schema.available &&
|
|
40067
|
-
const table = ctx.schema.getTable(
|
|
40787
|
+
const tableName2 = singlePhysicalTable(ctx.ast);
|
|
40788
|
+
if (ctx.schema.available && tableName2 && isSoleUnqualifiedStar(ctx.ast)) {
|
|
40789
|
+
const table = ctx.schema.getTable(tableName2);
|
|
40068
40790
|
if (table && table.columns.length > 0) {
|
|
40069
40791
|
if (wildcardIndexes.length === 1) {
|
|
40070
40792
|
const wildcardIndex = wildcardIndexes[0];
|
|
@@ -40973,17 +41695,17 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
|
|
|
40973
41695
|
function sha2563(value) {
|
|
40974
41696
|
return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
|
|
40975
41697
|
}
|
|
40976
|
-
function verdictFingerprint(
|
|
40977
|
-
if (!record3(
|
|
41698
|
+
function verdictFingerprint(verdict2) {
|
|
41699
|
+
if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
|
|
40978
41700
|
throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
|
|
40979
41701
|
}
|
|
40980
|
-
const bits =
|
|
41702
|
+
const bits = verdict2.checks.map((check) => {
|
|
40981
41703
|
if (!record3(check) || Object.keys(check).some((key) => key !== "pass") || typeof check.pass !== "boolean") {
|
|
40982
41704
|
throw new EvidenceReceiptValidationError("verdict checks must contain only pass bits");
|
|
40983
41705
|
}
|
|
40984
41706
|
return check.pass;
|
|
40985
41707
|
});
|
|
40986
|
-
return sha2563(JSON.stringify({ pass:
|
|
41708
|
+
return sha2563(JSON.stringify({ pass: verdict2.pass, checks: bits }));
|
|
40987
41709
|
}
|
|
40988
41710
|
function verificationOutcomeFingerprint(status2, artifactPersisted) {
|
|
40989
41711
|
if (!isVerificationStatus(status2)) {
|
|
@@ -41281,7 +42003,7 @@ var init_assert = __esm(() => {
|
|
|
41281
42003
|
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
|
|
41282
42004
|
await adapter.connect();
|
|
41283
42005
|
let auditRef = null;
|
|
41284
|
-
let
|
|
42006
|
+
let verdict2;
|
|
41285
42007
|
try {
|
|
41286
42008
|
const blacklistManager = new BlacklistManager(config);
|
|
41287
42009
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
@@ -41308,9 +42030,9 @@ var init_assert = __esm(() => {
|
|
|
41308
42030
|
});
|
|
41309
42031
|
checks.push(...compareAgainst(current, baseline, options.tolerance));
|
|
41310
42032
|
}
|
|
41311
|
-
|
|
42033
|
+
verdict2 = { pass: checks.every((c) => c.pass), checks };
|
|
41312
42034
|
auditRef = await writeAuditEntry(config, "assert", options, {
|
|
41313
|
-
success:
|
|
42035
|
+
success: verdict2.pass,
|
|
41314
42036
|
sql: query
|
|
41315
42037
|
});
|
|
41316
42038
|
} finally {
|
|
@@ -41320,7 +42042,7 @@ var init_assert = __esm(() => {
|
|
|
41320
42042
|
if (options.writeVerificationArtifact === true && verificationSubject) {
|
|
41321
42043
|
try {
|
|
41322
42044
|
const artifact2 = buildAssertVerificationArtifact({
|
|
41323
|
-
verdict,
|
|
42045
|
+
verdict: verdict2,
|
|
41324
42046
|
subject: verificationSubject,
|
|
41325
42047
|
summary: options.verificationSummary,
|
|
41326
42048
|
argv: process.argv,
|
|
@@ -41339,7 +42061,7 @@ var init_assert = __esm(() => {
|
|
|
41339
42061
|
context: await buildEvidenceReceiptContext(config, process.cwd()),
|
|
41340
42062
|
auditRef,
|
|
41341
42063
|
verificationArtifactRef: verificationArtifactPath ? basename7(verificationArtifactPath) : null,
|
|
41342
|
-
verdict
|
|
42064
|
+
verdict: verdict2
|
|
41343
42065
|
});
|
|
41344
42066
|
evidenceReceiptPath = await writeEvidenceReceipt(process.cwd(), options.evidenceReceipt, receipt);
|
|
41345
42067
|
} catch {
|
|
@@ -41349,23 +42071,23 @@ var init_assert = __esm(() => {
|
|
|
41349
42071
|
}
|
|
41350
42072
|
if (options.format === "json") {
|
|
41351
42073
|
console.log(JSON.stringify({
|
|
41352
|
-
...
|
|
42074
|
+
...verdict2,
|
|
41353
42075
|
...verificationArtifactPath ? { verificationArtifactPath } : {},
|
|
41354
42076
|
...evidenceReceiptPath ? { evidenceReceiptPath } : {}
|
|
41355
42077
|
}, null, 2));
|
|
41356
42078
|
} else {
|
|
41357
|
-
for (const c of
|
|
42079
|
+
for (const c of verdict2.checks) {
|
|
41358
42080
|
console.log(`${c.pass ? "PASS" : "FAIL"} ${c.name} expected=${c.expected} actual=${c.actual}`);
|
|
41359
42081
|
}
|
|
41360
42082
|
console.log(`
|
|
41361
|
-
Verdict: ${
|
|
42083
|
+
Verdict: ${verdict2.pass ? "PASS" : "FAIL"}`);
|
|
41362
42084
|
if (verificationArtifactPath) {
|
|
41363
42085
|
console.log(`Verification artifact: ${verificationArtifactPath}`);
|
|
41364
42086
|
}
|
|
41365
42087
|
if (evidenceReceiptPath)
|
|
41366
42088
|
console.log(`Evidence receipt: ${evidenceReceiptPath}`);
|
|
41367
42089
|
}
|
|
41368
|
-
process.exit(
|
|
42090
|
+
process.exit(verdict2.pass || options.fail === false ? 0 : 1);
|
|
41369
42091
|
} catch (error) {
|
|
41370
42092
|
if (error instanceof Error) {
|
|
41371
42093
|
console.error(error.message);
|
|
@@ -43849,6 +44571,9 @@ function classifyInput(raw) {
|
|
|
43849
44571
|
return { type: "meta", raw, normalized: normalized2 };
|
|
43850
44572
|
}
|
|
43851
44573
|
}
|
|
44574
|
+
if (normalized2.startsWith(COMMAND_PREFIX)) {
|
|
44575
|
+
return { type: "command", raw, normalized: normalized2 };
|
|
44576
|
+
}
|
|
43852
44577
|
const firstWord = (normalized2.split(/\s+/)[0] ?? "").toUpperCase();
|
|
43853
44578
|
if (sqlKeywordSet.has(firstWord)) {
|
|
43854
44579
|
return { type: "sql", raw, normalized: normalized2 };
|
|
@@ -43858,7 +44583,7 @@ function classifyInput(raw) {
|
|
|
43858
44583
|
}
|
|
43859
44584
|
return { type: "command", raw, normalized: normalized2 };
|
|
43860
44585
|
}
|
|
43861
|
-
var META_PREFIX = ".", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
44586
|
+
var META_PREFIX = ".", COMMAND_PREFIX = "\\", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
43862
44587
|
var init_input_classifier = __esm(() => {
|
|
43863
44588
|
init_types11();
|
|
43864
44589
|
sqlKeywordSet = new Set(SQL_KEYWORDS_FOR_DETECTION);
|
|
@@ -43882,6 +44607,10 @@ class MultilineBuffer {
|
|
|
43882
44607
|
isActive() {
|
|
43883
44608
|
return this.lines.length > 0;
|
|
43884
44609
|
}
|
|
44610
|
+
isInsideLiteral() {
|
|
44611
|
+
return insideLiteral(this.lines.join(`
|
|
44612
|
+
`));
|
|
44613
|
+
}
|
|
43885
44614
|
getPartial() {
|
|
43886
44615
|
return this.lines.join(`
|
|
43887
44616
|
`);
|
|
@@ -43891,9 +44620,17 @@ class MultilineBuffer {
|
|
|
43891
44620
|
}
|
|
43892
44621
|
}
|
|
43893
44622
|
function hasUnquotedSemicolon(sql) {
|
|
44623
|
+
return scan(sql).semicolon;
|
|
44624
|
+
}
|
|
44625
|
+
function insideLiteral(sql) {
|
|
44626
|
+
const { inSingleQuote, inDoubleQuote } = scan(sql);
|
|
44627
|
+
return inSingleQuote || inDoubleQuote;
|
|
44628
|
+
}
|
|
44629
|
+
function scan(sql) {
|
|
43894
44630
|
let inSingleQuote = false;
|
|
43895
44631
|
let inDoubleQuote = false;
|
|
43896
44632
|
let escaped = false;
|
|
44633
|
+
let semicolon = false;
|
|
43897
44634
|
for (let i = 0;i < sql.length; i++) {
|
|
43898
44635
|
const ch = sql[i];
|
|
43899
44636
|
if (escaped) {
|
|
@@ -43913,10 +44650,10 @@ function hasUnquotedSemicolon(sql) {
|
|
|
43913
44650
|
continue;
|
|
43914
44651
|
}
|
|
43915
44652
|
if (ch === ";" && !inSingleQuote && !inDoubleQuote) {
|
|
43916
|
-
|
|
44653
|
+
semicolon = true;
|
|
43917
44654
|
}
|
|
43918
44655
|
}
|
|
43919
|
-
return
|
|
44656
|
+
return { semicolon, inSingleQuote, inDoubleQuote };
|
|
43920
44657
|
}
|
|
43921
44658
|
|
|
43922
44659
|
// src/core/repl/meta-commands.ts
|
|
@@ -43960,7 +44697,8 @@ function helpText() {
|
|
|
43960
44697
|
"",
|
|
43961
44698
|
import_picocolors2.default.bold("Usage:"),
|
|
43962
44699
|
` SQL statements Execute directly (end with ${import_picocolors2.default.cyan(";")})`,
|
|
43963
|
-
` dbcli commands Run without ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44700
|
+
` dbcli commands Run without the ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44701
|
+
` ${import_picocolors2.default.cyan("\\<command>")} Force the dbcli subcommand \u2014 required for ${import_picocolors2.default.dim("delete")}, ${import_picocolors2.default.dim("update")}, ${import_picocolors2.default.dim("insert")} and other SQL keywords (e.g. ${import_picocolors2.default.cyan("\\delete users --where id=1")})`,
|
|
43964
44702
|
` Ctrl+D Exit`
|
|
43965
44703
|
];
|
|
43966
44704
|
return lines.join(`
|
|
@@ -44064,24 +44802,26 @@ function splitRespectingQuotes(input) {
|
|
|
44064
44802
|
let current = "";
|
|
44065
44803
|
let inDoubleQuote = false;
|
|
44066
44804
|
let inSingleQuote = false;
|
|
44805
|
+
let quoted = false;
|
|
44067
44806
|
for (let i = 0;i < input.length; i++) {
|
|
44068
44807
|
const ch = input[i];
|
|
44069
44808
|
if (ch === '"' && !inSingleQuote) {
|
|
44070
44809
|
inDoubleQuote = !inDoubleQuote;
|
|
44071
|
-
|
|
44810
|
+
quoted = true;
|
|
44072
44811
|
} else if (ch === "'" && !inDoubleQuote) {
|
|
44073
44812
|
inSingleQuote = !inSingleQuote;
|
|
44074
|
-
|
|
44813
|
+
quoted = true;
|
|
44075
44814
|
} else if (ch === " " && !inDoubleQuote && !inSingleQuote) {
|
|
44076
|
-
if (current.length > 0) {
|
|
44815
|
+
if (current.length > 0 || quoted) {
|
|
44077
44816
|
tokens.push(current);
|
|
44078
44817
|
current = "";
|
|
44818
|
+
quoted = false;
|
|
44079
44819
|
}
|
|
44080
44820
|
} else {
|
|
44081
44821
|
current += ch;
|
|
44082
44822
|
}
|
|
44083
44823
|
}
|
|
44084
|
-
if (current.length > 0) {
|
|
44824
|
+
if (current.length > 0 || quoted) {
|
|
44085
44825
|
tokens.push(current);
|
|
44086
44826
|
}
|
|
44087
44827
|
return tokens;
|
|
@@ -44147,14 +44887,16 @@ var init_history_manager = () => {};
|
|
|
44147
44887
|
class ReplEngine {
|
|
44148
44888
|
adapter;
|
|
44149
44889
|
context;
|
|
44890
|
+
writeGate;
|
|
44150
44891
|
state;
|
|
44151
44892
|
buffer;
|
|
44152
44893
|
history;
|
|
44153
44894
|
formatter;
|
|
44154
44895
|
config;
|
|
44155
|
-
constructor(adapter, context, historyPath, config = null) {
|
|
44896
|
+
constructor(adapter, context, historyPath, config = null, writeGate = null) {
|
|
44156
44897
|
this.adapter = adapter;
|
|
44157
44898
|
this.context = context;
|
|
44899
|
+
this.writeGate = writeGate;
|
|
44158
44900
|
this.state = { format: "table", timing: false, connected: true, noLimit: false };
|
|
44159
44901
|
this.buffer = new MultilineBuffer;
|
|
44160
44902
|
this.history = new HistoryManager(historyPath);
|
|
@@ -44167,6 +44909,9 @@ class ReplEngine {
|
|
|
44167
44909
|
isMultiline() {
|
|
44168
44910
|
return this.buffer.isActive();
|
|
44169
44911
|
}
|
|
44912
|
+
cancelMultiline() {
|
|
44913
|
+
this.buffer.reset();
|
|
44914
|
+
}
|
|
44170
44915
|
getHistory() {
|
|
44171
44916
|
return this.history.getAll();
|
|
44172
44917
|
}
|
|
@@ -44175,6 +44920,13 @@ class ReplEngine {
|
|
|
44175
44920
|
}
|
|
44176
44921
|
async processInput(line) {
|
|
44177
44922
|
if (this.buffer.isActive()) {
|
|
44923
|
+
const classified2 = classifyInput(line);
|
|
44924
|
+
if (classified2.type === "meta" && !this.buffer.isInsideLiteral()) {
|
|
44925
|
+
const result = this.handleMeta(classified2.normalized);
|
|
44926
|
+
if (result.action === "quit" || result.action === "clear")
|
|
44927
|
+
this.buffer.reset();
|
|
44928
|
+
return result;
|
|
44929
|
+
}
|
|
44178
44930
|
const bufResult = this.buffer.append(line);
|
|
44179
44931
|
if (bufResult.complete) {
|
|
44180
44932
|
this.history.add(bufResult.sql);
|
|
@@ -44211,14 +44963,27 @@ class ReplEngine {
|
|
|
44211
44963
|
this.history.add(bufResult.sql);
|
|
44212
44964
|
return this.executeSql(bufResult.sql);
|
|
44213
44965
|
}
|
|
44214
|
-
|
|
44966
|
+
const hint = this.subcommandHint(input);
|
|
44967
|
+
return hint ? { action: "multiline", output: hint } : { action: "multiline" };
|
|
44968
|
+
}
|
|
44969
|
+
subcommandHint(input) {
|
|
44970
|
+
const words = input.trim().split(/\s+/);
|
|
44971
|
+
const firstWord = (words[0] ?? "").toLowerCase();
|
|
44972
|
+
if (!this.context.commandNames.includes(firstWord))
|
|
44973
|
+
return;
|
|
44974
|
+
if (!/\s--[a-z]/.test(input))
|
|
44975
|
+
return;
|
|
44976
|
+
if (STATEMENT_CONTINUATION.has((words[1] ?? "").toUpperCase()))
|
|
44977
|
+
return;
|
|
44978
|
+
return import_picocolors3.default.dim(t_vars("shell.subcommand_prefix_hint", { command: `${COMMAND_PREFIX}${firstWord}` }));
|
|
44215
44979
|
}
|
|
44216
44980
|
async handleCommand(input) {
|
|
44217
|
-
const
|
|
44981
|
+
const invocation = input.startsWith(COMMAND_PREFIX) ? input.slice(COMMAND_PREFIX.length) : input;
|
|
44982
|
+
const parsed = parseCommandLine(invocation);
|
|
44218
44983
|
if (!isKnownCommand(parsed.command, this.context.commandNames)) {
|
|
44219
44984
|
if (this.context.system === "redis") {
|
|
44220
44985
|
this.history.add(input);
|
|
44221
|
-
return this.executeSql(
|
|
44986
|
+
return this.executeSql(invocation);
|
|
44222
44987
|
}
|
|
44223
44988
|
return {
|
|
44224
44989
|
action: "continue",
|
|
@@ -44228,11 +44993,11 @@ class ReplEngine {
|
|
|
44228
44993
|
this.history.add(input);
|
|
44229
44994
|
try {
|
|
44230
44995
|
const cliPath = new URL("../../cli.ts", import.meta.url).pathname;
|
|
44231
|
-
const argv = [
|
|
44996
|
+
const argv = ["--config", this.context.configPath, parsed.command, ...parsed.args];
|
|
44232
44997
|
const proc = Bun.spawn(["bun", "run", cliPath, ...argv], {
|
|
44233
44998
|
stdout: "pipe",
|
|
44234
44999
|
stderr: "pipe",
|
|
44235
|
-
env: { ...process.env }
|
|
45000
|
+
env: { ...process.env, DBCLI_SHELL_SUBCOMMAND: "1" }
|
|
44236
45001
|
});
|
|
44237
45002
|
const [stdoutBuf, stderrBuf] = await Promise.all([
|
|
44238
45003
|
new Response(proc.stdout).text(),
|
|
@@ -44288,6 +45053,11 @@ class ReplEngine {
|
|
|
44288
45053
|
};
|
|
44289
45054
|
}
|
|
44290
45055
|
}
|
|
45056
|
+
if (this.writeGate && !await this.writeGate(sql))
|
|
45057
|
+
return { action: "continue" };
|
|
45058
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
45059
|
+
}
|
|
45060
|
+
async runStatement(sql, blacklistValidator, referencedTables) {
|
|
44291
45061
|
const startTime = Date.now();
|
|
44292
45062
|
try {
|
|
44293
45063
|
const result = await this.adapter.execute(sql, undefined, {
|
|
@@ -44323,7 +45093,7 @@ class ReplEngine {
|
|
|
44323
45093
|
await this.adapter.connect();
|
|
44324
45094
|
this.state = { ...this.state, connected: true };
|
|
44325
45095
|
console.error(import_picocolors3.default.green(t("shell.error_reconnect_success")));
|
|
44326
|
-
return this.
|
|
45096
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
44327
45097
|
} catch (reconnectError) {
|
|
44328
45098
|
return {
|
|
44329
45099
|
action: "continue",
|
|
@@ -44354,7 +45124,7 @@ class ReplEngine {
|
|
|
44354
45124
|
return isTransportFailure(error);
|
|
44355
45125
|
}
|
|
44356
45126
|
}
|
|
44357
|
-
var import_picocolors3;
|
|
45127
|
+
var import_picocolors3, STATEMENT_CONTINUATION;
|
|
44358
45128
|
var init_repl_engine = __esm(() => {
|
|
44359
45129
|
init_connection_error_message();
|
|
44360
45130
|
init_input_classifier();
|
|
@@ -44368,6 +45138,15 @@ var init_repl_engine = __esm(() => {
|
|
|
44368
45138
|
init_blacklist_validator();
|
|
44369
45139
|
init_blacklist();
|
|
44370
45140
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
45141
|
+
STATEMENT_CONTINUATION = new Set([
|
|
45142
|
+
"FROM",
|
|
45143
|
+
"INTO",
|
|
45144
|
+
"SET",
|
|
45145
|
+
"TABLE",
|
|
45146
|
+
"INDEX",
|
|
45147
|
+
"VIEW",
|
|
45148
|
+
"DATABASE"
|
|
45149
|
+
]);
|
|
44371
45150
|
});
|
|
44372
45151
|
|
|
44373
45152
|
// src/core/repl/completer.ts
|
|
@@ -44399,9 +45178,9 @@ function createCompleter(ctx) {
|
|
|
44399
45178
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44400
45179
|
const tableHits = matchWithSuffix(allTableNames, lastWord);
|
|
44401
45180
|
const colHits = isColumnPosition(upperLine) ? (() => {
|
|
44402
|
-
const
|
|
44403
|
-
if (
|
|
44404
|
-
return matchWithSuffix([...ctx.columnsByTable[
|
|
45181
|
+
const tableName2 = extractTableFromLine(trimmed, allTableNames);
|
|
45182
|
+
if (tableName2 && ctx.columnsByTable[tableName2]) {
|
|
45183
|
+
return matchWithSuffix([...ctx.columnsByTable[tableName2]], lastWord);
|
|
44405
45184
|
}
|
|
44406
45185
|
return matchWithSuffix(uniqueColumns, lastWord);
|
|
44407
45186
|
})() : [[], lastWord];
|
|
@@ -44410,6 +45189,11 @@ function createCompleter(ctx) {
|
|
|
44410
45189
|
}
|
|
44411
45190
|
const words = trimmed.split(/\s+/);
|
|
44412
45191
|
if (words.length === 1) {
|
|
45192
|
+
if (lastWord.startsWith(COMMAND_PREFIX)) {
|
|
45193
|
+
const bare = lastWord.slice(COMMAND_PREFIX.length);
|
|
45194
|
+
const [hits] = matchWithSuffix([...ctx.commandNames], bare);
|
|
45195
|
+
return [hits.map((hit) => `${COMMAND_PREFIX}${hit}`), lastWord];
|
|
45196
|
+
}
|
|
44413
45197
|
const cmdHits = matchWithSuffix([...ctx.commandNames], lastWord);
|
|
44414
45198
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44415
45199
|
return [[...cmdHits[0], ...sqlHits[0]], lastWord];
|
|
@@ -44463,6 +45247,7 @@ function matchWithSuffix(candidates, prefix) {
|
|
|
44463
45247
|
var REDIS_COMMANDS, TABLE_POSITION_KEYWORDS, COMMANDS_TAKING_TABLE_ARG;
|
|
44464
45248
|
var init_completer = __esm(() => {
|
|
44465
45249
|
init_types11();
|
|
45250
|
+
init_input_classifier();
|
|
44466
45251
|
init_command_metadata();
|
|
44467
45252
|
REDIS_COMMANDS = Object.keys(REDIS_COMMAND_TABLE);
|
|
44468
45253
|
TABLE_POSITION_KEYWORDS = new Set([
|
|
@@ -44760,6 +45545,94 @@ var init_command_registry = __esm(() => {
|
|
|
44760
45545
|
REPL_DENYLIST = new Set(["shell"]);
|
|
44761
45546
|
});
|
|
44762
45547
|
|
|
45548
|
+
// src/commands/shell-prompt-asker.ts
|
|
45549
|
+
var exports_shell_prompt_asker = {};
|
|
45550
|
+
__export(exports_shell_prompt_asker, {
|
|
45551
|
+
createPromptAsker: () => createPromptAsker
|
|
45552
|
+
});
|
|
45553
|
+
function createPromptAsker(getInterface) {
|
|
45554
|
+
let pending = null;
|
|
45555
|
+
return {
|
|
45556
|
+
async ask(question) {
|
|
45557
|
+
const rl = getInterface();
|
|
45558
|
+
if (!rl)
|
|
45559
|
+
throw new Error("Write gate asked for confirmation before the shell started");
|
|
45560
|
+
return new Promise((resolve10) => {
|
|
45561
|
+
const controller = new AbortController;
|
|
45562
|
+
const settle = (answer) => {
|
|
45563
|
+
pending = null;
|
|
45564
|
+
rl.pause();
|
|
45565
|
+
resolve10(answer);
|
|
45566
|
+
};
|
|
45567
|
+
pending = {
|
|
45568
|
+
abort: () => {
|
|
45569
|
+
controller.abort();
|
|
45570
|
+
settle(null);
|
|
45571
|
+
}
|
|
45572
|
+
};
|
|
45573
|
+
try {
|
|
45574
|
+
rl.question(`${question}: `, { signal: controller.signal }, settle);
|
|
45575
|
+
} catch (error) {
|
|
45576
|
+
pending = null;
|
|
45577
|
+
throw error;
|
|
45578
|
+
}
|
|
45579
|
+
});
|
|
45580
|
+
},
|
|
45581
|
+
cancel() {
|
|
45582
|
+
if (!pending)
|
|
45583
|
+
return false;
|
|
45584
|
+
pending.abort();
|
|
45585
|
+
return true;
|
|
45586
|
+
}
|
|
45587
|
+
};
|
|
45588
|
+
}
|
|
45589
|
+
|
|
45590
|
+
// src/commands/shell-write-gate.ts
|
|
45591
|
+
var exports_shell_write_gate = {};
|
|
45592
|
+
__export(exports_shell_write_gate, {
|
|
45593
|
+
createShellWriteGate: () => createShellWriteGate
|
|
45594
|
+
});
|
|
45595
|
+
function createShellWriteGate(options) {
|
|
45596
|
+
const record4 = options.record ?? ((decision) => recordGateDecision({
|
|
45597
|
+
config: options.config,
|
|
45598
|
+
command: "shell",
|
|
45599
|
+
options: { config: options.configPath },
|
|
45600
|
+
verdict: decision.verdict,
|
|
45601
|
+
outcome: decision.outcome,
|
|
45602
|
+
tier: decision.tier,
|
|
45603
|
+
sql: decision.sql
|
|
45604
|
+
}));
|
|
45605
|
+
return async (sql) => {
|
|
45606
|
+
const verdict2 = await classifySqlWriteGate(sql, { dialect: options.dialect });
|
|
45607
|
+
if (verdict2.tier !== "two")
|
|
45608
|
+
return true;
|
|
45609
|
+
const decide5 = (outcome) => record4({ verdict: verdict2, outcome, tier: "two", sql });
|
|
45610
|
+
try {
|
|
45611
|
+
const proceed = await enforceWriteGate({
|
|
45612
|
+
verdict: verdict2,
|
|
45613
|
+
statement: sql,
|
|
45614
|
+
operation: verdict2.operation ?? "WRITE",
|
|
45615
|
+
human: { isTTY: process.stdin.isTTY === true },
|
|
45616
|
+
...options.ask && { ask: options.ask }
|
|
45617
|
+
});
|
|
45618
|
+
await decide5(proceed ? "allowed" : "declined");
|
|
45619
|
+
return proceed;
|
|
45620
|
+
} catch (error) {
|
|
45621
|
+
if (!(error instanceof WriteGateRefusal))
|
|
45622
|
+
throw error;
|
|
45623
|
+
await decide5("refused");
|
|
45624
|
+
process.stderr.write(`${error.message}
|
|
45625
|
+
`);
|
|
45626
|
+
return false;
|
|
45627
|
+
}
|
|
45628
|
+
};
|
|
45629
|
+
}
|
|
45630
|
+
var init_shell_write_gate = __esm(() => {
|
|
45631
|
+
init_write_gate();
|
|
45632
|
+
init_write_gate_prompt();
|
|
45633
|
+
init_write_gate_guard();
|
|
45634
|
+
});
|
|
45635
|
+
|
|
44763
45636
|
// src/commands/shell.ts
|
|
44764
45637
|
var exports_shell = {};
|
|
44765
45638
|
__export(exports_shell, {
|
|
@@ -44866,7 +45739,18 @@ async function runShell(options, configPath) {
|
|
|
44866
45739
|
columnsByTable,
|
|
44867
45740
|
commandNames
|
|
44868
45741
|
};
|
|
44869
|
-
|
|
45742
|
+
let replInterface = null;
|
|
45743
|
+
const { createPromptAsker: createPromptAsker2 } = await Promise.resolve().then(() => exports_shell_prompt_asker);
|
|
45744
|
+
const asker = createPromptAsker2(() => replInterface);
|
|
45745
|
+
const { toSqlDialect: toSqlDialect2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
45746
|
+
const dialect = toSqlDialect2(config.connection.system);
|
|
45747
|
+
const writeGate = dialect ? (await Promise.resolve().then(() => (init_shell_write_gate(), exports_shell_write_gate))).createShellWriteGate({
|
|
45748
|
+
config,
|
|
45749
|
+
configPath,
|
|
45750
|
+
dialect,
|
|
45751
|
+
ask: asker.ask
|
|
45752
|
+
}) : null;
|
|
45753
|
+
const engine = new ReplEngine(adapter, context, HISTORY_PATH, config, writeGate);
|
|
44870
45754
|
const complete = createCompleter(context);
|
|
44871
45755
|
console.error(import_picocolors5.default.bold(t_vars("shell.welcome", {
|
|
44872
45756
|
system: config.connection.system,
|
|
@@ -44900,10 +45784,22 @@ async function runShell(options, configPath) {
|
|
|
44900
45784
|
completer: (line) => complete(line),
|
|
44901
45785
|
terminal: process.stdin.isTTY ?? false
|
|
44902
45786
|
});
|
|
45787
|
+
replInterface = rl;
|
|
44903
45788
|
const continuationPrompt = import_picocolors5.default.dim(t("shell.continuation_prompt") + "> ");
|
|
44904
45789
|
rl.prompt();
|
|
44905
|
-
|
|
44906
|
-
|
|
45790
|
+
const handleLine = async (line) => {
|
|
45791
|
+
rl.pause();
|
|
45792
|
+
let result;
|
|
45793
|
+
try {
|
|
45794
|
+
result = await engine.processInput(line);
|
|
45795
|
+
} catch (error) {
|
|
45796
|
+
console.error(import_picocolors5.default.red(t_vars("shell.error_sql_failed", { message: error.message })));
|
|
45797
|
+
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45798
|
+
rl.prompt();
|
|
45799
|
+
return;
|
|
45800
|
+
} finally {
|
|
45801
|
+
rl.resume();
|
|
45802
|
+
}
|
|
44907
45803
|
switch (result.action) {
|
|
44908
45804
|
case "quit":
|
|
44909
45805
|
if (result.output)
|
|
@@ -44914,16 +45810,21 @@ async function runShell(options, configPath) {
|
|
|
44914
45810
|
console.clear();
|
|
44915
45811
|
break;
|
|
44916
45812
|
case "multiline":
|
|
44917
|
-
|
|
45813
|
+
if (result.output)
|
|
45814
|
+
console.error(result.output);
|
|
44918
45815
|
break;
|
|
44919
45816
|
case "continue":
|
|
44920
45817
|
if (result.output) {
|
|
44921
45818
|
console.log(result.output);
|
|
44922
45819
|
}
|
|
44923
|
-
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
44924
45820
|
break;
|
|
44925
45821
|
}
|
|
45822
|
+
rl.setPrompt(engine.isMultiline() ? continuationPrompt : import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
44926
45823
|
rl.prompt();
|
|
45824
|
+
};
|
|
45825
|
+
let pending = Promise.resolve();
|
|
45826
|
+
rl.on("line", (line) => {
|
|
45827
|
+
pending = pending.then(() => handleLine(line));
|
|
44927
45828
|
});
|
|
44928
45829
|
rl.on("close", async () => {
|
|
44929
45830
|
console.error(import_picocolors5.default.dim(t("shell.goodbye")));
|
|
@@ -44932,7 +45833,10 @@ async function runShell(options, configPath) {
|
|
|
44932
45833
|
process.exit(0);
|
|
44933
45834
|
});
|
|
44934
45835
|
rl.on("SIGINT", () => {
|
|
45836
|
+
if (asker.cancel())
|
|
45837
|
+
return;
|
|
44935
45838
|
if (engine.isMultiline()) {
|
|
45839
|
+
engine.cancelMultiline();
|
|
44936
45840
|
console.error(import_picocolors5.default.dim(t("shell.multiline_cancelled")));
|
|
44937
45841
|
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
44938
45842
|
}
|
|
@@ -44948,7 +45852,10 @@ async function runBatchSession(engine, input) {
|
|
|
44948
45852
|
continue;
|
|
44949
45853
|
const result = await engine.processInput(line);
|
|
44950
45854
|
if (result.output) {
|
|
44951
|
-
|
|
45855
|
+
if (result.action === "multiline")
|
|
45856
|
+
console.error(result.output);
|
|
45857
|
+
else
|
|
45858
|
+
console.log(result.output);
|
|
44952
45859
|
}
|
|
44953
45860
|
if (result.action === "quit") {
|
|
44954
45861
|
return;
|