@runa-ai/runa-cli 0.7.2 → 0.7.3

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.
@@ -16,7 +16,7 @@ init_esm_shims();
16
16
 
17
17
  // src/constants/versions.ts
18
18
  init_esm_shims();
19
- var COMPATIBLE_TEMPLATES_VERSION = "0.7.2";
19
+ var COMPATIBLE_TEMPLATES_VERSION = "0.7.3";
20
20
  var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
21
21
  var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
22
22
 
@@ -6,7 +6,7 @@ createRequire(import.meta.url);
6
6
 
7
7
  // src/version.ts
8
8
  init_esm_shims();
9
- var CLI_VERSION = "0.7.2";
9
+ var CLI_VERSION = "0.7.3";
10
10
  var HAS_ADMIN_COMMAND = false;
11
11
 
12
12
  export { CLI_VERSION, HAS_ADMIN_COMMAND };
@@ -9,7 +9,7 @@ init_esm_shims();
9
9
  var riskDetectorModulePromise = null;
10
10
  async function loadRiskDetectorModule() {
11
11
  if (!riskDetectorModulePromise) {
12
- riskDetectorModulePromise = import('./risk-detector-core-7WZJZ5ZI.js').catch((error) => {
12
+ riskDetectorModulePromise = import('./risk-detector-core-TGFKWHRS.js').catch((error) => {
13
13
  riskDetectorModulePromise = null;
14
14
  throw error;
15
15
  });
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'module';
3
3
  import { enableNonInteractiveMode } from './chunk-6Y3LAUGL.js';
4
4
  import { getRequestedCommandNameFromArgv } from './chunk-UWWSAPDR.js';
5
- import { CLI_VERSION, HAS_ADMIN_COMMAND } from './chunk-PMXE5XOJ.js';
5
+ import { CLI_VERSION, HAS_ADMIN_COMMAND } from './chunk-AO554K3G.js';
6
6
  import { emitDefaultSuccessIfNeeded } from './chunk-WJXC4MVY.js';
7
7
  import { parseOutputFormat, setOutputFormat, getOutputFormatFromEnv } from './chunk-HKUWEGUX.js';
8
8
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
@@ -145,7 +145,7 @@ function isTestCommand(requested) {
145
145
  async function registerProjectLifecycleCommands(program, requested, loadAllCommands) {
146
146
  if (!loadAllCommands && requested) {
147
147
  if (requested === "init") {
148
- const { initCommand: initCommand2 } = await import('./init-S2ATHLJ6.js');
148
+ const { initCommand: initCommand2 } = await import('./init-35JLDFHI.js');
149
149
  program.addCommand(initCommand2);
150
150
  return;
151
151
  }
@@ -155,7 +155,7 @@ async function registerProjectLifecycleCommands(program, requested, loadAllComma
155
155
  return;
156
156
  }
157
157
  if (requested === "upgrade") {
158
- const { upgradeCommand: upgradeCommand2 } = await import('./upgrade-BDUWBRT5.js');
158
+ const { upgradeCommand: upgradeCommand2 } = await import('./upgrade-7L4JIE4K.js');
159
159
  program.addCommand(upgradeCommand2);
160
160
  return;
161
161
  }
@@ -183,9 +183,9 @@ async function registerProjectLifecycleCommands(program, requested, loadAllComma
183
183
  { buildCommand },
184
184
  { devCommand }
185
185
  ] = await Promise.all([
186
- import('./init-S2ATHLJ6.js'),
186
+ import('./init-35JLDFHI.js'),
187
187
  import('./prepare-32DOVHTE.js'),
188
- import('./upgrade-BDUWBRT5.js'),
188
+ import('./upgrade-7L4JIE4K.js'),
189
189
  import('./validate-CAAW4Y44.js'),
190
190
  import('./build-HQMSVN6N.js'),
191
191
  import('./dev-MLRKIP7F.js')
@@ -466,7 +466,7 @@ async function registerCiCommand(program) {
466
466
  program.addCommand(ciCommand);
467
467
  }
468
468
  async function registerDbCommand(program) {
469
- const { dbCommand } = await import('./db-BPQ2TEQM.js');
469
+ const { dbCommand } = await import('./db-S4V4ETDR.js');
470
470
  program.addCommand(dbCommand);
471
471
  }
472
472
  async function registerServicesCommand(program) {
@@ -490,7 +490,7 @@ async function registerUiCommand(program) {
490
490
  program.addCommand(uiCommand);
491
491
  }
492
492
  async function registerWatchCommand(program) {
493
- const { watchCommand } = await import('./watch-ITYW57SL.js');
493
+ const { watchCommand } = await import('./watch-AL4LCBRM.js');
494
494
  program.addCommand(watchCommand);
495
495
  }
496
496
  async function registerWorkflowCommand(program) {
@@ -498,7 +498,7 @@ async function registerWorkflowCommand(program) {
498
498
  program.addCommand(workflowCommand);
499
499
  }
500
500
  async function registerVulnCheckCommand(program) {
501
- const { vulnCheckCommand } = await import('./vuln-check-66RXX3TO.js');
501
+ const { vulnCheckCommand } = await import('./vuln-check-D575VXIQ.js');
502
502
  program.addCommand(vulnCheckCommand);
503
503
  }
504
504
  async function registerTemplateCheckCommand(program) {
@@ -27,6 +27,7 @@ export type DirectoryPlacementAllowlistRule = {
27
27
  id: string;
28
28
  filePattern: RegExp;
29
29
  messagePattern: RegExp;
30
+ objectPattern?: RegExp;
30
31
  level?: BoundaryPolicyRiskLevel;
31
32
  lineStart?: number;
32
33
  lineEnd?: number;
@@ -65,6 +66,7 @@ export type RawRule = {
65
66
  code?: unknown;
66
67
  filePattern?: unknown;
67
68
  anchorPattern?: unknown;
69
+ objectPattern?: unknown;
68
70
  descriptionPattern?: unknown;
69
71
  messagePattern?: unknown;
70
72
  level?: unknown;
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * Sync strategy: Keep this in sync with packages/runa-templates/package.json version.
22
22
  */
23
- export declare const COMPATIBLE_TEMPLATES_VERSION = "0.7.2";
23
+ export declare const COMPATIBLE_TEMPLATES_VERSION = "0.7.3";
24
24
  /**
25
25
  * Templates package name on GitHub Packages.
26
26
  * Published to npm.pkg.github.com (requires NODE_AUTH_TOKEN).
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { detectDatabaseStack, getStackPaths } from './chunk-CCKG5R4Y.js';
4
- import { categorizeRisks, detectSchemaRisks } from './chunk-LCK2LGVR.js';
4
+ import { categorizeRisks, detectSchemaRisks } from './chunk-PAWNJA3N.js';
5
5
  import './chunk-ZZOXM6Q4.js';
6
6
  import { createError } from './chunk-JQXOVCOP.js';
7
7
  import { resolveDatabaseUrl, resolveDatabaseTarget, tryResolveDatabaseUrl } from './chunk-CKRLVEIO.js';
@@ -5947,7 +5947,7 @@ function logIdempotentRiskSummary(summary) {
5947
5947
  async function detectIdempotentRiskSummary(schemasDir, files, verbose) {
5948
5948
  const summary = emptyRiskSummary();
5949
5949
  try {
5950
- const { detectSchemaRisks: detectSchemaRisks2 } = await import('./risk-detector-VO5HJR4R.js');
5950
+ const { detectSchemaRisks: detectSchemaRisks2 } = await import('./risk-detector-S7XQF4I2.js');
5951
5951
  for (const file of files) {
5952
5952
  const filePath = join(schemasDir, file);
5953
5953
  const risks = await detectSchemaRisks2(filePath);
@@ -6673,8 +6673,28 @@ function buildDirectoryPlacementRule(entry, index, seenIds, issueFormatter) {
6673
6673
  issueFormatter
6674
6674
  );
6675
6675
  reportDeprecatedLineUsage(id, line, lineStart, lineEnd, issueFormatter);
6676
- const scopedLineRange = toScopedLineRange(lineStart, lineEnd, id, section, issueFormatter);
6677
- if (!scopedLineRange) return null;
6676
+ const objectPatternStr = coerceOptionalString(
6677
+ rawRule.objectPattern,
6678
+ index,
6679
+ `directoryPlacementAllowlist[${index}].objectPattern`,
6680
+ issueFormatter
6681
+ );
6682
+ const compiledObjectPattern = compileOptionalRulePattern(
6683
+ objectPatternStr ?? null,
6684
+ issueFormatter
6685
+ );
6686
+ if (objectPatternStr && !compiledObjectPattern) return null;
6687
+ const hasObjectPattern = compiledObjectPattern !== void 0;
6688
+ let scopedLineRange;
6689
+ if (!hasObjectPattern) {
6690
+ scopedLineRange = toScopedLineRange(lineStart, lineEnd, id, section, issueFormatter);
6691
+ if (!scopedLineRange) return null;
6692
+ } else {
6693
+ if (lineStart !== void 0 && lineEnd !== void 0) {
6694
+ scopedLineRange = toScopedLineRange(lineStart, lineEnd, id, section, issueFormatter);
6695
+ if (!scopedLineRange) return null;
6696
+ }
6697
+ }
6678
6698
  const expiresAt = coerceExpiresAt(
6679
6699
  rawRule.expiresAt,
6680
6700
  id,
@@ -6692,9 +6712,10 @@ function buildDirectoryPlacementRule(entry, index, seenIds, issueFormatter) {
6692
6712
  id,
6693
6713
  filePattern: compiledPatterns[0],
6694
6714
  messagePattern: compiledPatterns[1],
6715
+ objectPattern: compiledObjectPattern,
6695
6716
  level,
6696
- lineStart: scopedLineRange.lineStart,
6697
- lineEnd: scopedLineRange.lineEnd,
6717
+ lineStart: scopedLineRange?.lineStart,
6718
+ lineEnd: scopedLineRange?.lineEnd,
6698
6719
  reason,
6699
6720
  owner,
6700
6721
  ticket,
@@ -7044,6 +7065,10 @@ function findDirectoryPlacementAllowlistMatch(issue, policy) {
7044
7065
  if (entry.level !== void 0 && entry.level !== issue.level) return false;
7045
7066
  if (!entry.filePattern.test(issue.file)) return false;
7046
7067
  if (!entry.messagePattern.test(issue.message)) return false;
7068
+ if (entry.objectPattern) {
7069
+ if (entry.objectPattern.test(issue.message)) return true;
7070
+ if (!hasExplicitLineScope(entry)) return false;
7071
+ }
7047
7072
  if (!hasExplicitLineScope(entry)) return false;
7048
7073
  return entryLineScopeMatches(issue.line, entry);
7049
7074
  });
@@ -11996,7 +12021,9 @@ var IDEMPOTENT_DOWNGRADE_REASON_CODES = /* @__PURE__ */ new Set([
11996
12021
  "MEDIUM_RISK_DROP_SEQUENCE",
11997
12022
  "MEDIUM_RISK_DROP_INDEX",
11998
12023
  "MEDIUM_RISK_REVOKE",
11999
- "PLPGSQL_DO_BLOCK_DETECTED"
12024
+ "PLPGSQL_DO_BLOCK_DETECTED",
12025
+ "PLPGSQL_EXECUTE_BOUNDED_DISPATCH",
12026
+ "GUARDED_DDL_ADD_CONSTRAINT"
12000
12027
  ]);
12001
12028
  var IDEMPOTENT_MEDIUM_REASON_CODES = /* @__PURE__ */ new Set([
12002
12029
  "PLPGSQL_EXECUTE_UNRESOLVED",
@@ -12017,7 +12044,7 @@ init_esm_shims();
12017
12044
  var riskDetectorLoader = null;
12018
12045
  function loadRiskDetectorModule() {
12019
12046
  if (!riskDetectorLoader) {
12020
- riskDetectorLoader = import('./risk-detector-VO5HJR4R.js').then((module) => ({
12047
+ riskDetectorLoader = import('./risk-detector-S7XQF4I2.js').then((module) => ({
12021
12048
  detectSchemaRisks: module.detectSchemaRisks
12022
12049
  })).catch((error) => {
12023
12050
  riskDetectorLoader = null;
@@ -12130,6 +12157,7 @@ function formatCollectedIdempotentRisks(collected, allowlist) {
12130
12157
  }
12131
12158
  const high = collected.filter((r) => r.level === "high");
12132
12159
  const medium = collected.filter((r) => r.level === "medium");
12160
+ const low = collected.filter((r) => r.level === "low");
12133
12161
  const blockers = [];
12134
12162
  const warnings = [];
12135
12163
  for (const risk of high) {
@@ -12138,6 +12166,9 @@ function formatCollectedIdempotentRisks(collected, allowlist) {
12138
12166
  for (const risk of medium) {
12139
12167
  warnings.push(` [MEDIUM] ${formatDeclarativeRiskMessage(risk)}`);
12140
12168
  }
12169
+ for (const risk of low) {
12170
+ warnings.push(` [LOW] ${formatDeclarativeRiskMessage(risk)}`);
12171
+ }
12141
12172
  return {
12142
12173
  blockers: dedupeAndSort(blockers),
12143
12174
  warnings: dedupeAndSort(warnings),
@@ -12198,6 +12229,45 @@ async function processDeclarativeRiskFile(params) {
12198
12229
  });
12199
12230
  return { kind: "ok", detector: detectorResult.detector };
12200
12231
  }
12232
+ var CREATE_POLICY_NAME_PATTERN = /\bCREATE\s+(?:OR\s+REPLACE\s+)?POLICY\s+(?:"([^"]+)"|([A-Za-z_]\w*))/gi;
12233
+ var DROP_POLICY_NAME_LINE_PATTERN = /\bDROP\s+POLICY\s+IF\s+EXISTS\s+(?:"([^"]+)"|([A-Za-z_]\w*))/i;
12234
+ function extractCreatePolicyNames(content) {
12235
+ const names = /* @__PURE__ */ new Set();
12236
+ CREATE_POLICY_NAME_PATTERN.lastIndex = 0;
12237
+ let match = CREATE_POLICY_NAME_PATTERN.exec(content);
12238
+ while (match !== null) {
12239
+ const name = (match[1] ?? match[2] ?? "").toLowerCase();
12240
+ if (name) names.add(name);
12241
+ match = CREATE_POLICY_NAME_PATTERN.exec(content);
12242
+ }
12243
+ CREATE_POLICY_NAME_PATTERN.lastIndex = 0;
12244
+ return names;
12245
+ }
12246
+ function detectRecreatePairs(risks, absoluteFilePath) {
12247
+ const dropPolicyRisks = risks.filter((r) => r.reasonCode === "HIGH_RISK_DROP_POLICY");
12248
+ if (dropPolicyRisks.length === 0) return;
12249
+ let content;
12250
+ try {
12251
+ content = readFileSync(absoluteFilePath, "utf-8");
12252
+ } catch {
12253
+ return;
12254
+ }
12255
+ const createPolicyNames = extractCreatePolicyNames(content);
12256
+ if (createPolicyNames.size === 0) return;
12257
+ const lines = content.split("\n");
12258
+ for (const risk of dropPolicyRisks) {
12259
+ if (risk.line === void 0) continue;
12260
+ const startIdx = Math.max(0, risk.line - 2);
12261
+ const endIdx = Math.min(lines.length, risk.line + 1);
12262
+ const windowText = lines.slice(startIdx, endIdx).join(" ");
12263
+ const nameMatch = DROP_POLICY_NAME_LINE_PATTERN.exec(windowText);
12264
+ if (!nameMatch) continue;
12265
+ const dropName = (nameMatch[1] ?? nameMatch[2] ?? "").toLowerCase();
12266
+ if (dropName && createPolicyNames.has(dropName)) {
12267
+ risk.level = "low";
12268
+ }
12269
+ }
12270
+ }
12201
12271
  async function collectDeclarativeRiskReport() {
12202
12272
  const declarativeDir = path15.join(process.cwd(), "supabase", "schemas", "declarative");
12203
12273
  if (!existsSync(declarativeDir)) {
@@ -12266,9 +12336,13 @@ async function collectIdempotentRiskReport() {
12266
12336
  const risks = await detectSchemaRisks2(file);
12267
12337
  if (risks.length === 0) continue;
12268
12338
  const relPath = path15.relative(process.cwd(), file);
12269
- for (const risk of risks) {
12270
- const level = correctIdempotentRiskLevel(risk.level, risk.reasonCode);
12271
- const scopedRisk = { ...risk, level, file: relPath };
12339
+ const fileRisks = risks.map((risk) => ({
12340
+ ...risk,
12341
+ level: correctIdempotentRiskLevel(risk.level, risk.reasonCode),
12342
+ file: relPath
12343
+ }));
12344
+ detectRecreatePairs(fileRisks, file);
12345
+ for (const scopedRisk of fileRisks) {
12272
12346
  const matched = findDeclarativeRiskAllowlistMatch(scopedRisk, policy);
12273
12347
  if (matched) {
12274
12348
  if (SHOW_ALLOWLIST_REPORT2) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { getRequestedCommandNameFromArgv } from './chunk-UWWSAPDR.js';
4
- import { CLI_VERSION } from './chunk-PMXE5XOJ.js';
4
+ import { CLI_VERSION } from './chunk-AO554K3G.js';
5
5
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
6
6
  import { realpathSync } from 'fs';
7
7
  import { fileURLToPath } from 'url';
@@ -36,7 +36,7 @@ async function getProgram(options) {
36
36
  };
37
37
  const nextKey = getProgramCacheKey(resolvedOptions);
38
38
  if (!programInstance || programCacheKey !== nextKey) {
39
- const { createProgram } = await import('./cli-Q2XIQDRS.js');
39
+ const { createProgram } = await import('./cli-SVXOSMW6.js');
40
40
  programInstance = await createProgram(resolvedOptions);
41
41
  programCacheKey = nextKey;
42
42
  }
@@ -60,7 +60,7 @@ async function runCliFromProcessArgv() {
60
60
  return;
61
61
  }
62
62
  const { setupSignalHandlers } = await import('./signal-handler-DO3OANW5.js');
63
- const { executeProgram } = await import('./cli-Q2XIQDRS.js');
63
+ const { executeProgram } = await import('./cli-SVXOSMW6.js');
64
64
  setupSignalHandlers();
65
65
  const options = getProgramLoadOptions(argv);
66
66
  const program = await getProgram(options);
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'module';
3
3
  import { diagnoseInitFailure } from './chunk-AAIE4F2U.js';
4
4
  import { getVercelRootDirectory } from './chunk-MXRWBNIY.js';
5
- import { fetchTemplates } from './chunk-Z7A4BEWF.js';
5
+ import { fetchTemplates } from './chunk-3JO6YP3T.js';
6
6
  import { syncRunaConfigWithVercel } from './chunk-6AALH2ED.js';
7
7
  import './chunk-DRSUEMAK.js';
8
8
  import './chunk-RZLYEO4U.js';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- export { categorizeRisks, detectSchemaRisks } from './chunk-LCK2LGVR.js';
3
+ export { categorizeRisks, detectSchemaRisks } from './chunk-PAWNJA3N.js';
4
4
  import './chunk-VRXHCR5K.js';
5
5
 
6
6
  createRequire(import.meta.url);
@@ -54,7 +54,7 @@ var UNQUALIFIED_EXTENSION_REFERENCE_PATTERNS = [
54
54
  var plpgsqlModulePromise = null;
55
55
  async function loadPlpgsqlRiskDetectorModule() {
56
56
  if (!plpgsqlModulePromise) {
57
- plpgsqlModulePromise = import('./risk-detector-plpgsql-ULV7NLDB.js').catch((error) => {
57
+ plpgsqlModulePromise = import('./risk-detector-plpgsql-O32TUR34.js').catch((error) => {
58
58
  plpgsqlModulePromise = null;
59
59
  throw error;
60
60
  });
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { splitPlpgsqlStatementsWithOffsets, extractExecuteExpressions, skipWhitespace, parseTopLevelAssignment, extractStaticSqlFromExpression, mergeStringEnvValue, skipIdentifier } from './chunk-Y5ANTCKE.js';
3
+ import { splitPlpgsqlStatementsWithOffsets, parseTopLevelAssignment, extractExecuteExpressions, skipWhitespace, extractStaticSqlFromExpression, mergeStringEnvValue, skipIdentifier } from './chunk-Y5ANTCKE.js';
4
4
  import { stripSqlCommentsPreserveLines, buildLineStarts, lineNumberFromIndex, stripSqlStringsPreserveLines, detectRisksFromContent, stripSqlForPatternMatching } from './chunk-3FDQW524.js';
5
5
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
6
6
 
@@ -537,13 +537,74 @@ function maybeBuildUsingClauseRisk(execute, executeLine) {
537
537
  }
538
538
  };
539
539
  }
540
+ var GUARD_START_PATTERN = /\bIF\s+NOT\s+EXISTS\s*\(/i;
541
+ var PG_CONSTRAINT_PATTERN = /\bpg_constraint\b/i;
542
+ var ADD_CONSTRAINT_PATTERN = /\bADD\s+CONSTRAINT\b/i;
543
+ var END_IF_PATTERN = /\bEND\s+IF\b/i;
544
+ var THEN_PATTERN = /\bTHEN\b/i;
545
+ function detectGuardedDdlLines(body) {
546
+ const guardedLines = /* @__PURE__ */ new Set();
547
+ const lines = body.split("\n");
548
+ const guardedRanges = [];
549
+ for (let i = 0; i < lines.length; i++) {
550
+ if (!GUARD_START_PATTERN.test(lines[i])) continue;
551
+ let foundPgConstraint = false;
552
+ let thenLine = -1;
553
+ const scanEnd = Math.min(i + 20, lines.length);
554
+ for (let j = i; j < scanEnd; j++) {
555
+ if (PG_CONSTRAINT_PATTERN.test(lines[j])) foundPgConstraint = true;
556
+ if (THEN_PATTERN.test(lines[j])) {
557
+ thenLine = j;
558
+ break;
559
+ }
560
+ }
561
+ if (!foundPgConstraint || thenLine < 0) continue;
562
+ const endScan = Math.min(thenLine + 30, lines.length);
563
+ for (let j = thenLine + 1; j < endScan; j++) {
564
+ if (END_IF_PATTERN.test(lines[j])) {
565
+ guardedRanges.push({ thenLine: thenLine + 1, endIfLine: j + 1 });
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ for (let i = 0; i < lines.length; i++) {
571
+ if (!ADD_CONSTRAINT_PATTERN.test(lines[i])) continue;
572
+ const line1 = i + 1;
573
+ for (const range of guardedRanges) {
574
+ if (line1 >= range.thenLine && line1 <= range.endIfLine) {
575
+ guardedLines.add(line1);
576
+ break;
577
+ }
578
+ }
579
+ }
580
+ return guardedLines;
581
+ }
540
582
  function pushBodyStaticRisks(risks, dedupe, body, bodyStartLine) {
541
583
  const searchableBody = stripSqlStringsPreserveLines(body);
542
584
  if (!searchableBody.trim()) return;
543
585
  const bodyLineStarts = buildLineStarts(body);
544
586
  const bodyRisks = detectRisksFromContent(searchableBody, body, bodyLineStarts);
587
+ const guardedDdlLines = detectGuardedDdlLines(body);
545
588
  for (const risk of bodyRisks) {
546
- const line = bodyStartLine + ((risk.line ?? 1) - 1);
589
+ const bodyRelativeLine = risk.line ?? 1;
590
+ const line = bodyStartLine + (bodyRelativeLine - 1);
591
+ if (risk.reasonCode === "MEDIUM_RISK_ADD_UNIQUE" && guardedDdlLines.has(bodyRelativeLine)) {
592
+ const dedupeKey2 = `body:GUARDED_DDL_ADD_CONSTRAINT:${line}`;
593
+ if (dedupe.has(dedupeKey2)) continue;
594
+ dedupe.add(dedupeKey2);
595
+ risks.push({
596
+ ...risk,
597
+ level: "medium",
598
+ line,
599
+ reasonCode: "GUARDED_DDL_ADD_CONSTRAINT",
600
+ evidence: {
601
+ source: "plpgsql body",
602
+ snippet: body.trim().slice(0, 200),
603
+ detail: "ADD CONSTRAINT is guarded by IF NOT EXISTS (pg_constraint check)"
604
+ }
605
+ });
606
+ continue;
607
+ }
547
608
  const dedupeKey = `body:${risk.reasonCode ?? risk.description}:${line}`;
548
609
  if (dedupe.has(dedupeKey)) continue;
549
610
  dedupe.add(dedupeKey);
@@ -574,14 +635,40 @@ function buildUnresolvedExecuteRisk(execute, executeLine, reason) {
574
635
  }
575
636
  };
576
637
  }
577
- function analyzeExecuteStatement(risks, dedupe, content, lineStarts, range, statement, execute, stringEnv) {
638
+ function executeReferencesBoundedVars(expression, conditionallyRemoved) {
639
+ if (conditionallyRemoved.size === 0) return false;
640
+ const normalized = expression.toLowerCase();
641
+ for (const varName of conditionallyRemoved) {
642
+ if (normalized.includes(varName.toLowerCase())) return true;
643
+ }
644
+ return false;
645
+ }
646
+ function buildBoundedDispatchRisk(execute, executeLine, reason) {
647
+ return {
648
+ level: "medium",
649
+ description: `Bounded dynamic SQL dispatch in PL/pgSQL EXECUTE: ${reason}`,
650
+ mitigation: "SQL is constructed from a finite set of branches (IF/CASE). Review each branch for correctness.",
651
+ line: executeLine,
652
+ reasonCode: "PLPGSQL_EXECUTE_BOUNDED_DISPATCH",
653
+ confidence: "medium",
654
+ evidence: {
655
+ source: "extractStaticSqlFromExpression",
656
+ snippet: execute.expression.slice(0, 200),
657
+ detail: "EXECUTE expression uses variables assigned in conditional branches (bounded dispatch)"
658
+ }
659
+ };
660
+ }
661
+ function analyzeExecuteStatement(risks, dedupe, content, lineStarts, range, statement, execute, stringEnv, conditionallyRemoved = /* @__PURE__ */ new Set()) {
578
662
  const executeLine = buildExecuteLine(content, lineStarts, range, statement, execute);
579
663
  const executeKey = buildExecuteKey(range, statement, execute, executeLine);
580
664
  if (dedupe.has(executeKey)) return;
581
665
  dedupe.add(executeKey);
582
666
  const extracted = extractStaticSqlFromExpression(execute.expression, stringEnv);
583
667
  if (extracted.kind !== "static") {
584
- risks.push(buildUnresolvedExecuteRisk(execute, executeLine, extracted.reason));
668
+ const isBoundedDispatch = !execute.hasUsingClause && !execute.hasIntoClause && executeReferencesBoundedVars(execute.expression, conditionallyRemoved);
669
+ risks.push(
670
+ isBoundedDispatch ? buildBoundedDispatchRisk(execute, executeLine, extracted.reason) : buildUnresolvedExecuteRisk(execute, executeLine, extracted.reason)
671
+ );
585
672
  return;
586
673
  }
587
674
  risks.push(buildStaticExecuteRisk(execute, executeLine));
@@ -605,8 +692,18 @@ function analyzeBodyRange(risks, dedupe, content, commentlessContent, lineStarts
605
692
  }
606
693
  pushBodyStaticRisks(risks, dedupe, body, declarationLine);
607
694
  const stringEnv = /* @__PURE__ */ new Map();
695
+ const conditionallyRemoved = /* @__PURE__ */ new Set();
608
696
  for (const statement of splitPlpgsqlStatementsWithOffsets(body)) {
697
+ const beforeKeys = new Set(stringEnv.keys());
609
698
  applyStatementAssignment(statement, stringEnv);
699
+ const assignment = parseTopLevelAssignment(statement.statement);
700
+ if (assignment?.isConditionalContext) {
701
+ for (const key of beforeKeys) {
702
+ if (!stringEnv.has(key)) {
703
+ conditionallyRemoved.add(key);
704
+ }
705
+ }
706
+ }
610
707
  const executeStatements = extractExecuteExpressions(statement.statement);
611
708
  for (const execute of executeStatements) {
612
709
  analyzeExecuteStatement(
@@ -617,7 +714,8 @@ function analyzeBodyRange(risks, dedupe, content, commentlessContent, lineStarts
617
714
  range,
618
715
  statement,
619
716
  execute,
620
- stringEnv
717
+ stringEnv,
718
+ conditionallyRemoved
621
719
  );
622
720
  }
623
721
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { fetchTemplates } from './chunk-Z7A4BEWF.js';
3
+ import { fetchTemplates } from './chunk-3JO6YP3T.js';
4
4
  import { updateRunaConfigSdkVersion } from './chunk-6AALH2ED.js';
5
5
  import './chunk-DRSUEMAK.js';
6
6
  import './chunk-RZLYEO4U.js';
@@ -71,7 +71,7 @@ var vulnCheckCommand = new Command("vuln-check").description("Run comprehensive
71
71
  const logger = createCLILogger("vuln-check");
72
72
  const isJsonMode = getOutputFormatFromEnv() === "json" || options.format === "json";
73
73
  try {
74
- const { VulnChecker } = await import('./vuln-checker-FFOGOJPT.js');
74
+ const { VulnChecker } = await import('./vuln-checker-QV6XODTJ.js');
75
75
  const categoryMap = {
76
76
  code: ["injection", "auth", "crypto"],
77
77
  deps: ["dependency"],
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { CLI_VERSION } from './chunk-PMXE5XOJ.js';
3
+ import { CLI_VERSION } from './chunk-AO554K3G.js';
4
4
  import { init_esm_shims } from './chunk-VRXHCR5K.js';
5
5
  import { glob } from 'glob';
6
6
  import { exec } from 'child_process';
@@ -284,7 +284,7 @@ function validateSqlSchema(content, errors, warnings) {
284
284
  var riskDetectorLoader = null;
285
285
  function loadRiskDetectorModule() {
286
286
  if (!riskDetectorLoader) {
287
- riskDetectorLoader = import('./risk-detector-VO5HJR4R.js').then((module) => ({
287
+ riskDetectorLoader = import('./risk-detector-S7XQF4I2.js').then((module) => ({
288
288
  detectSchemaRisks: module.detectSchemaRisks
289
289
  })).catch((error) => {
290
290
  riskDetectorLoader = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "private": false,
5
5
  "description": "AI-powered DevOps CLI",
6
6
  "type": "module",