@runa-ai/runa-cli 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{chunk-Y5ANTCKE.js → chunk-EZ46JIEO.js} +5 -2
  2. package/dist/{chunk-ZPE52NEK.js → chunk-IR7SA2ME.js} +1 -1
  3. package/dist/{chunk-XRLIZKB2.js → chunk-LCJNIHZY.js} +3 -3
  4. package/dist/{chunk-PAWNJA3N.js → chunk-XFXGFUAM.js} +1 -1
  5. package/dist/{ci-3HZWUQFN.js → ci-6XYG7XNX.js} +3 -3
  6. package/dist/{cli-RES5QRC2.js → cli-2XL3VESS.js} +8 -8
  7. package/dist/commands/build/contract.d.ts +2 -2
  8. package/dist/commands/build/machine.d.ts +6 -6
  9. package/dist/commands/ci/commands/ci-prod-types.d.ts +1 -1
  10. package/dist/commands/ci/machine/contract.d.ts +10 -10
  11. package/dist/commands/ci/machine/machine.d.ts +3 -3
  12. package/dist/commands/ci/utils/ci-summary.d.ts +3 -3
  13. package/dist/commands/db/apply/contract.d.ts +1 -1
  14. package/dist/commands/db/apply/helpers/planner-artifact.d.ts +1 -1
  15. package/dist/commands/db/commands/db-preview-profile.d.ts +1 -1
  16. package/dist/commands/db/preflight/contract.d.ts +1 -1
  17. package/dist/commands/db/sync/contract.d.ts +5 -5
  18. package/dist/commands/db/sync/machine.d.ts +2 -2
  19. package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +1 -7
  20. package/dist/commands/db/utils/duplicate-function-ownership-allowlist.d.ts +13 -0
  21. package/dist/commands/upgrade.d.ts +36 -0
  22. package/dist/{db-PRGL7PBX.js → db-4AGPISOW.js} +326 -283
  23. package/dist/index.js +3 -3
  24. package/dist/{risk-detector-S7XQF4I2.js → risk-detector-GDDLISVE.js} +1 -1
  25. package/dist/{risk-detector-core-TGFKWHRS.js → risk-detector-core-YI3M6INI.js} +1 -1
  26. package/dist/{risk-detector-plpgsql-O32TUR34.js → risk-detector-plpgsql-4GWEQXUG.js} +1 -1
  27. package/dist/{template-check-VNNQQXCX.js → template-check-D35F2GDP.js} +4 -0
  28. package/dist/{upgrade-LBO3Z3J7.js → upgrade-X7P6WRD5.js} +189 -19
  29. package/dist/{vuln-check-5JJ2YAJW.js → vuln-check-LMDYYJUE.js} +1 -1
  30. package/dist/{vuln-checker-JF5234BL.js → vuln-checker-NHXLNZRM.js} +1 -1
  31. package/dist/{watch-RFVCEQLH.js → watch-4RHXVCQ3.js} +1 -1
  32. package/package.json +3 -3
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
3
  import { detectDatabaseStack, getStackPaths } from './chunk-MILCC3B6.js';
4
- import { categorizeRisks, detectSchemaRisks } from './chunk-PAWNJA3N.js';
5
- import { isExecaError, resolveDbPreviewEnvironment, buildDbPlanCommandLabel, runDbApply, buildDbApplyCliError, DbPlanOutputSchema, parseDbPreviewProfile, DEFAULT_DB_PREVIEW_PROFILE, getDbPreviewModeLabel, buildDbPreviewCommandLabel, isCompareOnlyPreviewProfile, DbApplyOutputSchema, applyCommand, getDbPreviewIdempotentSchemaCount, classifyDbSyncCommandFailure, getDbSyncFallbackSuggestions, detectAppSchemas, normalizeDatabaseUrlForDdl, analyzeDuplicateFunctionOwnership, formatDuplicateFunctionOwnershipFinding, reviewDeclarativeDependencyWarnings, logDeclarativeDependencyWarnings, buildDeclarativeDependencyWarningFailureLines, parsePlanOutput, validateDependencyOrder, getBoundaryPolicy, resolveProductionApplyStrictMode, findDeclarativeRiskAllowlistMatch, assertBoundaryPolicyUsable, assertBoundaryPolicyQualityGate, formatSchemasForSql, findDirectoryPlacementAllowlistMatch, formatAllowlistMetadata, assessPlanSize, formatPlanSizeSummary, extractFunctionOwnershipDefinition } from './chunk-XRLIZKB2.js';
4
+ import { categorizeRisks, detectSchemaRisks } from './chunk-XFXGFUAM.js';
5
+ import { isExecaError, resolveDbPreviewEnvironment, buildDbPlanCommandLabel, runDbApply, buildDbApplyCliError, DbPlanOutputSchema, parseDbPreviewProfile, DEFAULT_DB_PREVIEW_PROFILE, getDbPreviewModeLabel, buildDbPreviewCommandLabel, isCompareOnlyPreviewProfile, DbApplyOutputSchema, applyCommand, getDbPreviewIdempotentSchemaCount, classifyDbSyncCommandFailure, getDbSyncFallbackSuggestions, detectAppSchemas, normalizeDatabaseUrlForDdl, analyzeDuplicateFunctionOwnership, formatDuplicateFunctionOwnershipFinding, reviewDeclarativeDependencyWarnings, logDeclarativeDependencyWarnings, buildDeclarativeDependencyWarningFailureLines, parsePlanOutput, validateDependencyOrder, getBoundaryPolicy, resolveProductionApplyStrictMode, findDeclarativeRiskAllowlistMatch, assertBoundaryPolicyUsable, assertBoundaryPolicyQualityGate, formatSchemasForSql, findDirectoryPlacementAllowlistMatch, formatAllowlistMetadata, assessPlanSize, formatPlanSizeSummary, extractFunctionOwnershipDefinition } from './chunk-LCJNIHZY.js';
6
6
  import { createError } from './chunk-NIS77243.js';
7
7
  import { resolveDatabaseUrl, resolveDatabaseTarget, tryResolveDatabaseUrl } from './chunk-WGRVAGSR.js';
8
8
  export { resolveDatabaseUrl, tryResolveDatabaseUrl } from './chunk-WGRVAGSR.js';
9
9
  import { analyzeDeclarativeDependencyContract, formatDeclarativeDependencyViolation, parseSqlFilename, collectSqlFiles, splitSqlStatements, ALLOW_DYNAMIC_SQL_ANNOTATION, extractFirstDollarBody, sanitizeExecutableCode, detectExtensionFilePath, FUNCTION_DEFINITION_RE, blankQuotedStrings, sanitizeExecutableCodePreserveStrings, countNewlines, shouldReviewUnknownDeclarativeDdl, extractDdlObject, isNonSchemaOperation, isNonDdlMaintenanceStatement, shouldReviewUnknownIdempotentDdl } from './chunk-HWR5NUUZ.js';
10
10
  import './chunk-UHDAYPHH.js';
11
- import './chunk-Y5ANTCKE.js';
11
+ import './chunk-EZ46JIEO.js';
12
12
  import { loadEnvFiles } from './chunk-IWVXI5O4.js';
13
- import './chunk-ZPE52NEK.js';
13
+ import './chunk-IR7SA2ME.js';
14
14
  import { diagnoseSupabaseStart } from './chunk-AAIE4F2U.js';
15
15
  import { validateUserFilePath, filterSafePaths, resolveSafePath } from './chunk-B7C7CLW2.js';
16
16
  import { runMachine } from './chunk-QDF7QXBL.js';
@@ -1848,6 +1848,10 @@ async function collectSchemaRisks(sqlDir, sqlFiles) {
1848
1848
  }
1849
1849
  return allRisks;
1850
1850
  }
1851
+ function filterAllowlistedSchemaRisks(risks) {
1852
+ const policy = getBoundaryPolicy();
1853
+ return risks.filter((risk) => !findDeclarativeRiskAllowlistMatch(risk, policy));
1854
+ }
1851
1855
  function summarizeRisks(risks) {
1852
1856
  const summaries = /* @__PURE__ */ new Map();
1853
1857
  for (const risk of risks) {
@@ -1966,7 +1970,7 @@ async function runSqlSchemaRiskCheck(result, logger4, step) {
1966
1970
  const sqlFiles = getDeclarativeSqlFiles(sqlDir, logger4);
1967
1971
  if (!sqlFiles) return;
1968
1972
  try {
1969
- const allRisks = await collectSchemaRisks(sqlDir, sqlFiles);
1973
+ const allRisks = filterAllowlistedSchemaRisks(await collectSchemaRisks(sqlDir, sqlFiles));
1970
1974
  if (allRisks.length === 0) {
1971
1975
  logger4.success(`Scanned ${sqlFiles.length} SQL file(s) - no violations`);
1972
1976
  return;
@@ -2653,18 +2657,326 @@ async function runDeclarativeDependencyCheck(result, logger4, step, strictOption
2653
2657
 
2654
2658
  // src/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.ts
2655
2659
  init_esm_shims();
2660
+
2661
+ // src/commands/db/utils/duplicate-function-ownership-allowlist.ts
2662
+ init_esm_shims();
2663
+
2664
+ // src/commands/db/sync/schema-guardrail-config.ts
2665
+ init_esm_shims();
2666
+
2667
+ // src/commands/db/sync/schema-guardrail-config-test-support.ts
2668
+ init_esm_shims();
2669
+ function extractFirstStringMatch(content, fieldName) {
2670
+ const match = content.match(new RegExp(`${fieldName}\\s*:\\s*['"]([^'"]+)['"]`));
2671
+ return match?.[1];
2672
+ }
2673
+ function extractFirstNumberMatch(content, fieldName) {
2674
+ const match = content.match(new RegExp(`${fieldName}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`));
2675
+ if (!match?.[1]) {
2676
+ return void 0;
2677
+ }
2678
+ const parsed = Number(match[1]);
2679
+ return Number.isFinite(parsed) ? parsed : void 0;
2680
+ }
2681
+ function extractStringArrayMatch(content, fieldName) {
2682
+ const match = content.match(new RegExp(`${fieldName}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
2683
+ if (!match?.[1]) {
2684
+ return [];
2685
+ }
2686
+ return Array.from(match[1].matchAll(/['"]([^'"]+)['"]/g), (entry) => entry[1] ?? "").filter(
2687
+ (value) => value.length > 0
2688
+ );
2689
+ }
2690
+ function extractNamedObjectBlock(content, fieldName) {
2691
+ const nameMatch = new RegExp(`${fieldName}\\s*:\\s*\\{`).exec(content);
2692
+ if (!nameMatch) {
2693
+ return null;
2694
+ }
2695
+ const startIndex = nameMatch.index + nameMatch[0].length - 1;
2696
+ let depth = 0;
2697
+ for (let index = startIndex; index < content.length; index += 1) {
2698
+ const current = content[index];
2699
+ if (current === "{") {
2700
+ depth += 1;
2701
+ } else if (current === "}") {
2702
+ depth -= 1;
2703
+ if (depth === 0) {
2704
+ return content.slice(startIndex + 1, index);
2705
+ }
2706
+ }
2707
+ }
2708
+ return null;
2709
+ }
2710
+ function extractAllowedDuplicateFunctions(content, normalizers) {
2711
+ const match = content.match(/allowedDuplicateFunctions\s*:\s*\[([\s\S]*?)\]/);
2712
+ if (!match?.[1]) {
2713
+ return [];
2714
+ }
2715
+ const entries = [];
2716
+ for (const objectMatch of match[1].matchAll(/\{([\s\S]*?)\}/g)) {
2717
+ const objectBody = objectMatch[1] ?? "";
2718
+ const qualifiedName = extractFirstStringMatch(objectBody, "qualifiedName");
2719
+ const signature = extractFirstStringMatch(objectBody, "signature");
2720
+ const reason = extractFirstStringMatch(objectBody, "reason");
2721
+ if (!qualifiedName || !signature || !reason) {
2722
+ continue;
2723
+ }
2724
+ entries.push({
2725
+ qualifiedName: normalizers.normalizeFunctionQualifiedName(qualifiedName),
2726
+ signature: normalizers.normalizeAllowlistSignature(signature),
2727
+ reason,
2728
+ declarativeFile: extractFirstStringMatch(objectBody, "declarativeFile"),
2729
+ idempotentFile: extractFirstStringMatch(objectBody, "idempotentFile"),
2730
+ expectedBodyHash: extractFirstStringMatch(objectBody, "expectedBodyHash")
2731
+ });
2732
+ }
2733
+ return entries;
2734
+ }
2735
+ function tryLoadSchemaGuardrailConfigFromText(params) {
2736
+ const configPath = findRunaConfig(params.targetDir);
2737
+ if (!configPath || !existsSync(configPath)) {
2738
+ return null;
2739
+ }
2740
+ try {
2741
+ const content = readFileSync(configPath, "utf-8");
2742
+ const schemaGuardrailsBlock = extractNamedObjectBlock(content, "schemaGuardrails") ?? "";
2743
+ const pgSchemaDiffBlock = extractNamedObjectBlock(content, "pgSchemaDiff") ?? "";
2744
+ return {
2745
+ ...params.defaults,
2746
+ declarativeSqlDir: extractFirstStringMatch(schemaGuardrailsBlock, "declarativeSqlDir") ?? params.defaults.declarativeSqlDir,
2747
+ allowedDuplicateFunctions: extractAllowedDuplicateFunctions(
2748
+ schemaGuardrailsBlock,
2749
+ params.normalizers
2750
+ ),
2751
+ generatedHeaderRewriteTargets: params.normalizers.normalizeFileList(
2752
+ extractStringArrayMatch(schemaGuardrailsBlock, "generatedHeaderRewriteTargets").map(
2753
+ (value) => params.normalizers.normalizePathForMatch(value)
2754
+ )
2755
+ ),
2756
+ semanticWarnings: {
2757
+ threshold: extractFirstNumberMatch(schemaGuardrailsBlock, "threshold") ?? params.defaults.semanticWarnings.threshold,
2758
+ maxCandidates: extractFirstNumberMatch(schemaGuardrailsBlock, "maxCandidates") ?? params.defaults.semanticWarnings.maxCandidates,
2759
+ ignorePairs: new Set(
2760
+ extractStringArrayMatch(schemaGuardrailsBlock, "ignorePairs").map(
2761
+ (value) => params.normalizers.normalizeSuppressionPair(value)
2762
+ )
2763
+ )
2764
+ },
2765
+ tableHeaderMaxWidth: extractFirstNumberMatch(schemaGuardrailsBlock, "tableHeaderMaxWidth") ?? params.defaults.tableHeaderMaxWidth,
2766
+ excludeFromOrphanDetection: extractStringArrayMatch(
2767
+ pgSchemaDiffBlock,
2768
+ "excludeFromOrphanDetection"
2769
+ ),
2770
+ idempotentSqlDir: extractFirstStringMatch(pgSchemaDiffBlock, "idempotentSqlDir") ?? params.defaults.idempotentSqlDir
2771
+ };
2772
+ } catch {
2773
+ return null;
2774
+ }
2775
+ }
2776
+
2777
+ // src/commands/db/sync/schema-guardrail-config.ts
2778
+ var DEFAULT_TABLE_HEADER_MAX_WIDTH = 160;
2779
+ var DEFAULT_SEMANTIC_WARNING_THRESHOLD = 0.55;
2780
+ var DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES = 3;
2781
+ var GENERIC_SIMILARITY_COLUMNS = /* @__PURE__ */ new Set([
2782
+ "id",
2783
+ "created_at",
2784
+ "updated_at",
2785
+ "deleted_at",
2786
+ "scope_id"
2787
+ ]);
2788
+ function normalizePathForMatch(filePath) {
2789
+ return filePath.replaceAll("\\", "/");
2790
+ }
2791
+ function normalizeFunctionQualifiedName(value) {
2792
+ return value.trim().toLowerCase();
2793
+ }
2794
+ function normalizeAllowlistSignature(value) {
2795
+ return value.replace(/\s+/g, " ").trim();
2796
+ }
2797
+ function normalizeSuppressionPair(value) {
2798
+ return value.split("::").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0).sort((left, right) => left.localeCompare(right)).join("::");
2799
+ }
2800
+ function normalizeFileList(files) {
2801
+ return [...new Set(files)].sort((a, b) => a.localeCompare(b));
2802
+ }
2803
+ function isSchemaGuardrailTextFallbackAllowed() {
2804
+ return Boolean(process.env.VITEST);
2805
+ }
2806
+ function createDefaultSchemaGuardrailConfig() {
2807
+ return {
2808
+ declarativeSqlDir: "supabase/schemas/declarative",
2809
+ allowedDuplicateFunctions: [],
2810
+ generatedHeaderRewriteTargets: [],
2811
+ semanticWarnings: {
2812
+ threshold: DEFAULT_SEMANTIC_WARNING_THRESHOLD,
2813
+ maxCandidates: DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES,
2814
+ ignorePairs: /* @__PURE__ */ new Set()
2815
+ },
2816
+ tableHeaderMaxWidth: DEFAULT_TABLE_HEADER_MAX_WIDTH,
2817
+ excludeFromOrphanDetection: [],
2818
+ idempotentSqlDir: "supabase/schemas/idempotent"
2819
+ };
2820
+ }
2821
+ function loadAllowedDuplicatesFromSchemaOwnership(targetDir) {
2822
+ const candidates = [
2823
+ join(targetDir, "supabase", "schemas", "schema-ownership.json"),
2824
+ join(targetDir, "schema-ownership.json")
2825
+ ];
2826
+ for (const filePath of candidates) {
2827
+ if (!existsSync(filePath)) continue;
2828
+ try {
2829
+ const raw = JSON.parse(readFileSync(filePath, "utf-8"));
2830
+ const allowedDuplicates = raw?.rules?.allowed_duplicates;
2831
+ if (!Array.isArray(allowedDuplicates)) continue;
2832
+ return allowedDuplicates.filter(
2833
+ (entry) => typeof entry === "object" && entry !== null && "qualifiedName" in entry && typeof entry.qualifiedName === "string"
2834
+ ).map((entry) => ({
2835
+ qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
2836
+ signature: normalizeAllowlistSignature(entry.signature ?? ""),
2837
+ reason: entry.reason ?? "Loaded from schema-ownership.json"
2838
+ }));
2839
+ } catch {
2840
+ }
2841
+ }
2842
+ return [];
2843
+ }
2844
+ function loadSchemaGuardrailConfig(targetDir) {
2845
+ const defaults = createDefaultSchemaGuardrailConfig();
2846
+ try {
2847
+ const config = loadRunaConfig(targetDir);
2848
+ const databaseConfig = config.database ?? {};
2849
+ return {
2850
+ declarativeSqlDir: databaseConfig.schemaGuardrails?.declarativeSqlDir ?? defaults.declarativeSqlDir,
2851
+ allowedDuplicateFunctions: [
2852
+ ...databaseConfig.schemaGuardrails?.allowedDuplicateFunctions?.map((entry) => ({
2853
+ ...entry,
2854
+ qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
2855
+ signature: normalizeAllowlistSignature(entry.signature),
2856
+ declarativeFile: entry.declarativeFile ? normalizePathForMatch(entry.declarativeFile) : void 0,
2857
+ idempotentFile: entry.idempotentFile ? normalizePathForMatch(entry.idempotentFile) : void 0
2858
+ })) ?? [],
2859
+ // Fallback: merge schema-ownership.json allowed_duplicates if present
2860
+ ...loadAllowedDuplicatesFromSchemaOwnership(targetDir)
2861
+ ],
2862
+ generatedHeaderRewriteTargets: normalizeFileList(
2863
+ (databaseConfig.schemaGuardrails?.generatedHeaderRewriteTargets ?? []).map(
2864
+ (value) => normalizePathForMatch(value)
2865
+ )
2866
+ ),
2867
+ semanticWarnings: {
2868
+ threshold: databaseConfig.schemaGuardrails?.semanticWarnings?.threshold ?? defaults.semanticWarnings.threshold,
2869
+ maxCandidates: databaseConfig.schemaGuardrails?.semanticWarnings?.maxCandidates ?? defaults.semanticWarnings.maxCandidates,
2870
+ ignorePairs: new Set(
2871
+ (databaseConfig.schemaGuardrails?.semanticWarnings?.ignorePairs ?? []).map(
2872
+ (value) => normalizeSuppressionPair(value)
2873
+ )
2874
+ )
2875
+ },
2876
+ tableHeaderMaxWidth: databaseConfig.schemaGuardrails?.tableHeaderMaxWidth ?? defaults.tableHeaderMaxWidth,
2877
+ excludeFromOrphanDetection: databaseConfig.pgSchemaDiff?.excludeFromOrphanDetection ?? [],
2878
+ idempotentSqlDir: databaseConfig.pgSchemaDiff?.idempotentSqlDir ?? defaults.idempotentSqlDir
2879
+ };
2880
+ } catch (error) {
2881
+ if (isSchemaGuardrailTextFallbackAllowed()) {
2882
+ return tryLoadSchemaGuardrailConfigFromText({
2883
+ targetDir,
2884
+ defaults,
2885
+ normalizers: {
2886
+ normalizeAllowlistSignature,
2887
+ normalizeFileList,
2888
+ normalizeFunctionQualifiedName,
2889
+ normalizePathForMatch,
2890
+ normalizeSuppressionPair
2891
+ }
2892
+ }) ?? defaults;
2893
+ }
2894
+ throw error;
2895
+ }
2896
+ }
2897
+
2898
+ // src/commands/db/utils/duplicate-function-ownership-allowlist.ts
2899
+ function matchesDuplicateFunctionSignature(params) {
2900
+ return normalizeFunctionQualifiedName(params.entry.qualifiedName) === normalizeFunctionQualifiedName(params.finding.qualifiedName) && normalizeAllowlistSignature(params.entry.signature) === normalizeAllowlistSignature(params.finding.signature ?? "");
2901
+ }
2902
+ function matchesOptionalFilePath(params) {
2903
+ if (!params.configuredPath) {
2904
+ return true;
2905
+ }
2906
+ return params.definitionFiles.some(
2907
+ (filePath) => normalizePathForMatch(filePath) === params.configuredPath
2908
+ );
2909
+ }
2910
+ function matchesExpectedBodyHash(params) {
2911
+ if (!params.entry.expectedBodyHash || !params.bodyHashes) {
2912
+ return true;
2913
+ }
2914
+ const definitions = [
2915
+ ...params.finding.declarativeDefinitions,
2916
+ ...params.finding.idempotentDefinitions
2917
+ ];
2918
+ if (definitions.length === 0) {
2919
+ return false;
2920
+ }
2921
+ return definitions.every((definition) => {
2922
+ const key = `${definition.layer}:${definition.file}:${definition.line}`;
2923
+ return params.bodyHashes?.get(key) === params.entry.expectedBodyHash;
2924
+ });
2925
+ }
2926
+ function isAllowlistedDuplicateFunction(params) {
2927
+ const { finding, allowlist, bodyHashes } = params;
2928
+ if (!finding.signature) return false;
2929
+ return allowlist.some((entry) => {
2930
+ if (!matchesDuplicateFunctionSignature({ entry, finding })) {
2931
+ return false;
2932
+ }
2933
+ if (!matchesOptionalFilePath({
2934
+ configuredPath: entry.declarativeFile,
2935
+ definitionFiles: finding.declarativeDefinitions.map((definition) => definition.file)
2936
+ })) {
2937
+ return false;
2938
+ }
2939
+ if (!matchesOptionalFilePath({
2940
+ configuredPath: entry.idempotentFile,
2941
+ definitionFiles: finding.idempotentDefinitions.map((definition) => definition.file)
2942
+ })) {
2943
+ return false;
2944
+ }
2945
+ return matchesExpectedBodyHash({ entry, finding, bodyHashes });
2946
+ });
2947
+ }
2948
+ function filterAllowlistedDuplicateFunctions(params) {
2949
+ return params.findings.filter(
2950
+ (finding) => !isAllowlistedDuplicateFunction({
2951
+ finding,
2952
+ allowlist: params.allowlist,
2953
+ bodyHashes: params.bodyHashes
2954
+ })
2955
+ );
2956
+ }
2957
+
2958
+ // src/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.ts
2656
2959
  async function runDuplicateFunctionOwnershipCheck(result, logger4, step) {
2657
2960
  logger4.step("Checking duplicate function ownership", step.next());
2658
2961
  const analysis = analyzeDuplicateFunctionOwnership(process.cwd());
2659
- if (analysis.findings.length === 0) {
2962
+ let findings = analysis.findings;
2963
+ try {
2964
+ const allowlist = loadSchemaGuardrailConfig(process.cwd()).allowedDuplicateFunctions;
2965
+ findings = filterAllowlistedDuplicateFunctions({
2966
+ findings,
2967
+ allowlist
2968
+ });
2969
+ } catch {
2970
+ }
2971
+ if (findings.length === 0) {
2660
2972
  logger4.success("No duplicate declarative/idempotent function ownership detected");
2661
2973
  return;
2662
2974
  }
2663
2975
  result.passed = false;
2664
- result.errors.push(`Found ${analysis.findings.length} duplicate function ownership finding(s)`);
2665
- logger4.error(`Found ${analysis.findings.length} duplicate function ownership finding(s):`);
2976
+ result.errors.push(`Found ${findings.length} duplicate function ownership finding(s)`);
2977
+ logger4.error(`Found ${findings.length} duplicate function ownership finding(s):`);
2666
2978
  logger4.info(` ${analysis.contractNote}`);
2667
- for (const finding of analysis.findings) {
2979
+ for (const finding of findings) {
2668
2980
  const formatted = formatDuplicateFunctionOwnershipFinding(finding);
2669
2981
  logger4.info(` [ERROR] ${formatted.summary}`);
2670
2982
  for (const location of formatted.declarativeLocations) {
@@ -3884,7 +4196,7 @@ init_esm_shims();
3884
4196
  var riskDetectorLoader = null;
3885
4197
  function loadRiskDetectorModule() {
3886
4198
  if (!riskDetectorLoader) {
3887
- riskDetectorLoader = import('./risk-detector-S7XQF4I2.js').then((module) => ({
4199
+ riskDetectorLoader = import('./risk-detector-GDDLISVE.js').then((module) => ({
3888
4200
  detectSchemaRisks: module.detectSchemaRisks
3889
4201
  })).catch((error) => {
3890
4202
  riskDetectorLoader = null;
@@ -5124,240 +5436,6 @@ init_esm_shims();
5124
5436
  // src/commands/db/sync/schema-guardrail.ts
5125
5437
  init_esm_shims();
5126
5438
 
5127
- // src/commands/db/sync/schema-guardrail-config.ts
5128
- init_esm_shims();
5129
-
5130
- // src/commands/db/sync/schema-guardrail-config-test-support.ts
5131
- init_esm_shims();
5132
- function extractFirstStringMatch(content, fieldName) {
5133
- const match = content.match(new RegExp(`${fieldName}\\s*:\\s*['"]([^'"]+)['"]`));
5134
- return match?.[1];
5135
- }
5136
- function extractFirstNumberMatch(content, fieldName) {
5137
- const match = content.match(new RegExp(`${fieldName}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`));
5138
- if (!match?.[1]) {
5139
- return void 0;
5140
- }
5141
- const parsed = Number(match[1]);
5142
- return Number.isFinite(parsed) ? parsed : void 0;
5143
- }
5144
- function extractStringArrayMatch(content, fieldName) {
5145
- const match = content.match(new RegExp(`${fieldName}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
5146
- if (!match?.[1]) {
5147
- return [];
5148
- }
5149
- return Array.from(match[1].matchAll(/['"]([^'"]+)['"]/g), (entry) => entry[1] ?? "").filter(
5150
- (value) => value.length > 0
5151
- );
5152
- }
5153
- function extractNamedObjectBlock(content, fieldName) {
5154
- const nameMatch = new RegExp(`${fieldName}\\s*:\\s*\\{`).exec(content);
5155
- if (!nameMatch) {
5156
- return null;
5157
- }
5158
- const startIndex = nameMatch.index + nameMatch[0].length - 1;
5159
- let depth = 0;
5160
- for (let index = startIndex; index < content.length; index += 1) {
5161
- const current = content[index];
5162
- if (current === "{") {
5163
- depth += 1;
5164
- } else if (current === "}") {
5165
- depth -= 1;
5166
- if (depth === 0) {
5167
- return content.slice(startIndex + 1, index);
5168
- }
5169
- }
5170
- }
5171
- return null;
5172
- }
5173
- function extractAllowedDuplicateFunctions(content, normalizers) {
5174
- const match = content.match(/allowedDuplicateFunctions\s*:\s*\[([\s\S]*?)\]/);
5175
- if (!match?.[1]) {
5176
- return [];
5177
- }
5178
- const entries = [];
5179
- for (const objectMatch of match[1].matchAll(/\{([\s\S]*?)\}/g)) {
5180
- const objectBody = objectMatch[1] ?? "";
5181
- const qualifiedName = extractFirstStringMatch(objectBody, "qualifiedName");
5182
- const signature = extractFirstStringMatch(objectBody, "signature");
5183
- const reason = extractFirstStringMatch(objectBody, "reason");
5184
- if (!qualifiedName || !signature || !reason) {
5185
- continue;
5186
- }
5187
- entries.push({
5188
- qualifiedName: normalizers.normalizeFunctionQualifiedName(qualifiedName),
5189
- signature: normalizers.normalizeAllowlistSignature(signature),
5190
- reason,
5191
- declarativeFile: extractFirstStringMatch(objectBody, "declarativeFile"),
5192
- idempotentFile: extractFirstStringMatch(objectBody, "idempotentFile"),
5193
- expectedBodyHash: extractFirstStringMatch(objectBody, "expectedBodyHash")
5194
- });
5195
- }
5196
- return entries;
5197
- }
5198
- function tryLoadSchemaGuardrailConfigFromText(params) {
5199
- const configPath = findRunaConfig(params.targetDir);
5200
- if (!configPath || !existsSync(configPath)) {
5201
- return null;
5202
- }
5203
- try {
5204
- const content = readFileSync(configPath, "utf-8");
5205
- const schemaGuardrailsBlock = extractNamedObjectBlock(content, "schemaGuardrails") ?? "";
5206
- const pgSchemaDiffBlock = extractNamedObjectBlock(content, "pgSchemaDiff") ?? "";
5207
- return {
5208
- ...params.defaults,
5209
- declarativeSqlDir: extractFirstStringMatch(schemaGuardrailsBlock, "declarativeSqlDir") ?? params.defaults.declarativeSqlDir,
5210
- allowedDuplicateFunctions: extractAllowedDuplicateFunctions(
5211
- schemaGuardrailsBlock,
5212
- params.normalizers
5213
- ),
5214
- generatedHeaderRewriteTargets: params.normalizers.normalizeFileList(
5215
- extractStringArrayMatch(schemaGuardrailsBlock, "generatedHeaderRewriteTargets").map(
5216
- (value) => params.normalizers.normalizePathForMatch(value)
5217
- )
5218
- ),
5219
- semanticWarnings: {
5220
- threshold: extractFirstNumberMatch(schemaGuardrailsBlock, "threshold") ?? params.defaults.semanticWarnings.threshold,
5221
- maxCandidates: extractFirstNumberMatch(schemaGuardrailsBlock, "maxCandidates") ?? params.defaults.semanticWarnings.maxCandidates,
5222
- ignorePairs: new Set(
5223
- extractStringArrayMatch(schemaGuardrailsBlock, "ignorePairs").map(
5224
- (value) => params.normalizers.normalizeSuppressionPair(value)
5225
- )
5226
- )
5227
- },
5228
- tableHeaderMaxWidth: extractFirstNumberMatch(schemaGuardrailsBlock, "tableHeaderMaxWidth") ?? params.defaults.tableHeaderMaxWidth,
5229
- excludeFromOrphanDetection: extractStringArrayMatch(
5230
- pgSchemaDiffBlock,
5231
- "excludeFromOrphanDetection"
5232
- ),
5233
- idempotentSqlDir: extractFirstStringMatch(pgSchemaDiffBlock, "idempotentSqlDir") ?? params.defaults.idempotentSqlDir
5234
- };
5235
- } catch {
5236
- return null;
5237
- }
5238
- }
5239
-
5240
- // src/commands/db/sync/schema-guardrail-config.ts
5241
- var DEFAULT_TABLE_HEADER_MAX_WIDTH = 160;
5242
- var DEFAULT_SEMANTIC_WARNING_THRESHOLD = 0.55;
5243
- var DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES = 3;
5244
- var GENERIC_SIMILARITY_COLUMNS = /* @__PURE__ */ new Set([
5245
- "id",
5246
- "created_at",
5247
- "updated_at",
5248
- "deleted_at",
5249
- "scope_id"
5250
- ]);
5251
- function normalizePathForMatch(filePath) {
5252
- return filePath.replaceAll("\\", "/");
5253
- }
5254
- function normalizeFunctionQualifiedName(value) {
5255
- return value.trim().toLowerCase();
5256
- }
5257
- function normalizeAllowlistSignature(value) {
5258
- return value.replace(/\s+/g, " ").trim();
5259
- }
5260
- function normalizeSuppressionPair(value) {
5261
- return value.split("::").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0).sort((left, right) => left.localeCompare(right)).join("::");
5262
- }
5263
- function normalizeFileList(files) {
5264
- return [...new Set(files)].sort((a, b) => a.localeCompare(b));
5265
- }
5266
- function isSchemaGuardrailTextFallbackAllowed() {
5267
- return Boolean(process.env.VITEST);
5268
- }
5269
- function createDefaultSchemaGuardrailConfig() {
5270
- return {
5271
- declarativeSqlDir: "supabase/schemas/declarative",
5272
- allowedDuplicateFunctions: [],
5273
- generatedHeaderRewriteTargets: [],
5274
- semanticWarnings: {
5275
- threshold: DEFAULT_SEMANTIC_WARNING_THRESHOLD,
5276
- maxCandidates: DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES,
5277
- ignorePairs: /* @__PURE__ */ new Set()
5278
- },
5279
- tableHeaderMaxWidth: DEFAULT_TABLE_HEADER_MAX_WIDTH,
5280
- excludeFromOrphanDetection: [],
5281
- idempotentSqlDir: "supabase/schemas/idempotent"
5282
- };
5283
- }
5284
- function loadAllowedDuplicatesFromSchemaOwnership(targetDir) {
5285
- const candidates = [
5286
- join(targetDir, "supabase", "schemas", "schema-ownership.json"),
5287
- join(targetDir, "schema-ownership.json")
5288
- ];
5289
- for (const filePath of candidates) {
5290
- if (!existsSync(filePath)) continue;
5291
- try {
5292
- const raw = JSON.parse(readFileSync(filePath, "utf-8"));
5293
- const allowedDuplicates = raw?.rules?.allowed_duplicates;
5294
- if (!Array.isArray(allowedDuplicates)) continue;
5295
- return allowedDuplicates.filter(
5296
- (entry) => typeof entry === "object" && entry !== null && "qualifiedName" in entry && typeof entry.qualifiedName === "string"
5297
- ).map((entry) => ({
5298
- qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
5299
- signature: normalizeAllowlistSignature(entry.signature ?? ""),
5300
- reason: entry.reason ?? "Loaded from schema-ownership.json"
5301
- }));
5302
- } catch {
5303
- }
5304
- }
5305
- return [];
5306
- }
5307
- function loadSchemaGuardrailConfig(targetDir) {
5308
- const defaults = createDefaultSchemaGuardrailConfig();
5309
- try {
5310
- const config = loadRunaConfig(targetDir);
5311
- const databaseConfig = config.database ?? {};
5312
- return {
5313
- declarativeSqlDir: databaseConfig.schemaGuardrails?.declarativeSqlDir ?? defaults.declarativeSqlDir,
5314
- allowedDuplicateFunctions: [
5315
- ...databaseConfig.schemaGuardrails?.allowedDuplicateFunctions?.map((entry) => ({
5316
- ...entry,
5317
- qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
5318
- signature: normalizeAllowlistSignature(entry.signature),
5319
- declarativeFile: entry.declarativeFile ? normalizePathForMatch(entry.declarativeFile) : void 0,
5320
- idempotentFile: entry.idempotentFile ? normalizePathForMatch(entry.idempotentFile) : void 0
5321
- })) ?? [],
5322
- // Fallback: merge schema-ownership.json allowed_duplicates if present
5323
- ...loadAllowedDuplicatesFromSchemaOwnership(targetDir)
5324
- ],
5325
- generatedHeaderRewriteTargets: normalizeFileList(
5326
- (databaseConfig.schemaGuardrails?.generatedHeaderRewriteTargets ?? []).map(
5327
- (value) => normalizePathForMatch(value)
5328
- )
5329
- ),
5330
- semanticWarnings: {
5331
- threshold: databaseConfig.schemaGuardrails?.semanticWarnings?.threshold ?? defaults.semanticWarnings.threshold,
5332
- maxCandidates: databaseConfig.schemaGuardrails?.semanticWarnings?.maxCandidates ?? defaults.semanticWarnings.maxCandidates,
5333
- ignorePairs: new Set(
5334
- (databaseConfig.schemaGuardrails?.semanticWarnings?.ignorePairs ?? []).map(
5335
- (value) => normalizeSuppressionPair(value)
5336
- )
5337
- )
5338
- },
5339
- tableHeaderMaxWidth: databaseConfig.schemaGuardrails?.tableHeaderMaxWidth ?? defaults.tableHeaderMaxWidth,
5340
- excludeFromOrphanDetection: databaseConfig.pgSchemaDiff?.excludeFromOrphanDetection ?? [],
5341
- idempotentSqlDir: databaseConfig.pgSchemaDiff?.idempotentSqlDir ?? defaults.idempotentSqlDir
5342
- };
5343
- } catch (error) {
5344
- if (isSchemaGuardrailTextFallbackAllowed()) {
5345
- return tryLoadSchemaGuardrailConfigFromText({
5346
- targetDir,
5347
- defaults,
5348
- normalizers: {
5349
- normalizeAllowlistSignature,
5350
- normalizeFileList,
5351
- normalizeFunctionQualifiedName,
5352
- normalizePathForMatch,
5353
- normalizeSuppressionPair
5354
- }
5355
- }) ?? defaults;
5356
- }
5357
- throw error;
5358
- }
5359
- }
5360
-
5361
5439
  // src/commands/db/sync/schema-guardrail-graph.ts
5362
5440
  init_esm_shims();
5363
5441
 
@@ -5960,37 +6038,6 @@ function buildFunctionBodyHashMap(files, layer) {
5960
6038
  }
5961
6039
  return hashes;
5962
6040
  }
5963
- function isAllowlistedDuplicateFunction(params) {
5964
- const { finding, allowlist, bodyHashes } = params;
5965
- if (!finding.signature) return false;
5966
- return allowlist.some((entry) => {
5967
- if (normalizeFunctionQualifiedName(entry.qualifiedName) !== normalizeFunctionQualifiedName(finding.qualifiedName)) {
5968
- return false;
5969
- }
5970
- if (normalizeAllowlistSignature(entry.signature) !== normalizeAllowlistSignature(finding.signature ?? "")) {
5971
- return false;
5972
- }
5973
- if (entry.declarativeFile && !finding.declarativeDefinitions.some(
5974
- (definition) => normalizePathForMatch(definition.file) === entry.declarativeFile
5975
- )) {
5976
- return false;
5977
- }
5978
- if (entry.idempotentFile && !finding.idempotentDefinitions.some(
5979
- (definition) => normalizePathForMatch(definition.file) === entry.idempotentFile
5980
- )) {
5981
- return false;
5982
- }
5983
- if (!entry.expectedBodyHash) {
5984
- return true;
5985
- }
5986
- const definitions = [...finding.declarativeDefinitions, ...finding.idempotentDefinitions];
5987
- if (definitions.length === 0) return false;
5988
- return definitions.every((definition) => {
5989
- const key = `${definition.layer}:${definition.file}:${definition.line}`;
5990
- return bodyHashes.get(key) === entry.expectedBodyHash;
5991
- });
5992
- });
5993
- }
5994
6041
  function normalizeQualifiedObjectRef(schema, name, isFunctionCall) {
5995
6042
  return `${schema}.${name}${isFunctionCall ? "()" : ""}`;
5996
6043
  }
@@ -12069,14 +12116,10 @@ async function collectLocalPrecheckBundle(strict) {
12069
12116
  guardrailAllowlist = loadSchemaGuardrailConfig(process.cwd()).allowedDuplicateFunctions;
12070
12117
  } catch {
12071
12118
  }
12072
- const duplicateOwnershipBlockers = duplicateOwnershipAnalysis.findings.filter(
12073
- (finding) => !isAllowlistedDuplicateFunction({
12074
- finding,
12075
- allowlist: guardrailAllowlist,
12076
- bodyHashes: /* @__PURE__ */ new Map()
12077
- // Hash check skipped; name + signature matching still works
12078
- })
12079
- ).map((finding) => {
12119
+ const duplicateOwnershipBlockers = filterAllowlistedDuplicateFunctions({
12120
+ findings: duplicateOwnershipAnalysis.findings,
12121
+ allowlist: guardrailAllowlist
12122
+ }).map((finding) => {
12080
12123
  const formatted = formatDuplicateFunctionOwnershipFinding(finding);
12081
12124
  return [
12082
12125
  formatted.summary,