@carllee1983/dbcli 2.0.0 → 3.0.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 +8 -1
- package/.cursor/skills/dbcli/reference.md +114 -9
- package/.github/skills/dbcli/SKILL.md +8 -1
- package/.github/skills/dbcli/reference.md +114 -9
- package/CHANGELOG.md +82 -0
- package/assets/SKILL.md +8 -1
- package/assets/SKILL.zh-TW.md +7 -1
- package/assets/reference.md +114 -9
- package/dist/cli-runtime.mjs +679 -104
- package/dist/cli.mjs +3 -1
- package/dist/core.mjs +34 -2
- package/package.json +3 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +8 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +114 -9
- package/skills/dbcli/SKILL.md +8 -1
- package/skills/dbcli/reference.md +114 -9
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: "
|
|
54
|
+
version: "3.0.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -133,7 +133,9 @@ var init_package = __esm(() => {
|
|
|
133
133
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
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
|
+
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
136
137
|
typecheck: "tsc --noEmit --pretty false",
|
|
138
|
+
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
137
139
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
138
140
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
139
141
|
"format:check": "prettier --check .",
|
|
@@ -8903,6 +8905,15 @@ var init_messages = __esm(() => {
|
|
|
8903
8905
|
health: {
|
|
8904
8906
|
description: "Show AuditLogger health snapshot (size, entries, lock state, last write)"
|
|
8905
8907
|
},
|
|
8908
|
+
write_gate: {
|
|
8909
|
+
description: "Summarize tier-two write-gate decisions (ADR 0010's measurement)",
|
|
8910
|
+
header: "Write-gate summary \u2014 connection: {conn}",
|
|
8911
|
+
window: "Audit entries scanned: {scanned}, {from} \u2192 {to}",
|
|
8912
|
+
decisions: "Tier-two evaluations: {count}, {from} \u2192 {to}",
|
|
8913
|
+
tier_one: "Tier one, counted apart: {count} ({outcomes})",
|
|
8914
|
+
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.",
|
|
8915
|
+
no_entries: "The audit log is empty, so this says nothing about the gate either way. Run some writes, or check `dbcli audit health`."
|
|
8916
|
+
},
|
|
8906
8917
|
disabled_hint: "Audit is disabled (audit.enabled = false in .dbcli). Use 'dbcli audit health' for details.",
|
|
8907
8918
|
no_entries: "No audit entries.",
|
|
8908
8919
|
show_no_match: "No audit entry matches '{prefix}'.",
|
|
@@ -9211,6 +9222,15 @@ var init_messages2 = __esm(() => {
|
|
|
9211
9222
|
health: {
|
|
9212
9223
|
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
9224
|
},
|
|
9225
|
+
write_gate: {
|
|
9226
|
+
description: "\u7D71\u8A08\u7B2C\u4E8C\u7D1A\u5BEB\u5165\u9598\u9580\u7684\u5224\u5B9A\uFF08ADR 0010 \u62BC\u6CE8\u7684\u90A3\u500B\u91CF\u6E2C\uFF09",
|
|
9227
|
+
header: "\u5BEB\u5165\u9598\u9580\u6458\u8981 \u2014 \u9023\u7DDA\uFF1A{conn}",
|
|
9228
|
+
window: "\u6383\u904E\u7684\u7A3D\u6838\u7D00\u9304\uFF1A{scanned} \u7B46\uFF0C{from} \u2192 {to}",
|
|
9229
|
+
decisions: "\u7B2C\u4E8C\u7D1A\u5224\u5B9A\uFF1A{count} \u6B21\uFF0C{from} \u2192 {to}",
|
|
9230
|
+
tier_one: "\u7B2C\u4E00\u7D1A\uFF0C\u5206\u958B\u8A08\u7B97\uFF1A{count} \u6B21\uFF08{outcomes}\uFF09",
|
|
9231
|
+
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",
|
|
9232
|
+
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"
|
|
9233
|
+
},
|
|
9214
9234
|
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
9235
|
no_entries: "\u6C92\u6709 audit \u7D00\u9304\u3002",
|
|
9216
9236
|
show_no_match: "\u627E\u4E0D\u5230\u7B26\u5408 '{prefix}' \u7684 audit \u7D00\u9304\u3002",
|
|
@@ -9422,7 +9442,8 @@ var init_shell = __esm(() => {
|
|
|
9422
9442
|
multiline_cancelled: "Multiline input cancelled.",
|
|
9423
9443
|
timing_display: "Time: {ms}ms",
|
|
9424
9444
|
rows_display: "{count} row(s)",
|
|
9425
|
-
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands."
|
|
9445
|
+
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
|
|
9446
|
+
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
9447
|
};
|
|
9427
9448
|
});
|
|
9428
9449
|
|
|
@@ -9448,7 +9469,8 @@ var init_shell2 = __esm(() => {
|
|
|
9448
9469
|
multiline_cancelled: "\u5DF2\u53D6\u6D88\u591A\u884C\u8F38\u5165\u3002",
|
|
9449
9470
|
timing_display: "\u8017\u6642\uFF1A{ms}ms",
|
|
9450
9471
|
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"
|
|
9472
|
+
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
|
|
9473
|
+
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
9474
|
};
|
|
9453
9475
|
});
|
|
9454
9476
|
|
|
@@ -9482,14 +9504,20 @@ var init_ceremony = __esm(() => {
|
|
|
9482
9504
|
gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
|
|
9483
9505
|
gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
|
|
9484
9506
|
gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
|
|
9507
|
+
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.",
|
|
9508
|
+
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.",
|
|
9485
9509
|
gate_typed_prompt: "Type {phrase} to run it",
|
|
9486
9510
|
gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
|
|
9487
9511
|
gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
|
|
9512
|
+
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}).",
|
|
9488
9513
|
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.",
|
|
9489
9514
|
gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
|
|
9490
9515
|
gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
|
|
9491
9516
|
gate_remedy_multiple_statements: "Run one statement per command.",
|
|
9517
|
+
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.",
|
|
9518
|
+
gate_remedy_nested_write: "Run the nested write as its own statement, or run this one where someone can confirm it.",
|
|
9492
9519
|
gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
|
|
9520
|
+
gate_remedy_in_shell: "Type the statement at the dbcli> prompt instead \u2014 there the shell asks you to type the target table name.",
|
|
9493
9521
|
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9494
9522
|
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9495
9523
|
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
@@ -9526,14 +9554,20 @@ var init_ceremony2 = __esm(() => {
|
|
|
9526
9554
|
gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9527
9555
|
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",
|
|
9528
9556
|
gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
|
|
9557
|
+
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",
|
|
9558
|
+
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",
|
|
9529
9559
|
gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
|
|
9530
9560
|
gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9531
9561
|
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",
|
|
9562
|
+
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",
|
|
9532
9563
|
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",
|
|
9533
9564
|
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",
|
|
9534
9565
|
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",
|
|
9535
9566
|
gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
|
|
9567
|
+
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",
|
|
9568
|
+
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",
|
|
9536
9569
|
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",
|
|
9570
|
+
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",
|
|
9537
9571
|
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",
|
|
9538
9572
|
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9539
9573
|
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
@@ -18007,7 +18041,7 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
18007
18041
|
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
18008
18042
|
const engine = config.connection?.system || "postgresql";
|
|
18009
18043
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
18010
|
-
let tier = getEngineCapability(engine, commandName).tier;
|
|
18044
|
+
let tier = outcome.sideEffectTier ?? getEngineCapability(engine, commandName).tier;
|
|
18011
18045
|
if (options.dryRun || options.plan) {
|
|
18012
18046
|
tier = "dry-run";
|
|
18013
18047
|
}
|
|
@@ -18940,6 +18974,7 @@ __export(exports_permission_guard, {
|
|
|
18940
18974
|
checkPermissionForClassification: () => checkPermissionForClassification,
|
|
18941
18975
|
checkPermission: () => checkPermission,
|
|
18942
18976
|
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18977
|
+
SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
|
|
18943
18978
|
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18944
18979
|
PermissionError: () => PermissionError
|
|
18945
18980
|
});
|
|
@@ -22358,7 +22393,8 @@ async function enforceTierTwo(request, attended) {
|
|
|
22358
22393
|
const { verdict } = request;
|
|
22359
22394
|
const reason = verdict.reason ?? "no_where";
|
|
22360
22395
|
if (!attended) {
|
|
22361
|
-
|
|
22396
|
+
const inShell = process.env.DBCLI_SHELL_SUBCOMMAND === "1";
|
|
22397
|
+
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);
|
|
22362
22398
|
}
|
|
22363
22399
|
writeSummary(request);
|
|
22364
22400
|
const warning = verdict.table === undefined && reason === "ddl_destruction" ? t("ceremony.gate_ddl_warning_untargeted") : t_vars(WARNING_KEY[reason], { table: verdict.table ?? "" });
|
|
@@ -22366,7 +22402,13 @@ async function enforceTierTwo(request, attended) {
|
|
|
22366
22402
|
${warning}
|
|
22367
22403
|
${t(REMEDY_KEY[reason])}
|
|
22368
22404
|
`);
|
|
22369
|
-
const
|
|
22405
|
+
const ask = request.ask ?? promptUser.text;
|
|
22406
|
+
const typed = await ask(t_vars("ceremony.gate_typed_prompt", { phrase: verdict.confirmationPhrase }));
|
|
22407
|
+
if (typed === null) {
|
|
22408
|
+
process.stderr.write(`${t("ceremony.gate_cancelled")}
|
|
22409
|
+
`);
|
|
22410
|
+
return false;
|
|
22411
|
+
}
|
|
22370
22412
|
const matched = typed.trim().toLowerCase() === verdict.confirmationPhrase.toLowerCase();
|
|
22371
22413
|
if (!matched) {
|
|
22372
22414
|
process.stderr.write(`${t_vars("ceremony.gate_typed_mismatch", { phrase: verdict.confirmationPhrase })}
|
|
@@ -22404,14 +22446,18 @@ var init_write_gate_prompt = __esm(() => {
|
|
|
22404
22446
|
ddl_destruction: "ceremony.gate_remedy_ddl_destruction",
|
|
22405
22447
|
unparseable: "ceremony.gate_remedy_unparseable",
|
|
22406
22448
|
non_unique_where: "ceremony.gate_remedy_non_unique_where",
|
|
22407
|
-
multiple_statements: "ceremony.gate_remedy_multiple_statements"
|
|
22449
|
+
multiple_statements: "ceremony.gate_remedy_multiple_statements",
|
|
22450
|
+
multi_table: "ceremony.gate_remedy_multi_table",
|
|
22451
|
+
nested_write: "ceremony.gate_remedy_nested_write"
|
|
22408
22452
|
};
|
|
22409
22453
|
WARNING_KEY = {
|
|
22410
22454
|
no_where: "ceremony.gate_full_table_warning",
|
|
22411
22455
|
ddl_destruction: "ceremony.gate_ddl_warning",
|
|
22412
22456
|
unparseable: "ceremony.gate_unparseable_warning",
|
|
22413
22457
|
non_unique_where: "ceremony.gate_full_table_warning",
|
|
22414
|
-
multiple_statements: "ceremony.gate_multiple_statements_warning"
|
|
22458
|
+
multiple_statements: "ceremony.gate_multiple_statements_warning",
|
|
22459
|
+
multi_table: "ceremony.gate_multi_table_warning",
|
|
22460
|
+
nested_write: "ceremony.gate_nested_write_warning"
|
|
22415
22461
|
};
|
|
22416
22462
|
});
|
|
22417
22463
|
|
|
@@ -22446,6 +22492,12 @@ async function classifySqlWriteGate(sql, options = {}) {
|
|
|
22446
22492
|
if (containsMultipleStatements(sql, dialect)) {
|
|
22447
22493
|
return verdict("two", "multiple_statements", undefined, type);
|
|
22448
22494
|
}
|
|
22495
|
+
const nested = nestedWrite(sql, dialect);
|
|
22496
|
+
if (nested)
|
|
22497
|
+
return verdict("two", "nested_write", undefined, nested);
|
|
22498
|
+
const merge = classifyMerge(sql, dialect);
|
|
22499
|
+
if (merge)
|
|
22500
|
+
return merge;
|
|
22449
22501
|
if (type === "DROP" || type === "TRUNCATE") {
|
|
22450
22502
|
const matched = DDL_TARGET.exec(sql)?.[1];
|
|
22451
22503
|
const table = matched ? matched.replace(/[`"[\]]/g, "") : undefined;
|
|
@@ -22464,20 +22516,139 @@ async function classifyQualification(sql, dialect, type) {
|
|
|
22464
22516
|
return unparseable(sql, dialect, type);
|
|
22465
22517
|
}
|
|
22466
22518
|
const statement = Array.isArray(ast) ? ast[0] : ast;
|
|
22467
|
-
if (!statement)
|
|
22519
|
+
if (!statement || statement.type !== "update" && statement.type !== "delete") {
|
|
22468
22520
|
return unparseable(sql, dialect, type);
|
|
22469
|
-
|
|
22470
|
-
|
|
22521
|
+
}
|
|
22522
|
+
const scope = [...statement.table ?? [], ...statement.from ?? []];
|
|
22523
|
+
const sources = new Set(scope.map((ref, index) => refIdentity(ref) ?? `#unnamed-${index}`));
|
|
22524
|
+
if (sources.size > 1) {
|
|
22525
|
+
return verdict("two", "multi_table", undefined, type);
|
|
22526
|
+
}
|
|
22527
|
+
const target = normaliseRef(statement.table?.[0] ?? scope[0]);
|
|
22528
|
+
const table = tableName(target);
|
|
22529
|
+
if (statement.limit != null)
|
|
22530
|
+
return verdict("one", undefined, table, type);
|
|
22531
|
+
if (statement.where != null && narrowsTarget(statement.where, refNames(target))) {
|
|
22471
22532
|
return verdict("one", undefined, table, type);
|
|
22472
22533
|
}
|
|
22473
22534
|
return verdict("two", "no_where", table, type);
|
|
22474
22535
|
}
|
|
22536
|
+
function normaliseRef(ref) {
|
|
22537
|
+
if (ref?.table?.toUpperCase() !== "ONLY")
|
|
22538
|
+
return ref;
|
|
22539
|
+
if (typeof ref.as !== "string" || ref.as.length === 0)
|
|
22540
|
+
return ref;
|
|
22541
|
+
return {
|
|
22542
|
+
db: ref.db,
|
|
22543
|
+
on: ref.on,
|
|
22544
|
+
as: null,
|
|
22545
|
+
table: typeof ref.as === "string" ? ref.as : undefined
|
|
22546
|
+
};
|
|
22547
|
+
}
|
|
22548
|
+
function refIdentity(reference) {
|
|
22549
|
+
const ref = normaliseRef(reference);
|
|
22550
|
+
if (typeof ref?.table !== "string")
|
|
22551
|
+
return;
|
|
22552
|
+
return (ref.db ? `${ref.db}.${ref.table}` : ref.table).toLowerCase();
|
|
22553
|
+
}
|
|
22554
|
+
function refNames(reference) {
|
|
22555
|
+
const ref = normaliseRef(reference);
|
|
22556
|
+
const name = typeof ref?.as === "string" && ref.as.length > 0 ? ref.as : ref?.table;
|
|
22557
|
+
return typeof name === "string" ? [name.toLowerCase()] : [];
|
|
22558
|
+
}
|
|
22559
|
+
function narrowsTarget(where, targetNames) {
|
|
22560
|
+
const names = new Set(targetNames);
|
|
22561
|
+
const { owners, correlated, skippedSubquery } = scanCondition(where);
|
|
22562
|
+
if (owners.length === 0 && correlated.length === 0)
|
|
22563
|
+
return !skippedSubquery;
|
|
22564
|
+
return owners.some((owner) => owner === null || names.has(owner)) || correlated.some((owner) => names.has(owner));
|
|
22565
|
+
}
|
|
22566
|
+
function mergeScans(scans) {
|
|
22567
|
+
return {
|
|
22568
|
+
owners: scans.flatMap((scan) => scan.owners),
|
|
22569
|
+
correlated: scans.flatMap((scan) => scan.correlated),
|
|
22570
|
+
skippedSubquery: scans.some((scan) => scan.skippedSubquery)
|
|
22571
|
+
};
|
|
22572
|
+
}
|
|
22573
|
+
function scanCondition(node, bound) {
|
|
22574
|
+
if (node === null || typeof node !== "object")
|
|
22575
|
+
return EMPTY_SCAN;
|
|
22576
|
+
const record = node;
|
|
22577
|
+
const inner = record.ast ?? (record.type === "select" ? record : undefined);
|
|
22578
|
+
const nested = inner !== undefined;
|
|
22579
|
+
if (record.type === "column_ref") {
|
|
22580
|
+
const owner = record.table;
|
|
22581
|
+
const name = owner == null ? null : String(owner).toLowerCase();
|
|
22582
|
+
if (bound === undefined)
|
|
22583
|
+
return { owners: [name], correlated: [], skippedSubquery: false };
|
|
22584
|
+
if (name === null || bound.has(name))
|
|
22585
|
+
return EMPTY_SCAN;
|
|
22586
|
+
return { owners: [], correlated: [name], skippedSubquery: false };
|
|
22587
|
+
}
|
|
22588
|
+
const scope = nested ? new Set([...bound ?? [], ...boundNames(inner)]) : bound;
|
|
22589
|
+
const scans = Object.values(record).map((value) => Array.isArray(value) ? mergeScans(value.map((item) => scanCondition(item, scope))) : scanCondition(value, scope));
|
|
22590
|
+
const merged = mergeScans(scans);
|
|
22591
|
+
return nested ? { ...merged, skippedSubquery: true } : merged;
|
|
22592
|
+
}
|
|
22593
|
+
function boundNames(select2) {
|
|
22594
|
+
const from = select2?.from ?? [];
|
|
22595
|
+
return Array.isArray(from) ? from.flatMap((ref) => refNames(ref)) : [];
|
|
22596
|
+
}
|
|
22597
|
+
function stripParenthesised(text2) {
|
|
22598
|
+
let previous = text2;
|
|
22599
|
+
for (;; ) {
|
|
22600
|
+
const next = previous.replace(/\([^()]*\)/g, " ");
|
|
22601
|
+
if (next === previous)
|
|
22602
|
+
return next;
|
|
22603
|
+
previous = next;
|
|
22604
|
+
}
|
|
22605
|
+
}
|
|
22475
22606
|
function unparseable(sql, dialect, type) {
|
|
22476
22607
|
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {});
|
|
22608
|
+
const statementLevel = stripParenthesised(executable);
|
|
22609
|
+
const singleTable = SINGLE_TARGET.test(executable) && !SECOND_SOURCE_ANYWHERE.test(executable) && !SECOND_SOURCE_AT_STATEMENT_LEVEL.some((pattern) => pattern.test(statementLevel));
|
|
22610
|
+
if (!singleTable)
|
|
22611
|
+
return verdict("two", "unparseable", undefined, type);
|
|
22477
22612
|
if (/\bWHERE\b|\bLIMIT\b/i.test(executable))
|
|
22478
22613
|
return verdict("one", undefined, undefined, type);
|
|
22479
22614
|
return verdict("two", "unparseable", undefined, type);
|
|
22480
22615
|
}
|
|
22616
|
+
function classifyMerge(sql, dialect) {
|
|
22617
|
+
const executable = stripParenthesised(stripCommentsAndStrings(sql, dialect ? { dialect } : {}));
|
|
22618
|
+
if (!MERGE_STATEMENT.test(executable))
|
|
22619
|
+
return;
|
|
22620
|
+
const matched = MERGE_TARGET.exec(executable)?.[1];
|
|
22621
|
+
const table = matched === undefined || matched.endsWith(".") || /^USING$/i.test(matched) ? undefined : matched;
|
|
22622
|
+
const destructive = /\bTHEN\s+DELETE\b/i.test(executable) ? "DELETE" : /\bTHEN\s+UPDATE\b/i.test(executable) ? "UPDATE" : undefined;
|
|
22623
|
+
if (!destructive)
|
|
22624
|
+
return verdict("one", undefined, table, "INSERT");
|
|
22625
|
+
return verdict("two", "multi_table", table, destructive);
|
|
22626
|
+
}
|
|
22627
|
+
function nestedWrite(sql, dialect) {
|
|
22628
|
+
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {}).replace(SQL_LOCK_CLAUSE, " ").replace(REFERENTIAL_ACTION, " ");
|
|
22629
|
+
const match = /\b(INSERT|UPDATE|DELETE)\b|\bMERGE\s+INTO\b/i.exec(insideParentheses(executable));
|
|
22630
|
+
if (match === null)
|
|
22631
|
+
return;
|
|
22632
|
+
return keywordToType(match[1] ?? "MERGE");
|
|
22633
|
+
}
|
|
22634
|
+
function insideParentheses(text2) {
|
|
22635
|
+
let depth = 0;
|
|
22636
|
+
let result = "";
|
|
22637
|
+
for (const char of text2) {
|
|
22638
|
+
if (char === "(") {
|
|
22639
|
+
depth += 1;
|
|
22640
|
+
result += " ";
|
|
22641
|
+
continue;
|
|
22642
|
+
}
|
|
22643
|
+
if (char === ")") {
|
|
22644
|
+
depth = Math.max(0, depth - 1);
|
|
22645
|
+
result += " ";
|
|
22646
|
+
continue;
|
|
22647
|
+
}
|
|
22648
|
+
result += depth > 0 ? char : " ";
|
|
22649
|
+
}
|
|
22650
|
+
return result;
|
|
22651
|
+
}
|
|
22481
22652
|
function classifyStructuredWriteGate(request) {
|
|
22482
22653
|
const conditions = new Set(Object.keys(request.where).map((key) => key.toLowerCase()));
|
|
22483
22654
|
const identifies = uniqueSelectors(request.schema).some((selector) => selector.length > 0 && selector.every((column) => conditions.has(column.toLowerCase())));
|
|
@@ -22511,7 +22682,7 @@ function verdict(tier, reason, table, operation) {
|
|
|
22511
22682
|
confirmationPhrase: table ?? WRITE_GATE_FALLBACK_PHRASE
|
|
22512
22683
|
};
|
|
22513
22684
|
}
|
|
22514
|
-
var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET;
|
|
22685
|
+
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;
|
|
22515
22686
|
var init_write_gate = __esm(() => {
|
|
22516
22687
|
init_permission_guard();
|
|
22517
22688
|
init_sql_analysis();
|
|
@@ -22530,6 +22701,13 @@ var init_write_gate = __esm(() => {
|
|
|
22530
22701
|
"CREATE"
|
|
22531
22702
|
]);
|
|
22532
22703
|
DDL_TARGET = /\b(?:DROP\s+TABLE|TRUNCATE(?:\s+TABLE)?)\s+(?:IF\s+EXISTS\s+)?([`"[\]\w$.]+)\s*;?\s*$/i;
|
|
22704
|
+
EMPTY_SCAN = { owners: [], correlated: [], skippedSubquery: false };
|
|
22705
|
+
SINGLE_TARGET = /^\s*(?:UPDATE\s+(?:ONLY\s+)?[\w`"[\]$.]+\s+SET\b|DELETE\s+FROM\s+(?:ONLY\s+)?[\w`"[\]$.]+(?!\s*,)(?:\s|$))/i;
|
|
22706
|
+
SECOND_SOURCE_ANYWHERE = /\b(?:SELECT|TABLE|VALUES|JOIN|USING)\b/i;
|
|
22707
|
+
SECOND_SOURCE_AT_STATEMENT_LEVEL = [/\bWITH\b/i, /\bUPDATE\b[\s\S]*?\bFROM\b/i];
|
|
22708
|
+
MERGE_STATEMENT = /\bMERGE\s+INTO\b/i;
|
|
22709
|
+
MERGE_TARGET = /\bMERGE\s+INTO\s+(?:ONLY\s+)?([\w$.]+)/i;
|
|
22710
|
+
REFERENTIAL_ACTION = /\bON\s+(?:DELETE|UPDATE)\b/gi;
|
|
22533
22711
|
});
|
|
22534
22712
|
|
|
22535
22713
|
// src/commands/write-gate-guard.ts
|
|
@@ -22541,6 +22719,7 @@ __export(exports_write_gate_guard, {
|
|
|
22541
22719
|
async function recordGateDecision(request) {
|
|
22542
22720
|
await writeAuditEntry(request.config, request.command, request.options, {
|
|
22543
22721
|
success: request.outcome === "allowed",
|
|
22722
|
+
sideEffectTier: "db-write",
|
|
22544
22723
|
...request.sql && { sql: request.sql },
|
|
22545
22724
|
...request.verdict.table && { target: request.verdict.table },
|
|
22546
22725
|
metadata: {
|
|
@@ -25491,9 +25670,12 @@ async function qCommand(name, options, command) {
|
|
|
25491
25670
|
console.error(`\u26A0 ${w2}`);
|
|
25492
25671
|
if (prepared.warnings.length > 0)
|
|
25493
25672
|
console.error("");
|
|
25673
|
+
const family = engineFamily(engine);
|
|
25674
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25675
|
+
const classification = family === "sql" ? enforcePermission(prepared.rewrittenSql, config.permission, sqlDialect) : undefined;
|
|
25494
25676
|
if (options.dryRun) {
|
|
25495
25677
|
console.log(formatDryRun({
|
|
25496
|
-
family
|
|
25678
|
+
family,
|
|
25497
25679
|
driverSql: prepared.driver.sql,
|
|
25498
25680
|
values: prepared.driver.values,
|
|
25499
25681
|
execHints: prepared.execHints,
|
|
@@ -25514,8 +25696,6 @@ async function qCommand(name, options, command) {
|
|
|
25514
25696
|
}
|
|
25515
25697
|
const blacklistManager = new BlacklistManager(config);
|
|
25516
25698
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
25517
|
-
const family = engineFamily(engine);
|
|
25518
|
-
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25519
25699
|
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
25520
25700
|
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
25521
25701
|
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
@@ -25523,8 +25703,8 @@ async function qCommand(name, options, command) {
|
|
|
25523
25703
|
targetNameForAudit = targetName || name;
|
|
25524
25704
|
if (family === "es") {
|
|
25525
25705
|
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
25526
|
-
} else if (
|
|
25527
|
-
blacklistValidator.checkTablesBlacklist(
|
|
25706
|
+
} else if (classification && targets.length > 0) {
|
|
25707
|
+
blacklistValidator.checkTablesBlacklist(classification.type, targets);
|
|
25528
25708
|
}
|
|
25529
25709
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
25530
25710
|
await adapter.connect();
|
|
@@ -36541,6 +36721,81 @@ Verification artifact: ${verificationArtifactPath}`);
|
|
|
36541
36721
|
});
|
|
36542
36722
|
});
|
|
36543
36723
|
|
|
36724
|
+
// src/core/audit/write-gate-summary.ts
|
|
36725
|
+
function metadataOf(entry) {
|
|
36726
|
+
const metadata = entry.metadata;
|
|
36727
|
+
return metadata && typeof metadata === "object" ? metadata : {};
|
|
36728
|
+
}
|
|
36729
|
+
function readString(source, key) {
|
|
36730
|
+
const value = source[key];
|
|
36731
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
36732
|
+
}
|
|
36733
|
+
|
|
36734
|
+
class Span {
|
|
36735
|
+
from;
|
|
36736
|
+
to;
|
|
36737
|
+
add(ts) {
|
|
36738
|
+
if (this.from === undefined || ts < this.from)
|
|
36739
|
+
this.from = ts;
|
|
36740
|
+
if (this.to === undefined || ts > this.to)
|
|
36741
|
+
this.to = ts;
|
|
36742
|
+
}
|
|
36743
|
+
value() {
|
|
36744
|
+
return this.from === undefined || this.to === undefined ? null : { from: this.from, to: this.to };
|
|
36745
|
+
}
|
|
36746
|
+
}
|
|
36747
|
+
function summarizeWriteGate(entries) {
|
|
36748
|
+
const outcomes = Object.fromEntries(GATE_OUTCOMES.map((o) => [o, 0]));
|
|
36749
|
+
const reasons = Object.fromEntries(GATE_REASONS.map((r) => [r, 0]));
|
|
36750
|
+
const tierOneOutcomes = {};
|
|
36751
|
+
const window = new Span;
|
|
36752
|
+
const range = new Span;
|
|
36753
|
+
let total = 0;
|
|
36754
|
+
let tierOneTotal = 0;
|
|
36755
|
+
const tally = (into, key) => {
|
|
36756
|
+
into[key] = (into[key] ?? 0) + 1;
|
|
36757
|
+
};
|
|
36758
|
+
for (const entry of entries) {
|
|
36759
|
+
window.add(entry.ts);
|
|
36760
|
+
const metadata = metadataOf(entry);
|
|
36761
|
+
const tier = readString(metadata, "write_gate_tier");
|
|
36762
|
+
if (!tier)
|
|
36763
|
+
continue;
|
|
36764
|
+
const outcome = readString(metadata, "write_gate_outcome") ?? "unknown";
|
|
36765
|
+
if (tier !== "two") {
|
|
36766
|
+
tierOneTotal += 1;
|
|
36767
|
+
tally(tierOneOutcomes, outcome);
|
|
36768
|
+
continue;
|
|
36769
|
+
}
|
|
36770
|
+
total += 1;
|
|
36771
|
+
range.add(entry.ts);
|
|
36772
|
+
tally(outcomes, outcome);
|
|
36773
|
+
tally(reasons, readString(metadata, "write_gate_reason") ?? "unknown");
|
|
36774
|
+
}
|
|
36775
|
+
return {
|
|
36776
|
+
scanned: entries.length,
|
|
36777
|
+
window: window.value(),
|
|
36778
|
+
total,
|
|
36779
|
+
outcomes,
|
|
36780
|
+
reasons,
|
|
36781
|
+
tierOne: { total: tierOneTotal, outcomes: tierOneOutcomes },
|
|
36782
|
+
range: range.value()
|
|
36783
|
+
};
|
|
36784
|
+
}
|
|
36785
|
+
var GATE_OUTCOMES, GATE_REASONS;
|
|
36786
|
+
var init_write_gate_summary = __esm(() => {
|
|
36787
|
+
GATE_OUTCOMES = ["allowed", "declined", "refused"];
|
|
36788
|
+
GATE_REASONS = [
|
|
36789
|
+
"no_where",
|
|
36790
|
+
"ddl_destruction",
|
|
36791
|
+
"unparseable",
|
|
36792
|
+
"multiple_statements",
|
|
36793
|
+
"non_unique_where",
|
|
36794
|
+
"multi_table",
|
|
36795
|
+
"nested_write"
|
|
36796
|
+
];
|
|
36797
|
+
});
|
|
36798
|
+
|
|
36544
36799
|
// src/commands/audit.ts
|
|
36545
36800
|
var exports_audit = {};
|
|
36546
36801
|
__export(exports_audit, {
|
|
@@ -36726,6 +36981,53 @@ function readLineFromStdinWithStderrPrompt(prompt) {
|
|
|
36726
36981
|
process.stdin.resume();
|
|
36727
36982
|
});
|
|
36728
36983
|
}
|
|
36984
|
+
function formatOutcomeCounts(outcomes) {
|
|
36985
|
+
const seen = Object.entries(outcomes).filter(([, count]) => count > 0);
|
|
36986
|
+
if (seen.length === 0)
|
|
36987
|
+
return MISSING_PLACEHOLDER;
|
|
36988
|
+
return seen.map(([name, count]) => `${name} ${count}`).join(", ");
|
|
36989
|
+
}
|
|
36990
|
+
function renderWriteGateSummary(summary, connections) {
|
|
36991
|
+
const lines = [];
|
|
36992
|
+
lines.push(t_vars("audit.write_gate.header", {
|
|
36993
|
+
conn: connections.length > 0 ? connections.join(", ") : MISSING_PLACEHOLDER
|
|
36994
|
+
}));
|
|
36995
|
+
if (summary.window) {
|
|
36996
|
+
lines.push(t_vars("audit.write_gate.window", {
|
|
36997
|
+
scanned: String(summary.scanned),
|
|
36998
|
+
from: summary.window.from,
|
|
36999
|
+
to: summary.window.to
|
|
37000
|
+
}));
|
|
37001
|
+
}
|
|
37002
|
+
if (summary.total === 0) {
|
|
37003
|
+
lines.push("");
|
|
37004
|
+
lines.push(summary.scanned === 0 ? t("audit.write_gate.no_entries") : t("audit.write_gate.no_decisions"));
|
|
37005
|
+
if (summary.tierOne.total > 0) {
|
|
37006
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37007
|
+
count: String(summary.tierOne.total),
|
|
37008
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37009
|
+
}));
|
|
37010
|
+
}
|
|
37011
|
+
return lines.join(`
|
|
37012
|
+
`);
|
|
37013
|
+
}
|
|
37014
|
+
lines.push(t_vars("audit.write_gate.decisions", {
|
|
37015
|
+
count: String(summary.total),
|
|
37016
|
+
from: summary.range?.from ?? MISSING_PLACEHOLDER,
|
|
37017
|
+
to: summary.range?.to ?? MISSING_PLACEHOLDER
|
|
37018
|
+
}));
|
|
37019
|
+
lines.push("");
|
|
37020
|
+
lines.push(renderTable(Object.entries(summary.outcomes).map(([name, count]) => [name, String(count)]), ["outcome", "count"]));
|
|
37021
|
+
lines.push("");
|
|
37022
|
+
lines.push(renderTable(Object.entries(summary.reasons).map(([name, count]) => [name, String(count)]), ["reason", "count"]));
|
|
37023
|
+
lines.push("");
|
|
37024
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37025
|
+
count: String(summary.tierOne.total),
|
|
37026
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37027
|
+
}));
|
|
37028
|
+
return lines.join(`
|
|
37029
|
+
`);
|
|
37030
|
+
}
|
|
36729
37031
|
async function statAuditFile(file) {
|
|
36730
37032
|
try {
|
|
36731
37033
|
const s = await stat7(file);
|
|
@@ -36745,6 +37047,7 @@ var init_audit = __esm(() => {
|
|
|
36745
37047
|
init_config_binding();
|
|
36746
37048
|
init_reader2();
|
|
36747
37049
|
init_integration_helper();
|
|
37050
|
+
init_write_gate_summary();
|
|
36748
37051
|
ALLOWED_FORMATS14 = ["table", "json"];
|
|
36749
37052
|
auditCommand = new Command("audit").description(t("audit.description"));
|
|
36750
37053
|
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) => {
|
|
@@ -36952,6 +37255,33 @@ var init_audit = __esm(() => {
|
|
|
36952
37255
|
`);
|
|
36953
37256
|
process.exit(0);
|
|
36954
37257
|
});
|
|
37258
|
+
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) => {
|
|
37259
|
+
const format = options.forAgent === true ? "json" : options.format;
|
|
37260
|
+
validateFormat(format, ALLOWED_FORMATS14, "audit write-gate");
|
|
37261
|
+
const configPath = resolveConfigPath(command, options);
|
|
37262
|
+
const config = await configModule.read(configPath);
|
|
37263
|
+
if (isAuditDisabled(config))
|
|
37264
|
+
emitDisabledAndExit0();
|
|
37265
|
+
const { auditDir, auditFile, connectionName } = await resolveAuditPaths(configPath, config);
|
|
37266
|
+
let entries = [];
|
|
37267
|
+
let connections = [connectionName];
|
|
37268
|
+
if (options.all === true) {
|
|
37269
|
+
const discovered = await discoverConnections(auditDir);
|
|
37270
|
+
connections = discovered.map((c) => c.connection);
|
|
37271
|
+
for (const c of discovered) {
|
|
37272
|
+
for (const file of c.files)
|
|
37273
|
+
entries.push(...await readEntries(file));
|
|
37274
|
+
}
|
|
37275
|
+
} else {
|
|
37276
|
+
entries = await readEntries(auditFile, { include_rotated: true });
|
|
37277
|
+
}
|
|
37278
|
+
const summary = summarizeWriteGate(entries);
|
|
37279
|
+
if (format === "json") {
|
|
37280
|
+
console.log(JSON.stringify({ connections, summary }, null, 2));
|
|
37281
|
+
return;
|
|
37282
|
+
}
|
|
37283
|
+
console.log(renderWriteGateSummary(summary, connections));
|
|
37284
|
+
});
|
|
36955
37285
|
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) => {
|
|
36956
37286
|
const forAgent = options.forAgent === true;
|
|
36957
37287
|
const format = forAgent ? "json" : options.format;
|
|
@@ -41196,20 +41526,39 @@ var init_grammar = __esm(() => {
|
|
|
41196
41526
|
});
|
|
41197
41527
|
|
|
41198
41528
|
// src/core/assert/evaluator.ts
|
|
41529
|
+
function numeric(value) {
|
|
41530
|
+
if (typeof value === "number")
|
|
41531
|
+
return Number.isNaN(value) ? null : value;
|
|
41532
|
+
const trimmed = value.trim();
|
|
41533
|
+
if (trimmed === "")
|
|
41534
|
+
return null;
|
|
41535
|
+
const parsed = Number(trimmed);
|
|
41536
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
41537
|
+
}
|
|
41199
41538
|
function compare(a, op, b) {
|
|
41539
|
+
let left = a;
|
|
41540
|
+
let right = b;
|
|
41541
|
+
if (typeof a !== typeof b) {
|
|
41542
|
+
const na = numeric(a);
|
|
41543
|
+
const nb = numeric(b);
|
|
41544
|
+
if (na !== null && nb !== null) {
|
|
41545
|
+
left = na;
|
|
41546
|
+
right = nb;
|
|
41547
|
+
}
|
|
41548
|
+
}
|
|
41200
41549
|
switch (op) {
|
|
41201
41550
|
case ">":
|
|
41202
|
-
return
|
|
41551
|
+
return left > right;
|
|
41203
41552
|
case ">=":
|
|
41204
|
-
return
|
|
41553
|
+
return left >= right;
|
|
41205
41554
|
case "<":
|
|
41206
|
-
return
|
|
41555
|
+
return left < right;
|
|
41207
41556
|
case "<=":
|
|
41208
|
-
return
|
|
41557
|
+
return left <= right;
|
|
41209
41558
|
case "==":
|
|
41210
|
-
return
|
|
41559
|
+
return left === right;
|
|
41211
41560
|
case "!=":
|
|
41212
|
-
return
|
|
41561
|
+
return left !== right;
|
|
41213
41562
|
}
|
|
41214
41563
|
}
|
|
41215
41564
|
function firstScalar(result) {
|
|
@@ -41262,7 +41611,10 @@ function evaluateExpect(node, result) {
|
|
|
41262
41611
|
};
|
|
41263
41612
|
}
|
|
41264
41613
|
if (pred.type === "between") {
|
|
41265
|
-
const bad2 = nonNull.filter((v) =>
|
|
41614
|
+
const bad2 = nonNull.filter((v) => {
|
|
41615
|
+
const n = numeric(v);
|
|
41616
|
+
return n === null || n < pred.low || n > pred.high;
|
|
41617
|
+
});
|
|
41266
41618
|
return {
|
|
41267
41619
|
name: `col:${column} between ${pred.low} and ${pred.high}`,
|
|
41268
41620
|
expected: "0 out of range",
|
|
@@ -41293,7 +41645,7 @@ function compareVs(a, b, mode) {
|
|
|
41293
41645
|
name: "vs:value",
|
|
41294
41646
|
expected: String(bv),
|
|
41295
41647
|
actual: String(av),
|
|
41296
|
-
pass: av !== null && bv !== null && av
|
|
41648
|
+
pass: av !== null && bv !== null && compare(av, "==", bv)
|
|
41297
41649
|
};
|
|
41298
41650
|
}
|
|
41299
41651
|
var AssertShapeError;
|
|
@@ -41366,7 +41718,7 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
|
|
|
41366
41718
|
function sha2563(value) {
|
|
41367
41719
|
return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
|
|
41368
41720
|
}
|
|
41369
|
-
function
|
|
41721
|
+
function verdictObservation(verdict2) {
|
|
41370
41722
|
if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
|
|
41371
41723
|
throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
|
|
41372
41724
|
}
|
|
@@ -41376,26 +41728,30 @@ function verdictFingerprint(verdict2) {
|
|
|
41376
41728
|
}
|
|
41377
41729
|
return check.pass;
|
|
41378
41730
|
});
|
|
41379
|
-
return
|
|
41731
|
+
return {
|
|
41732
|
+
kind: "assert-verdict",
|
|
41733
|
+
checksPassed: bits.filter(Boolean).length,
|
|
41734
|
+
checksTotal: bits.length
|
|
41735
|
+
};
|
|
41380
41736
|
}
|
|
41381
|
-
function
|
|
41737
|
+
function verificationObservation(status2, artifactPersisted) {
|
|
41382
41738
|
if (!isVerificationStatus(status2)) {
|
|
41383
41739
|
throw new EvidenceReceiptValidationError("verificationStatus must be a verification status");
|
|
41384
41740
|
}
|
|
41385
41741
|
if (typeof artifactPersisted !== "boolean") {
|
|
41386
41742
|
throw new EvidenceReceiptValidationError("verificationArtifactPersisted must be a boolean");
|
|
41387
41743
|
}
|
|
41388
|
-
return
|
|
41744
|
+
return { kind: "verify-outcome", status: status2 };
|
|
41389
41745
|
}
|
|
41390
41746
|
function buildEvidenceReceipt(input, options = {}) {
|
|
41391
41747
|
const operation = input.operation === "verify" ? "verify" : "assert";
|
|
41392
41748
|
const command = canonicalizeEvidenceReceiptCommand(input.command, operation);
|
|
41393
41749
|
const context = parseContext2(input.context);
|
|
41394
|
-
const {
|
|
41395
|
-
|
|
41750
|
+
const { observation, outcome } = input.operation === "verify" ? {
|
|
41751
|
+
observation: verificationObservation(input.verificationStatus, input.verificationArtifactPersisted),
|
|
41396
41752
|
outcome: input.verificationStatus === "verified" && input.verificationArtifactPersisted ? "succeeded" : "failed"
|
|
41397
41753
|
} : {
|
|
41398
|
-
|
|
41754
|
+
observation: verdictObservation(input.verdict),
|
|
41399
41755
|
outcome: input.verdict.pass ? "succeeded" : "failed"
|
|
41400
41756
|
};
|
|
41401
41757
|
const receipt = {
|
|
@@ -41412,10 +41768,7 @@ function buildEvidenceReceipt(input, options = {}) {
|
|
|
41412
41768
|
verificationArtifactRef: input.verificationArtifactRef === undefined ? null : nullableId(input.verificationArtifactRef, "verificationArtifactRef")
|
|
41413
41769
|
},
|
|
41414
41770
|
replay: { status: "context-required" },
|
|
41415
|
-
observation
|
|
41416
|
-
kind: operation === "assert" ? "assert-verdict" : "verify-outcome",
|
|
41417
|
-
fingerprint: observationFingerprint
|
|
41418
|
-
}
|
|
41771
|
+
observation
|
|
41419
41772
|
};
|
|
41420
41773
|
return receipt;
|
|
41421
41774
|
}
|
|
@@ -41460,9 +41813,7 @@ function parseEvidenceReceipt(raw) {
|
|
|
41460
41813
|
throw new EvidenceReceiptValidationError("command provenance hash mismatch");
|
|
41461
41814
|
if (!record3(raw.replay) || raw.replay.status !== "context-required" || Object.keys(raw.replay).length !== 1)
|
|
41462
41815
|
throw new EvidenceReceiptValidationError("replay must be context-required");
|
|
41463
|
-
|
|
41464
|
-
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41465
|
-
const observationKind = operation === "assert" ? "assert-verdict" : "verify-outcome";
|
|
41816
|
+
const observation = parseObservation(raw.observation, operation);
|
|
41466
41817
|
return {
|
|
41467
41818
|
version: EVIDENCE_RECEIPT_VERSION,
|
|
41468
41819
|
id: safeId(raw.id, "id"),
|
|
@@ -41477,10 +41828,26 @@ function parseEvidenceReceipt(raw) {
|
|
|
41477
41828
|
verificationArtifactRef: nullableId(raw.provenance.verificationArtifactRef, "provenance.verificationArtifactRef")
|
|
41478
41829
|
},
|
|
41479
41830
|
replay: { status: "context-required" },
|
|
41480
|
-
observation
|
|
41481
|
-
|
|
41482
|
-
|
|
41483
|
-
|
|
41831
|
+
observation
|
|
41832
|
+
};
|
|
41833
|
+
}
|
|
41834
|
+
function parseObservation(raw, operation) {
|
|
41835
|
+
if (!record3(raw))
|
|
41836
|
+
throw new EvidenceReceiptValidationError("observation must be an object");
|
|
41837
|
+
if (operation === "verify") {
|
|
41838
|
+
exact(raw, ["kind", "status"], "observation");
|
|
41839
|
+
if (raw.kind !== "verify-outcome" || !isVerificationStatus(raw.status))
|
|
41840
|
+
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41841
|
+
return { kind: "verify-outcome", status: raw.status };
|
|
41842
|
+
}
|
|
41843
|
+
exact(raw, ["kind", "checksPassed", "checksTotal"], "observation");
|
|
41844
|
+
const { kind, checksPassed, checksTotal } = raw;
|
|
41845
|
+
if (kind !== "assert-verdict" || !Number.isSafeInteger(checksPassed) || !Number.isSafeInteger(checksTotal) || checksPassed < 0 || checksTotal < checksPassed)
|
|
41846
|
+
throw new EvidenceReceiptValidationError("observation must match receipt operation");
|
|
41847
|
+
return {
|
|
41848
|
+
kind: "assert-verdict",
|
|
41849
|
+
checksPassed,
|
|
41850
|
+
checksTotal
|
|
41484
41851
|
};
|
|
41485
41852
|
}
|
|
41486
41853
|
function inside(root, target) {
|
|
@@ -44242,6 +44609,9 @@ function classifyInput(raw) {
|
|
|
44242
44609
|
return { type: "meta", raw, normalized: normalized2 };
|
|
44243
44610
|
}
|
|
44244
44611
|
}
|
|
44612
|
+
if (normalized2.startsWith(COMMAND_PREFIX)) {
|
|
44613
|
+
return { type: "command", raw, normalized: normalized2 };
|
|
44614
|
+
}
|
|
44245
44615
|
const firstWord = (normalized2.split(/\s+/)[0] ?? "").toUpperCase();
|
|
44246
44616
|
if (sqlKeywordSet.has(firstWord)) {
|
|
44247
44617
|
return { type: "sql", raw, normalized: normalized2 };
|
|
@@ -44251,7 +44621,7 @@ function classifyInput(raw) {
|
|
|
44251
44621
|
}
|
|
44252
44622
|
return { type: "command", raw, normalized: normalized2 };
|
|
44253
44623
|
}
|
|
44254
|
-
var META_PREFIX = ".", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
44624
|
+
var META_PREFIX = ".", COMMAND_PREFIX = "\\", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
44255
44625
|
var init_input_classifier = __esm(() => {
|
|
44256
44626
|
init_types11();
|
|
44257
44627
|
sqlKeywordSet = new Set(SQL_KEYWORDS_FOR_DETECTION);
|
|
@@ -44275,6 +44645,10 @@ class MultilineBuffer {
|
|
|
44275
44645
|
isActive() {
|
|
44276
44646
|
return this.lines.length > 0;
|
|
44277
44647
|
}
|
|
44648
|
+
isInsideLiteral() {
|
|
44649
|
+
return insideLiteral(this.lines.join(`
|
|
44650
|
+
`));
|
|
44651
|
+
}
|
|
44278
44652
|
getPartial() {
|
|
44279
44653
|
return this.lines.join(`
|
|
44280
44654
|
`);
|
|
@@ -44284,9 +44658,17 @@ class MultilineBuffer {
|
|
|
44284
44658
|
}
|
|
44285
44659
|
}
|
|
44286
44660
|
function hasUnquotedSemicolon(sql) {
|
|
44661
|
+
return scan(sql).semicolon;
|
|
44662
|
+
}
|
|
44663
|
+
function insideLiteral(sql) {
|
|
44664
|
+
const { inSingleQuote, inDoubleQuote } = scan(sql);
|
|
44665
|
+
return inSingleQuote || inDoubleQuote;
|
|
44666
|
+
}
|
|
44667
|
+
function scan(sql) {
|
|
44287
44668
|
let inSingleQuote = false;
|
|
44288
44669
|
let inDoubleQuote = false;
|
|
44289
44670
|
let escaped = false;
|
|
44671
|
+
let semicolon = false;
|
|
44290
44672
|
for (let i = 0;i < sql.length; i++) {
|
|
44291
44673
|
const ch = sql[i];
|
|
44292
44674
|
if (escaped) {
|
|
@@ -44306,10 +44688,10 @@ function hasUnquotedSemicolon(sql) {
|
|
|
44306
44688
|
continue;
|
|
44307
44689
|
}
|
|
44308
44690
|
if (ch === ";" && !inSingleQuote && !inDoubleQuote) {
|
|
44309
|
-
|
|
44691
|
+
semicolon = true;
|
|
44310
44692
|
}
|
|
44311
44693
|
}
|
|
44312
|
-
return
|
|
44694
|
+
return { semicolon, inSingleQuote, inDoubleQuote };
|
|
44313
44695
|
}
|
|
44314
44696
|
|
|
44315
44697
|
// src/core/repl/meta-commands.ts
|
|
@@ -44353,7 +44735,8 @@ function helpText() {
|
|
|
44353
44735
|
"",
|
|
44354
44736
|
import_picocolors2.default.bold("Usage:"),
|
|
44355
44737
|
` SQL statements Execute directly (end with ${import_picocolors2.default.cyan(";")})`,
|
|
44356
|
-
` dbcli commands Run without ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44738
|
+
` dbcli commands Run without the ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44739
|
+
` ${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")})`,
|
|
44357
44740
|
` Ctrl+D Exit`
|
|
44358
44741
|
];
|
|
44359
44742
|
return lines.join(`
|
|
@@ -44457,24 +44840,26 @@ function splitRespectingQuotes(input) {
|
|
|
44457
44840
|
let current = "";
|
|
44458
44841
|
let inDoubleQuote = false;
|
|
44459
44842
|
let inSingleQuote = false;
|
|
44843
|
+
let quoted = false;
|
|
44460
44844
|
for (let i = 0;i < input.length; i++) {
|
|
44461
44845
|
const ch = input[i];
|
|
44462
44846
|
if (ch === '"' && !inSingleQuote) {
|
|
44463
44847
|
inDoubleQuote = !inDoubleQuote;
|
|
44464
|
-
|
|
44848
|
+
quoted = true;
|
|
44465
44849
|
} else if (ch === "'" && !inDoubleQuote) {
|
|
44466
44850
|
inSingleQuote = !inSingleQuote;
|
|
44467
|
-
|
|
44851
|
+
quoted = true;
|
|
44468
44852
|
} else if (ch === " " && !inDoubleQuote && !inSingleQuote) {
|
|
44469
|
-
if (current.length > 0) {
|
|
44853
|
+
if (current.length > 0 || quoted) {
|
|
44470
44854
|
tokens.push(current);
|
|
44471
44855
|
current = "";
|
|
44856
|
+
quoted = false;
|
|
44472
44857
|
}
|
|
44473
44858
|
} else {
|
|
44474
44859
|
current += ch;
|
|
44475
44860
|
}
|
|
44476
44861
|
}
|
|
44477
|
-
if (current.length > 0) {
|
|
44862
|
+
if (current.length > 0 || quoted) {
|
|
44478
44863
|
tokens.push(current);
|
|
44479
44864
|
}
|
|
44480
44865
|
return tokens;
|
|
@@ -44540,14 +44925,16 @@ var init_history_manager = () => {};
|
|
|
44540
44925
|
class ReplEngine {
|
|
44541
44926
|
adapter;
|
|
44542
44927
|
context;
|
|
44928
|
+
writeGate;
|
|
44543
44929
|
state;
|
|
44544
44930
|
buffer;
|
|
44545
44931
|
history;
|
|
44546
44932
|
formatter;
|
|
44547
44933
|
config;
|
|
44548
|
-
constructor(adapter, context, historyPath, config = null) {
|
|
44934
|
+
constructor(adapter, context, historyPath, config = null, writeGate = null) {
|
|
44549
44935
|
this.adapter = adapter;
|
|
44550
44936
|
this.context = context;
|
|
44937
|
+
this.writeGate = writeGate;
|
|
44551
44938
|
this.state = { format: "table", timing: false, connected: true, noLimit: false };
|
|
44552
44939
|
this.buffer = new MultilineBuffer;
|
|
44553
44940
|
this.history = new HistoryManager(historyPath);
|
|
@@ -44560,6 +44947,9 @@ class ReplEngine {
|
|
|
44560
44947
|
isMultiline() {
|
|
44561
44948
|
return this.buffer.isActive();
|
|
44562
44949
|
}
|
|
44950
|
+
cancelMultiline() {
|
|
44951
|
+
this.buffer.reset();
|
|
44952
|
+
}
|
|
44563
44953
|
getHistory() {
|
|
44564
44954
|
return this.history.getAll();
|
|
44565
44955
|
}
|
|
@@ -44568,6 +44958,13 @@ class ReplEngine {
|
|
|
44568
44958
|
}
|
|
44569
44959
|
async processInput(line) {
|
|
44570
44960
|
if (this.buffer.isActive()) {
|
|
44961
|
+
const classified2 = classifyInput(line);
|
|
44962
|
+
if (classified2.type === "meta" && !this.buffer.isInsideLiteral()) {
|
|
44963
|
+
const result = this.handleMeta(classified2.normalized);
|
|
44964
|
+
if (result.action === "quit" || result.action === "clear")
|
|
44965
|
+
this.buffer.reset();
|
|
44966
|
+
return result;
|
|
44967
|
+
}
|
|
44571
44968
|
const bufResult = this.buffer.append(line);
|
|
44572
44969
|
if (bufResult.complete) {
|
|
44573
44970
|
this.history.add(bufResult.sql);
|
|
@@ -44604,14 +45001,27 @@ class ReplEngine {
|
|
|
44604
45001
|
this.history.add(bufResult.sql);
|
|
44605
45002
|
return this.executeSql(bufResult.sql);
|
|
44606
45003
|
}
|
|
44607
|
-
|
|
45004
|
+
const hint = this.subcommandHint(input);
|
|
45005
|
+
return hint ? { action: "multiline", output: hint } : { action: "multiline" };
|
|
45006
|
+
}
|
|
45007
|
+
subcommandHint(input) {
|
|
45008
|
+
const words = input.trim().split(/\s+/);
|
|
45009
|
+
const firstWord = (words[0] ?? "").toLowerCase();
|
|
45010
|
+
if (!this.context.commandNames.includes(firstWord))
|
|
45011
|
+
return;
|
|
45012
|
+
if (!/\s--[a-z]/.test(input))
|
|
45013
|
+
return;
|
|
45014
|
+
if (STATEMENT_CONTINUATION.has((words[1] ?? "").toUpperCase()))
|
|
45015
|
+
return;
|
|
45016
|
+
return import_picocolors3.default.dim(t_vars("shell.subcommand_prefix_hint", { command: `${COMMAND_PREFIX}${firstWord}` }));
|
|
44608
45017
|
}
|
|
44609
45018
|
async handleCommand(input) {
|
|
44610
|
-
const
|
|
45019
|
+
const invocation = input.startsWith(COMMAND_PREFIX) ? input.slice(COMMAND_PREFIX.length) : input;
|
|
45020
|
+
const parsed = parseCommandLine(invocation);
|
|
44611
45021
|
if (!isKnownCommand(parsed.command, this.context.commandNames)) {
|
|
44612
45022
|
if (this.context.system === "redis") {
|
|
44613
45023
|
this.history.add(input);
|
|
44614
|
-
return this.executeSql(
|
|
45024
|
+
return this.executeSql(invocation);
|
|
44615
45025
|
}
|
|
44616
45026
|
return {
|
|
44617
45027
|
action: "continue",
|
|
@@ -44621,11 +45031,11 @@ class ReplEngine {
|
|
|
44621
45031
|
this.history.add(input);
|
|
44622
45032
|
try {
|
|
44623
45033
|
const cliPath = new URL("../../cli.ts", import.meta.url).pathname;
|
|
44624
|
-
const argv = [
|
|
45034
|
+
const argv = ["--config", this.context.configPath, parsed.command, ...parsed.args];
|
|
44625
45035
|
const proc = Bun.spawn(["bun", "run", cliPath, ...argv], {
|
|
44626
45036
|
stdout: "pipe",
|
|
44627
45037
|
stderr: "pipe",
|
|
44628
|
-
env: { ...process.env }
|
|
45038
|
+
env: { ...process.env, DBCLI_SHELL_SUBCOMMAND: "1" }
|
|
44629
45039
|
});
|
|
44630
45040
|
const [stdoutBuf, stderrBuf] = await Promise.all([
|
|
44631
45041
|
new Response(proc.stdout).text(),
|
|
@@ -44681,6 +45091,11 @@ class ReplEngine {
|
|
|
44681
45091
|
};
|
|
44682
45092
|
}
|
|
44683
45093
|
}
|
|
45094
|
+
if (this.writeGate && !await this.writeGate(sql))
|
|
45095
|
+
return { action: "continue" };
|
|
45096
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
45097
|
+
}
|
|
45098
|
+
async runStatement(sql, blacklistValidator, referencedTables) {
|
|
44684
45099
|
const startTime = Date.now();
|
|
44685
45100
|
try {
|
|
44686
45101
|
const result = await this.adapter.execute(sql, undefined, {
|
|
@@ -44716,7 +45131,7 @@ class ReplEngine {
|
|
|
44716
45131
|
await this.adapter.connect();
|
|
44717
45132
|
this.state = { ...this.state, connected: true };
|
|
44718
45133
|
console.error(import_picocolors3.default.green(t("shell.error_reconnect_success")));
|
|
44719
|
-
return this.
|
|
45134
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
44720
45135
|
} catch (reconnectError) {
|
|
44721
45136
|
return {
|
|
44722
45137
|
action: "continue",
|
|
@@ -44747,7 +45162,7 @@ class ReplEngine {
|
|
|
44747
45162
|
return isTransportFailure(error);
|
|
44748
45163
|
}
|
|
44749
45164
|
}
|
|
44750
|
-
var import_picocolors3;
|
|
45165
|
+
var import_picocolors3, STATEMENT_CONTINUATION;
|
|
44751
45166
|
var init_repl_engine = __esm(() => {
|
|
44752
45167
|
init_connection_error_message();
|
|
44753
45168
|
init_input_classifier();
|
|
@@ -44761,6 +45176,15 @@ var init_repl_engine = __esm(() => {
|
|
|
44761
45176
|
init_blacklist_validator();
|
|
44762
45177
|
init_blacklist();
|
|
44763
45178
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
45179
|
+
STATEMENT_CONTINUATION = new Set([
|
|
45180
|
+
"FROM",
|
|
45181
|
+
"INTO",
|
|
45182
|
+
"SET",
|
|
45183
|
+
"TABLE",
|
|
45184
|
+
"INDEX",
|
|
45185
|
+
"VIEW",
|
|
45186
|
+
"DATABASE"
|
|
45187
|
+
]);
|
|
44764
45188
|
});
|
|
44765
45189
|
|
|
44766
45190
|
// src/core/repl/completer.ts
|
|
@@ -44803,6 +45227,11 @@ function createCompleter(ctx) {
|
|
|
44803
45227
|
}
|
|
44804
45228
|
const words = trimmed.split(/\s+/);
|
|
44805
45229
|
if (words.length === 1) {
|
|
45230
|
+
if (lastWord.startsWith(COMMAND_PREFIX)) {
|
|
45231
|
+
const bare = lastWord.slice(COMMAND_PREFIX.length);
|
|
45232
|
+
const [hits] = matchWithSuffix([...ctx.commandNames], bare);
|
|
45233
|
+
return [hits.map((hit) => `${COMMAND_PREFIX}${hit}`), lastWord];
|
|
45234
|
+
}
|
|
44806
45235
|
const cmdHits = matchWithSuffix([...ctx.commandNames], lastWord);
|
|
44807
45236
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44808
45237
|
return [[...cmdHits[0], ...sqlHits[0]], lastWord];
|
|
@@ -44856,6 +45285,7 @@ function matchWithSuffix(candidates, prefix) {
|
|
|
44856
45285
|
var REDIS_COMMANDS, TABLE_POSITION_KEYWORDS, COMMANDS_TAKING_TABLE_ARG;
|
|
44857
45286
|
var init_completer = __esm(() => {
|
|
44858
45287
|
init_types11();
|
|
45288
|
+
init_input_classifier();
|
|
44859
45289
|
init_command_metadata();
|
|
44860
45290
|
REDIS_COMMANDS = Object.keys(REDIS_COMMAND_TABLE);
|
|
44861
45291
|
TABLE_POSITION_KEYWORDS = new Set([
|
|
@@ -45153,6 +45583,94 @@ var init_command_registry = __esm(() => {
|
|
|
45153
45583
|
REPL_DENYLIST = new Set(["shell"]);
|
|
45154
45584
|
});
|
|
45155
45585
|
|
|
45586
|
+
// src/commands/shell-prompt-asker.ts
|
|
45587
|
+
var exports_shell_prompt_asker = {};
|
|
45588
|
+
__export(exports_shell_prompt_asker, {
|
|
45589
|
+
createPromptAsker: () => createPromptAsker
|
|
45590
|
+
});
|
|
45591
|
+
function createPromptAsker(getInterface) {
|
|
45592
|
+
let pending = null;
|
|
45593
|
+
return {
|
|
45594
|
+
async ask(question) {
|
|
45595
|
+
const rl = getInterface();
|
|
45596
|
+
if (!rl)
|
|
45597
|
+
throw new Error("Write gate asked for confirmation before the shell started");
|
|
45598
|
+
return new Promise((resolve10) => {
|
|
45599
|
+
const controller = new AbortController;
|
|
45600
|
+
const settle = (answer) => {
|
|
45601
|
+
pending = null;
|
|
45602
|
+
rl.pause();
|
|
45603
|
+
resolve10(answer);
|
|
45604
|
+
};
|
|
45605
|
+
pending = {
|
|
45606
|
+
abort: () => {
|
|
45607
|
+
controller.abort();
|
|
45608
|
+
settle(null);
|
|
45609
|
+
}
|
|
45610
|
+
};
|
|
45611
|
+
try {
|
|
45612
|
+
rl.question(`${question}: `, { signal: controller.signal }, settle);
|
|
45613
|
+
} catch (error) {
|
|
45614
|
+
pending = null;
|
|
45615
|
+
throw error;
|
|
45616
|
+
}
|
|
45617
|
+
});
|
|
45618
|
+
},
|
|
45619
|
+
cancel() {
|
|
45620
|
+
if (!pending)
|
|
45621
|
+
return false;
|
|
45622
|
+
pending.abort();
|
|
45623
|
+
return true;
|
|
45624
|
+
}
|
|
45625
|
+
};
|
|
45626
|
+
}
|
|
45627
|
+
|
|
45628
|
+
// src/commands/shell-write-gate.ts
|
|
45629
|
+
var exports_shell_write_gate = {};
|
|
45630
|
+
__export(exports_shell_write_gate, {
|
|
45631
|
+
createShellWriteGate: () => createShellWriteGate
|
|
45632
|
+
});
|
|
45633
|
+
function createShellWriteGate(options) {
|
|
45634
|
+
const record4 = options.record ?? ((decision) => recordGateDecision({
|
|
45635
|
+
config: options.config,
|
|
45636
|
+
command: "shell",
|
|
45637
|
+
options: { config: options.configPath },
|
|
45638
|
+
verdict: decision.verdict,
|
|
45639
|
+
outcome: decision.outcome,
|
|
45640
|
+
tier: decision.tier,
|
|
45641
|
+
sql: decision.sql
|
|
45642
|
+
}));
|
|
45643
|
+
return async (sql) => {
|
|
45644
|
+
const verdict2 = await classifySqlWriteGate(sql, { dialect: options.dialect });
|
|
45645
|
+
if (verdict2.tier !== "two")
|
|
45646
|
+
return true;
|
|
45647
|
+
const decide5 = (outcome) => record4({ verdict: verdict2, outcome, tier: "two", sql });
|
|
45648
|
+
try {
|
|
45649
|
+
const proceed = await enforceWriteGate({
|
|
45650
|
+
verdict: verdict2,
|
|
45651
|
+
statement: sql,
|
|
45652
|
+
operation: verdict2.operation ?? "WRITE",
|
|
45653
|
+
human: { isTTY: process.stdin.isTTY === true },
|
|
45654
|
+
...options.ask && { ask: options.ask }
|
|
45655
|
+
});
|
|
45656
|
+
await decide5(proceed ? "allowed" : "declined");
|
|
45657
|
+
return proceed;
|
|
45658
|
+
} catch (error) {
|
|
45659
|
+
if (!(error instanceof WriteGateRefusal))
|
|
45660
|
+
throw error;
|
|
45661
|
+
await decide5("refused");
|
|
45662
|
+
process.stderr.write(`${error.message}
|
|
45663
|
+
`);
|
|
45664
|
+
return false;
|
|
45665
|
+
}
|
|
45666
|
+
};
|
|
45667
|
+
}
|
|
45668
|
+
var init_shell_write_gate = __esm(() => {
|
|
45669
|
+
init_write_gate();
|
|
45670
|
+
init_write_gate_prompt();
|
|
45671
|
+
init_write_gate_guard();
|
|
45672
|
+
});
|
|
45673
|
+
|
|
45156
45674
|
// src/commands/shell.ts
|
|
45157
45675
|
var exports_shell = {};
|
|
45158
45676
|
__export(exports_shell, {
|
|
@@ -45259,7 +45777,18 @@ async function runShell(options, configPath) {
|
|
|
45259
45777
|
columnsByTable,
|
|
45260
45778
|
commandNames
|
|
45261
45779
|
};
|
|
45262
|
-
|
|
45780
|
+
let replInterface = null;
|
|
45781
|
+
const { createPromptAsker: createPromptAsker2 } = await Promise.resolve().then(() => exports_shell_prompt_asker);
|
|
45782
|
+
const asker = createPromptAsker2(() => replInterface);
|
|
45783
|
+
const { toSqlDialect: toSqlDialect2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
45784
|
+
const dialect = toSqlDialect2(config.connection.system);
|
|
45785
|
+
const writeGate = dialect ? (await Promise.resolve().then(() => (init_shell_write_gate(), exports_shell_write_gate))).createShellWriteGate({
|
|
45786
|
+
config,
|
|
45787
|
+
configPath,
|
|
45788
|
+
dialect,
|
|
45789
|
+
ask: asker.ask
|
|
45790
|
+
}) : null;
|
|
45791
|
+
const engine = new ReplEngine(adapter, context, HISTORY_PATH, config, writeGate);
|
|
45263
45792
|
const complete = createCompleter(context);
|
|
45264
45793
|
console.error(import_picocolors5.default.bold(t_vars("shell.welcome", {
|
|
45265
45794
|
system: config.connection.system,
|
|
@@ -45293,10 +45822,22 @@ async function runShell(options, configPath) {
|
|
|
45293
45822
|
completer: (line) => complete(line),
|
|
45294
45823
|
terminal: process.stdin.isTTY ?? false
|
|
45295
45824
|
});
|
|
45825
|
+
replInterface = rl;
|
|
45296
45826
|
const continuationPrompt = import_picocolors5.default.dim(t("shell.continuation_prompt") + "> ");
|
|
45297
45827
|
rl.prompt();
|
|
45298
|
-
|
|
45299
|
-
|
|
45828
|
+
const handleLine = async (line) => {
|
|
45829
|
+
rl.pause();
|
|
45830
|
+
let result;
|
|
45831
|
+
try {
|
|
45832
|
+
result = await engine.processInput(line);
|
|
45833
|
+
} catch (error) {
|
|
45834
|
+
console.error(import_picocolors5.default.red(t_vars("shell.error_sql_failed", { message: error.message })));
|
|
45835
|
+
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45836
|
+
rl.prompt();
|
|
45837
|
+
return;
|
|
45838
|
+
} finally {
|
|
45839
|
+
rl.resume();
|
|
45840
|
+
}
|
|
45300
45841
|
switch (result.action) {
|
|
45301
45842
|
case "quit":
|
|
45302
45843
|
if (result.output)
|
|
@@ -45307,16 +45848,21 @@ async function runShell(options, configPath) {
|
|
|
45307
45848
|
console.clear();
|
|
45308
45849
|
break;
|
|
45309
45850
|
case "multiline":
|
|
45310
|
-
|
|
45851
|
+
if (result.output)
|
|
45852
|
+
console.error(result.output);
|
|
45311
45853
|
break;
|
|
45312
45854
|
case "continue":
|
|
45313
45855
|
if (result.output) {
|
|
45314
45856
|
console.log(result.output);
|
|
45315
45857
|
}
|
|
45316
|
-
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45317
45858
|
break;
|
|
45318
45859
|
}
|
|
45860
|
+
rl.setPrompt(engine.isMultiline() ? continuationPrompt : import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45319
45861
|
rl.prompt();
|
|
45862
|
+
};
|
|
45863
|
+
let pending = Promise.resolve();
|
|
45864
|
+
rl.on("line", (line) => {
|
|
45865
|
+
pending = pending.then(() => handleLine(line));
|
|
45320
45866
|
});
|
|
45321
45867
|
rl.on("close", async () => {
|
|
45322
45868
|
console.error(import_picocolors5.default.dim(t("shell.goodbye")));
|
|
@@ -45325,7 +45871,10 @@ async function runShell(options, configPath) {
|
|
|
45325
45871
|
process.exit(0);
|
|
45326
45872
|
});
|
|
45327
45873
|
rl.on("SIGINT", () => {
|
|
45874
|
+
if (asker.cancel())
|
|
45875
|
+
return;
|
|
45328
45876
|
if (engine.isMultiline()) {
|
|
45877
|
+
engine.cancelMultiline();
|
|
45329
45878
|
console.error(import_picocolors5.default.dim(t("shell.multiline_cancelled")));
|
|
45330
45879
|
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45331
45880
|
}
|
|
@@ -45341,7 +45890,10 @@ async function runBatchSession(engine, input) {
|
|
|
45341
45890
|
continue;
|
|
45342
45891
|
const result = await engine.processInput(line);
|
|
45343
45892
|
if (result.output) {
|
|
45344
|
-
|
|
45893
|
+
if (result.action === "multiline")
|
|
45894
|
+
console.error(result.output);
|
|
45895
|
+
else
|
|
45896
|
+
console.log(result.output);
|
|
45345
45897
|
}
|
|
45346
45898
|
if (result.action === "quit") {
|
|
45347
45899
|
return;
|
|
@@ -49354,11 +49906,19 @@ function assertVerificationSubjects(subject, claims) {
|
|
|
49354
49906
|
throw new EvidencePackValidationError("verification evidence must match the claims subject kind");
|
|
49355
49907
|
}
|
|
49356
49908
|
}
|
|
49357
|
-
function
|
|
49358
|
-
|
|
49909
|
+
function canonicalize(value) {
|
|
49910
|
+
if (value === null || typeof value !== "object")
|
|
49911
|
+
return JSON.stringify(value) ?? "null";
|
|
49912
|
+
if (Array.isArray(value))
|
|
49913
|
+
return `[${value.map(canonicalize).join(",")}]`;
|
|
49914
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, v]) => `${JSON.stringify(key)}:${canonicalize(v)}`);
|
|
49915
|
+
return `{${entries.join(",")}}`;
|
|
49916
|
+
}
|
|
49917
|
+
function digest(content) {
|
|
49918
|
+
return createHash8("sha256").update(canonicalize(content)).digest("hex");
|
|
49359
49919
|
}
|
|
49360
|
-
function
|
|
49361
|
-
return
|
|
49920
|
+
function packId(contentDigest) {
|
|
49921
|
+
return `evp_${contentDigest.slice(0, 32)}`;
|
|
49362
49922
|
}
|
|
49363
49923
|
function parseEvidenceClaimsInput(raw) {
|
|
49364
49924
|
if (!isRecord4(raw))
|
|
@@ -49393,49 +49953,52 @@ function buildEvidencePack(input, references, options = {}) {
|
|
|
49393
49953
|
if (evidence2.some((reference) => reference.kind === "verification-artifact" && reference.subjectKind !== normalized2.subject.kind)) {
|
|
49394
49954
|
throw new EvidencePackValidationError("verification evidence must match the claims subject kind");
|
|
49395
49955
|
}
|
|
49396
|
-
const
|
|
49956
|
+
const content = {
|
|
49397
49957
|
version: EVIDENCE_PACK_VERSION,
|
|
49398
|
-
id: id(options.idFactory?.() ?? `evp_${randomUUID6()}`, "pack.id"),
|
|
49399
|
-
createdAt: (options.now?.() ?? new Date).toISOString(),
|
|
49400
49958
|
subject: normalized2.subject,
|
|
49401
|
-
claims: normalized2.claims.map((claim) => ({ ...claim, evidence: evidence2 }))
|
|
49402
|
-
|
|
49959
|
+
claims: normalized2.claims.map((claim) => ({ ...claim, evidence: evidence2 }))
|
|
49960
|
+
};
|
|
49961
|
+
const contentDigest = digest(content);
|
|
49962
|
+
return {
|
|
49963
|
+
...content,
|
|
49964
|
+
id: packId(contentDigest),
|
|
49965
|
+
createdAt: (options.now?.() ?? new Date).toISOString(),
|
|
49966
|
+
integrity: { algorithm: "sha256", digest: contentDigest }
|
|
49403
49967
|
};
|
|
49404
|
-
return { ...base, integrity: { algorithm: "sha256", digest: digest(base) } };
|
|
49405
49968
|
}
|
|
49406
49969
|
function parseEvidencePack(raw) {
|
|
49407
49970
|
if (!isRecord4(raw))
|
|
49408
49971
|
throw new EvidencePackValidationError("evidence pack must be an object");
|
|
49409
|
-
requireExactKeys(raw, ["version", "id", "createdAt", "subject", "claims", "
|
|
49972
|
+
requireExactKeys(raw, ["version", "id", "createdAt", "subject", "claims", "integrity"], "evidence pack");
|
|
49410
49973
|
if (raw.version !== EVIDENCE_PACK_VERSION) {
|
|
49411
49974
|
throw new EvidencePackValidationError(`evidence pack version must be ${EVIDENCE_PACK_VERSION}`);
|
|
49412
49975
|
}
|
|
49413
|
-
if (!isRecord4(raw.coverage))
|
|
49414
|
-
throw new EvidencePackValidationError("coverage must be an object");
|
|
49415
|
-
requireExactKeys(raw.coverage, ["completeForDeclaredEvidence", "gaps"], "coverage");
|
|
49416
|
-
if (raw.coverage.completeForDeclaredEvidence !== true || !Array.isArray(raw.coverage.gaps) || raw.coverage.gaps.length !== 0) {
|
|
49417
|
-
throw new EvidencePackValidationError("v1 coverage must be complete for declared evidence with no gaps");
|
|
49418
|
-
}
|
|
49419
49976
|
if (!isRecord4(raw.integrity))
|
|
49420
49977
|
throw new EvidencePackValidationError("integrity must be an object");
|
|
49421
49978
|
requireExactKeys(raw.integrity, ["algorithm", "digest"], "integrity");
|
|
49422
49979
|
if (raw.integrity.algorithm !== "sha256")
|
|
49423
49980
|
throw new EvidencePackValidationError("integrity.algorithm must be sha256");
|
|
49424
|
-
const
|
|
49981
|
+
const content = {
|
|
49425
49982
|
version: EVIDENCE_PACK_VERSION,
|
|
49426
|
-
id: id(raw.id, "pack.id"),
|
|
49427
|
-
createdAt: iso(raw.createdAt, "pack.createdAt"),
|
|
49428
49983
|
subject: parseSubject(raw.subject),
|
|
49429
|
-
claims: parseClaims(raw.claims, true)
|
|
49430
|
-
coverage: { completeForDeclaredEvidence: true, gaps: [] }
|
|
49984
|
+
claims: parseClaims(raw.claims, true)
|
|
49431
49985
|
};
|
|
49432
|
-
assertVerificationSubjects(
|
|
49986
|
+
assertVerificationSubjects(content.subject, content.claims);
|
|
49433
49987
|
const actualDigest = text4(raw.integrity.digest, "integrity.digest", 128);
|
|
49434
49988
|
if (!/^[a-f0-9]{64}$/.test(actualDigest))
|
|
49435
49989
|
throw new EvidencePackValidationError("integrity.digest must be sha256 hex");
|
|
49436
|
-
|
|
49990
|
+
const expectedDigest = digest(content);
|
|
49991
|
+
if (actualDigest !== expectedDigest)
|
|
49437
49992
|
throw new EvidencePackValidationError("evidence pack digest mismatch");
|
|
49438
|
-
|
|
49993
|
+
const packIdentity = id(raw.id, "pack.id");
|
|
49994
|
+
if (packIdentity !== packId(expectedDigest))
|
|
49995
|
+
throw new EvidencePackValidationError("evidence pack id does not match its content digest");
|
|
49996
|
+
return {
|
|
49997
|
+
...content,
|
|
49998
|
+
id: packIdentity,
|
|
49999
|
+
createdAt: iso(raw.createdAt, "pack.createdAt"),
|
|
50000
|
+
integrity: { algorithm: "sha256", digest: actualDigest }
|
|
50001
|
+
};
|
|
49439
50002
|
}
|
|
49440
50003
|
function isInside(root, target) {
|
|
49441
50004
|
const relativePath = relative5(root, target);
|
|
@@ -49558,7 +50121,9 @@ var init_evidence_pack = __esm(() => {
|
|
|
49558
50121
|
// src/commands/evidence.ts
|
|
49559
50122
|
var exports_evidence = {};
|
|
49560
50123
|
__export(exports_evidence, {
|
|
49561
|
-
evidenceCommand: () => evidenceCommand
|
|
50124
|
+
evidenceCommand: () => evidenceCommand,
|
|
50125
|
+
containsBlockedTerm: () => containsBlockedTerm,
|
|
50126
|
+
blockedTerms: () => blockedTerms
|
|
49562
50127
|
});
|
|
49563
50128
|
import { join as join42, relative as relative6, resolve as resolve12, sep as sep8 } from "path";
|
|
49564
50129
|
import { realpath as realpath3 } from "fs/promises";
|
|
@@ -49581,24 +50146,34 @@ async function readClaimsInput(filePath) {
|
|
|
49581
50146
|
function blockedTerms(config) {
|
|
49582
50147
|
return [
|
|
49583
50148
|
...config.blacklist?.tables ?? [],
|
|
50149
|
+
...Object.keys(config.blacklist?.columns ?? {}),
|
|
49584
50150
|
...Object.values(config.blacklist?.columns ?? {}).flat()
|
|
49585
50151
|
].map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0);
|
|
49586
50152
|
}
|
|
49587
|
-
function
|
|
50153
|
+
function containsBlockedTerm(value, terms) {
|
|
50154
|
+
const normalized2 = value.toLowerCase();
|
|
50155
|
+
return terms.some((term) => {
|
|
50156
|
+
const trimmed = term.trim().toLowerCase();
|
|
50157
|
+
if (trimmed.length === 0)
|
|
50158
|
+
return false;
|
|
50159
|
+
const escaped = trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50160
|
+
return new RegExp(`(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])`).test(normalized2);
|
|
50161
|
+
});
|
|
50162
|
+
}
|
|
50163
|
+
function assertNoBlockedText(value, terms, field) {
|
|
49588
50164
|
if (!value)
|
|
49589
50165
|
return;
|
|
49590
|
-
|
|
49591
|
-
|
|
49592
|
-
throw new EvidencePackValidationError("evidence content contains a blocked identifier");
|
|
50166
|
+
if (containsBlockedTerm(value, terms)) {
|
|
50167
|
+
throw new EvidencePackValidationError(`${field} contains a blocked identifier`);
|
|
49593
50168
|
}
|
|
49594
50169
|
}
|
|
49595
50170
|
function assertClaimsSafe(raw, terms) {
|
|
49596
50171
|
const input = parseEvidenceClaimsInput(raw);
|
|
49597
|
-
assertNoBlockedText(input.subject.kind, terms);
|
|
49598
|
-
assertNoBlockedText(input.subject.name, terms);
|
|
49599
|
-
for (const claim of input.claims) {
|
|
49600
|
-
assertNoBlockedText(claim.id, terms);
|
|
49601
|
-
assertNoBlockedText(claim.text, terms);
|
|
50172
|
+
assertNoBlockedText(input.subject.kind, terms, "subject.kind");
|
|
50173
|
+
assertNoBlockedText(input.subject.name, terms, "subject.name");
|
|
50174
|
+
for (const [index, claim] of input.claims.entries()) {
|
|
50175
|
+
assertNoBlockedText(claim.id, terms, `claim ${index + 1} id`);
|
|
50176
|
+
assertNoBlockedText(claim.text, terms, `claim ${index + 1} text`);
|
|
49602
50177
|
}
|
|
49603
50178
|
}
|
|
49604
50179
|
function sameReference(left, right) {
|
|
@@ -50788,7 +51363,7 @@ var init_change_set = __esm(() => {
|
|
|
50788
51363
|
import { createReadStream } from "fs";
|
|
50789
51364
|
import { lstat as lstat7 } from "fs/promises";
|
|
50790
51365
|
import { createInterface as createInterface3 } from "readline";
|
|
50791
|
-
async function
|
|
51366
|
+
async function loadWorkloadSource(options) {
|
|
50792
51367
|
const now = options.now ?? new Date;
|
|
50793
51368
|
let info;
|
|
50794
51369
|
try {
|
|
@@ -51169,7 +51744,7 @@ function semanticSchema(schema, blocked) {
|
|
|
51169
51744
|
}
|
|
51170
51745
|
return result;
|
|
51171
51746
|
}
|
|
51172
|
-
async function
|
|
51747
|
+
async function loadObservedWorkloadSource(path4, workspaceRoot, blocked) {
|
|
51173
51748
|
if (path4 === undefined) {
|
|
51174
51749
|
return {
|
|
51175
51750
|
state: "absent",
|
|
@@ -51179,7 +51754,7 @@ async function loadObservedWorkloadEvidence(path4, workspaceRoot, blocked) {
|
|
|
51179
51754
|
issues: []
|
|
51180
51755
|
};
|
|
51181
51756
|
}
|
|
51182
|
-
return
|
|
51757
|
+
return loadWorkloadSource({ path: resolve14(workspaceRoot, path4), blockedIdentifiers: blocked });
|
|
51183
51758
|
}
|
|
51184
51759
|
function shouldFail(report, failOn) {
|
|
51185
51760
|
if (failOn === "never")
|
|
@@ -51317,7 +51892,7 @@ var init_impact2 = __esm(() => {
|
|
|
51317
51892
|
const contracts = await loadContractEvidence(workspaceRoot, semantic, baseline, savedQueries, blockedIdentifiers);
|
|
51318
51893
|
const dataAccess = await loadDataAccessEvidence(workspaceRoot, semantic, baseline, savedQueries, blockedIdentifiers);
|
|
51319
51894
|
const verifications = await loadVerificationEvidence(workspaceRoot, blockedIdentifiers);
|
|
51320
|
-
const observedWorkload = await
|
|
51895
|
+
const observedWorkload = await loadObservedWorkloadSource(options.events, workspaceRoot, blockedIdentifiers);
|
|
51321
51896
|
const report = assessImpact({
|
|
51322
51897
|
changes,
|
|
51323
51898
|
semantic,
|