@runa-ai/runa-cli 0.10.1 → 0.10.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.
- package/dist/{chunk-Y5ANTCKE.js → chunk-EZ46JIEO.js} +5 -2
- package/dist/{chunk-XRLIZKB2.js → chunk-S7VGVFYF.js} +4934 -4289
- package/dist/{chunk-ZPE52NEK.js → chunk-SS7RIWW3.js} +1 -1
- package/dist/{chunk-PAWNJA3N.js → chunk-XFXGFUAM.js} +1 -1
- package/dist/{ci-3HZWUQFN.js → ci-6P7VK6WB.js} +3 -3
- package/dist/{cli-RES5QRC2.js → cli-Q665YRVT.js} +8 -8
- package/dist/commands/build/contract.d.ts +2 -2
- package/dist/commands/build/machine.d.ts +6 -6
- package/dist/commands/ci/commands/ci-prod-types.d.ts +1 -1
- package/dist/commands/ci/machine/contract.d.ts +10 -10
- package/dist/commands/ci/machine/machine.d.ts +3 -3
- package/dist/commands/ci/utils/ci-summary.d.ts +3 -3
- package/dist/commands/db/apply/contract.d.ts +1 -1
- package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +1 -1
- package/dist/commands/db/apply/helpers/planner-artifact.d.ts +1 -1
- package/dist/commands/db/commands/db-preview-profile.d.ts +1 -1
- package/dist/commands/db/preflight/contract.d.ts +1 -1
- package/dist/commands/db/sync/contract.d.ts +5 -5
- package/dist/commands/db/sync/machine.d.ts +2 -2
- package/dist/commands/db/sync/schema-guardrail-graph-metadata.d.ts +1 -7
- package/dist/commands/db/sync/schema-guardrail-graph.d.ts +2 -0
- package/dist/commands/db/sync/schema-guardrail-rewrite.d.ts +8 -0
- package/dist/commands/db/sync/schema-guardrail-types.d.ts +2 -2
- package/dist/commands/db/utils/duplicate-function-ownership-allowlist.d.ts +13 -0
- package/dist/commands/db/utils/function-acl-manifest.d.ts +39 -0
- package/dist/commands/upgrade.d.ts +36 -0
- package/dist/{db-PRGL7PBX.js → db-BQOVOQXU.js} +816 -732
- package/dist/index.js +3 -3
- package/dist/{risk-detector-S7XQF4I2.js → risk-detector-GDDLISVE.js} +1 -1
- package/dist/{risk-detector-core-TGFKWHRS.js → risk-detector-core-YI3M6INI.js} +1 -1
- package/dist/{risk-detector-plpgsql-O32TUR34.js → risk-detector-plpgsql-4GWEQXUG.js} +1 -1
- package/dist/{template-check-VNNQQXCX.js → template-check-D35F2GDP.js} +4 -0
- package/dist/{upgrade-LBO3Z3J7.js → upgrade-X7P6WRD5.js} +189 -19
- package/dist/{vuln-check-5JJ2YAJW.js → vuln-check-WW43E7PS.js} +1 -1
- package/dist/{vuln-checker-JF5234BL.js → vuln-checker-BC3ZAXJ3.js} +1 -1
- package/dist/{watch-RFVCEQLH.js → watch-4RHXVCQ3.js} +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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, FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH, functionAclManifestHasEntries, validateFunctionAclMigration, isManagedFunctionAclFileContentStale, renderFunctionAclFile, assessPlanSize, formatPlanSizeSummary, buildFunctionAclManifestFromSqlFiles, buildFunctionAclIdempotentTouchMetadata, stableSorted, normalizePolicyCommand, normalizeFileList, extractFunctionOwnershipDefinition, SECURITY_DEFINER_RE, MANAGED_BOUNDARY_SCHEMAS, currentIsoTimestamp, makeGraphVersion, GENERATOR_VERSION, MAX_SCHEMA_GUIDANCE_TARGETS_PER_FILE, QUALIFIED_SQL_OBJECT_RE, SEARCH_PATH_LOCK_RE, TRIGGER_GUIDANCE_SUPPRESSED_FUNCTIONS, SQL_EXTENSION_IDENTIFIER_PATTERN, SQL_IDENTIFIER_PATTERN } from './chunk-S7VGVFYF.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-
|
|
11
|
+
import './chunk-EZ46JIEO.js';
|
|
12
12
|
import { loadEnvFiles } from './chunk-IWVXI5O4.js';
|
|
13
|
-
import './chunk-
|
|
13
|
+
import './chunk-SS7RIWW3.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';
|
|
@@ -1145,7 +1145,8 @@ z.object({
|
|
|
1145
1145
|
});
|
|
1146
1146
|
var SchemaManagedBlockKindSchema = z.enum([
|
|
1147
1147
|
"file-header",
|
|
1148
|
-
"table-header"
|
|
1148
|
+
"table-header",
|
|
1149
|
+
"generated-file"
|
|
1149
1150
|
]);
|
|
1150
1151
|
var SchemaGuardrailPhaseIdSchema = z.enum([
|
|
1151
1152
|
"load_sources",
|
|
@@ -1166,6 +1167,7 @@ var SchemaGuardrailFailureCodeSchema = z.enum([
|
|
|
1166
1167
|
"dynamic_sql_blocked",
|
|
1167
1168
|
"extension_placement_blocked",
|
|
1168
1169
|
"stale_generated_header",
|
|
1170
|
+
"function_acl_migration_required",
|
|
1169
1171
|
"generated_header_validation_failed",
|
|
1170
1172
|
"generated_header_rewrite_failed",
|
|
1171
1173
|
"static_graph_build_failed",
|
|
@@ -1509,39 +1511,46 @@ function buildGuardrailConflictEntries(report, entries) {
|
|
|
1509
1511
|
pushSemanticEvidenceEntries(entries, report);
|
|
1510
1512
|
pushBoundaryGuidanceEntries(entries, report);
|
|
1511
1513
|
}
|
|
1512
|
-
function
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1514
|
+
function describeStaleBlock(block) {
|
|
1515
|
+
return block.kind === "file-header" ? "file metadata" : block.kind === "generated-file" ? "managed file body" : `table: ${block.target}`;
|
|
1516
|
+
}
|
|
1517
|
+
function pushStaleArtifactEntries(report, entries) {
|
|
1518
|
+
if (report.staleBlocks.length === 0) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1522
|
+
for (const block of report.staleBlocks) {
|
|
1523
|
+
const kinds = byFile.get(block.file) ?? [];
|
|
1524
|
+
kinds.push(describeStaleBlock(block));
|
|
1525
|
+
byFile.set(block.file, kinds);
|
|
1526
|
+
}
|
|
1527
|
+
entries.push({
|
|
1528
|
+
level: "warn",
|
|
1529
|
+
message: `Generated SQL artifacts are stale (${report.staleBlocks.length} block(s) in ${byFile.size} file(s)):`
|
|
1530
|
+
});
|
|
1531
|
+
for (const [file, kinds] of byFile) {
|
|
1520
1532
|
entries.push({
|
|
1521
|
-
level: "
|
|
1522
|
-
message: `
|
|
1533
|
+
level: "info",
|
|
1534
|
+
message: ` ${file}: ${kinds.join(", ")}`
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
if (!report.failure) {
|
|
1538
|
+
entries.push({
|
|
1539
|
+
level: "info",
|
|
1540
|
+
message: "Auto-fix: Run `runa db sync` to regenerate managed SQL artifacts automatically."
|
|
1541
|
+
});
|
|
1542
|
+
entries.push({
|
|
1543
|
+
level: "info",
|
|
1544
|
+
message: "Managed SQL artifacts track: FK references, RLS policies, triggers, function ownership, schema dependencies, and function ACL reconciliation."
|
|
1523
1545
|
});
|
|
1524
|
-
for (const [file, kinds] of byFile) {
|
|
1525
|
-
entries.push({
|
|
1526
|
-
level: "info",
|
|
1527
|
-
message: ` ${file}: ${kinds.join(", ")}`
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
if (!report.failure) {
|
|
1531
|
-
entries.push({
|
|
1532
|
-
level: "info",
|
|
1533
|
-
message: "Auto-fix: Run `runa db sync` to regenerate all headers automatically."
|
|
1534
|
-
});
|
|
1535
|
-
entries.push({
|
|
1536
|
-
level: "info",
|
|
1537
|
-
message: "Headers track: FK references, RLS policies, triggers, function ownership, and schema dependencies."
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
1546
|
}
|
|
1547
|
+
}
|
|
1548
|
+
function buildGuardrailHeaderEntries(report, entries) {
|
|
1549
|
+
pushStaleArtifactEntries(report, entries);
|
|
1541
1550
|
if (report.headersRewritten.length > 0) {
|
|
1542
1551
|
entries.push({
|
|
1543
1552
|
level: "info",
|
|
1544
|
-
message: `Generated
|
|
1553
|
+
message: `Generated SQL artifacts refreshed in ${report.headersRewritten.length} file(s).`
|
|
1545
1554
|
});
|
|
1546
1555
|
}
|
|
1547
1556
|
}
|
|
@@ -1848,6 +1857,10 @@ async function collectSchemaRisks(sqlDir, sqlFiles) {
|
|
|
1848
1857
|
}
|
|
1849
1858
|
return allRisks;
|
|
1850
1859
|
}
|
|
1860
|
+
function filterAllowlistedSchemaRisks(risks) {
|
|
1861
|
+
const policy = getBoundaryPolicy();
|
|
1862
|
+
return risks.filter((risk) => !findDeclarativeRiskAllowlistMatch(risk, policy));
|
|
1863
|
+
}
|
|
1851
1864
|
function summarizeRisks(risks) {
|
|
1852
1865
|
const summaries = /* @__PURE__ */ new Map();
|
|
1853
1866
|
for (const risk of risks) {
|
|
@@ -1966,7 +1979,7 @@ async function runSqlSchemaRiskCheck(result, logger4, step) {
|
|
|
1966
1979
|
const sqlFiles = getDeclarativeSqlFiles(sqlDir, logger4);
|
|
1967
1980
|
if (!sqlFiles) return;
|
|
1968
1981
|
try {
|
|
1969
|
-
const allRisks = await collectSchemaRisks(sqlDir, sqlFiles);
|
|
1982
|
+
const allRisks = filterAllowlistedSchemaRisks(await collectSchemaRisks(sqlDir, sqlFiles));
|
|
1970
1983
|
if (allRisks.length === 0) {
|
|
1971
1984
|
logger4.success(`Scanned ${sqlFiles.length} SQL file(s) - no violations`);
|
|
1972
1985
|
return;
|
|
@@ -2653,193 +2666,501 @@ async function runDeclarativeDependencyCheck(result, logger4, step, strictOption
|
|
|
2653
2666
|
|
|
2654
2667
|
// src/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.ts
|
|
2655
2668
|
init_esm_shims();
|
|
2656
|
-
async function runDuplicateFunctionOwnershipCheck(result, logger4, step) {
|
|
2657
|
-
logger4.step("Checking duplicate function ownership", step.next());
|
|
2658
|
-
const analysis = analyzeDuplicateFunctionOwnership(process.cwd());
|
|
2659
|
-
if (analysis.findings.length === 0) {
|
|
2660
|
-
logger4.success("No duplicate declarative/idempotent function ownership detected");
|
|
2661
|
-
return;
|
|
2662
|
-
}
|
|
2663
|
-
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):`);
|
|
2666
|
-
logger4.info(` ${analysis.contractNote}`);
|
|
2667
|
-
for (const finding of analysis.findings) {
|
|
2668
|
-
const formatted = formatDuplicateFunctionOwnershipFinding(finding);
|
|
2669
|
-
logger4.info(` [ERROR] ${formatted.summary}`);
|
|
2670
|
-
for (const location of formatted.declarativeLocations) {
|
|
2671
|
-
logger4.info(` declarative: ${location}`);
|
|
2672
|
-
}
|
|
2673
|
-
for (const location of formatted.idempotentLocations) {
|
|
2674
|
-
logger4.info(` idempotent: ${location}`);
|
|
2675
|
-
}
|
|
2676
|
-
logger4.info(` ${formatted.suggestion}`);
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
// src/commands/db/utils/preflight-checks/schema-boundary-checks.ts
|
|
2681
|
-
init_esm_shims();
|
|
2682
2669
|
|
|
2683
|
-
// src/commands/db/
|
|
2670
|
+
// src/commands/db/utils/duplicate-function-ownership-allowlist.ts
|
|
2684
2671
|
init_esm_shims();
|
|
2685
|
-
var SHOW_ALLOWLIST_REPORT = process.env.RUNA_DB_PRECHECK_ALLOWLIST_REPORT === "1";
|
|
2686
|
-
var DIRECTORY_PLACEMENT_WARNING_PREFIX = " [misplacement] ";
|
|
2687
|
-
function applyStrictModeToReport(report, strict) {
|
|
2688
|
-
if (!strict) return report;
|
|
2689
|
-
return {
|
|
2690
|
-
blockers: [...report.blockers, ...report.warnings],
|
|
2691
|
-
warnings: []
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
function formatAllowlistReason({
|
|
2695
|
-
label,
|
|
2696
|
-
ruleId,
|
|
2697
|
-
reason,
|
|
2698
|
-
rule
|
|
2699
|
-
}) {
|
|
2700
|
-
const meta = rule ? formatAllowlistMetadata(rule) : "";
|
|
2701
|
-
return meta ? `[allowlist:${label}] ${ruleId}: ${reason} (${meta})` : `[allowlist:${label}] ${ruleId}: ${reason}`;
|
|
2702
|
-
}
|
|
2703
2672
|
|
|
2704
|
-
// src/commands/db/
|
|
2673
|
+
// src/commands/db/sync/schema-guardrail-config.ts
|
|
2705
2674
|
init_esm_shims();
|
|
2706
2675
|
|
|
2707
|
-
// src/commands/db/
|
|
2676
|
+
// src/commands/db/sync/schema-guardrail-config-test-support.ts
|
|
2708
2677
|
init_esm_shims();
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
const raw = process.env[name];
|
|
2713
|
-
if (!raw) return fallback;
|
|
2714
|
-
const value = Number.parseInt(raw, 10);
|
|
2715
|
-
if (!Number.isFinite(value) || Number.isNaN(value)) return fallback;
|
|
2716
|
-
const normalized = Math.trunc(value);
|
|
2717
|
-
if (normalized < min) return min;
|
|
2718
|
-
if (normalized > max) return max;
|
|
2719
|
-
return normalized;
|
|
2720
|
-
}
|
|
2721
|
-
var RUNA_SCHEMA_PRECHECK_MAX_FILES = parseIntEnv(
|
|
2722
|
-
"RUNA_SCHEMA_PRECHECK_MAX_FILES",
|
|
2723
|
-
3e3,
|
|
2724
|
-
100,
|
|
2725
|
-
2e4
|
|
2726
|
-
);
|
|
2727
|
-
var RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES = parseIntEnv(
|
|
2728
|
-
"RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES",
|
|
2729
|
-
512 * ONE_MB,
|
|
2730
|
-
32 * ONE_MB,
|
|
2731
|
-
20 * ONE_GB
|
|
2732
|
-
);
|
|
2733
|
-
var RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES = parseIntEnv(
|
|
2734
|
-
"RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES",
|
|
2735
|
-
64 * ONE_MB,
|
|
2736
|
-
1 * ONE_MB,
|
|
2737
|
-
512 * ONE_MB
|
|
2738
|
-
);
|
|
2739
|
-
var RUNA_PLAN_PRECHECK_MAX_BYTES = parseIntEnv(
|
|
2740
|
-
"RUNA_PLAN_PRECHECK_MAX_BYTES",
|
|
2741
|
-
128 * ONE_MB,
|
|
2742
|
-
8 * ONE_MB,
|
|
2743
|
-
4 * ONE_GB
|
|
2744
|
-
);
|
|
2745
|
-
var RUNA_PLAN_PRECHECK_MAX_STATEMENTS = parseIntEnv(
|
|
2746
|
-
"RUNA_PLAN_PRECHECK_MAX_STATEMENTS",
|
|
2747
|
-
4e3,
|
|
2748
|
-
100,
|
|
2749
|
-
5e4
|
|
2750
|
-
);
|
|
2751
|
-
function formatBytes2(value) {
|
|
2752
|
-
if (value >= ONE_MB * 1024) return `${(value / (ONE_MB * 1024)).toFixed(1)} GB`;
|
|
2753
|
-
if (value >= ONE_MB) return `${(value / ONE_MB).toFixed(1)} MB`;
|
|
2754
|
-
return `${(value / 1024).toFixed(1)} KB`;
|
|
2755
|
-
}
|
|
2756
|
-
function buildBudgetExceededReason(context, files, bytes, maxFiles, maxBytes) {
|
|
2757
|
-
return `${context}: budget exceeded (files=${files}/${maxFiles}, bytes=${formatBytes2(bytes)}/${formatBytes2(maxBytes)}). Stop and review with stricter scoping or increase RUNA_SCHEMA_PRECHECK_MAX_* settings only after approval.`;
|
|
2758
|
-
}
|
|
2759
|
-
function createSchemaPrecheckBudgetState() {
|
|
2760
|
-
return {
|
|
2761
|
-
scannedFiles: 0,
|
|
2762
|
-
scannedBytes: 0,
|
|
2763
|
-
maxFiles: RUNA_SCHEMA_PRECHECK_MAX_FILES,
|
|
2764
|
-
maxBytes: RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES,
|
|
2765
|
-
maxFileBytes: RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES
|
|
2766
|
-
};
|
|
2678
|
+
function extractFirstStringMatch(content, fieldName) {
|
|
2679
|
+
const match = content.match(new RegExp(`${fieldName}\\s*:\\s*['"]([^'"]+)['"]`));
|
|
2680
|
+
return match?.[1];
|
|
2767
2681
|
}
|
|
2768
|
-
function
|
|
2769
|
-
const
|
|
2770
|
-
if (
|
|
2771
|
-
return
|
|
2772
|
-
state.maxFileBytes
|
|
2773
|
-
)}`;
|
|
2774
|
-
}
|
|
2775
|
-
if (state.scannedFiles + 1 > state.maxFiles) {
|
|
2776
|
-
return `Schema file scan budget exceeds ${state.maxFiles} files at ${path12.basename(filePath)}.`;
|
|
2777
|
-
}
|
|
2778
|
-
const projectedBytes = state.scannedBytes + size;
|
|
2779
|
-
if (projectedBytes > state.maxBytes) {
|
|
2780
|
-
return `Schema scan budget exceeds total size limit ${formatBytes2(state.maxBytes)}.`;
|
|
2682
|
+
function extractFirstNumberMatch(content, fieldName) {
|
|
2683
|
+
const match = content.match(new RegExp(`${fieldName}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`));
|
|
2684
|
+
if (!match?.[1]) {
|
|
2685
|
+
return void 0;
|
|
2781
2686
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
return null;
|
|
2687
|
+
const parsed = Number(match[1]);
|
|
2688
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2785
2689
|
}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
"
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
"backup",
|
|
2795
|
-
"node_modules",
|
|
2796
|
-
".git",
|
|
2797
|
-
"dist",
|
|
2798
|
-
"build"
|
|
2799
|
-
]);
|
|
2800
|
-
function shouldSkipDirectory(name) {
|
|
2801
|
-
return IGNORED_DIRECTORY_NAMES.has(name.toLowerCase());
|
|
2690
|
+
function extractStringArrayMatch(content, fieldName) {
|
|
2691
|
+
const match = content.match(new RegExp(`${fieldName}\\s*:\\s*\\[([\\s\\S]*?)\\]`));
|
|
2692
|
+
if (!match?.[1]) {
|
|
2693
|
+
return [];
|
|
2694
|
+
}
|
|
2695
|
+
return Array.from(match[1].matchAll(/['"]([^'"]+)['"]/g), (entry) => entry[1] ?? "").filter(
|
|
2696
|
+
(value) => value.length > 0
|
|
2697
|
+
);
|
|
2802
2698
|
}
|
|
2803
|
-
function
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2699
|
+
function extractNamedObjectBlock(content, fieldName) {
|
|
2700
|
+
const nameMatch = new RegExp(`${fieldName}\\s*:\\s*\\{`).exec(content);
|
|
2701
|
+
if (!nameMatch) {
|
|
2702
|
+
return null;
|
|
2703
|
+
}
|
|
2704
|
+
const startIndex = nameMatch.index + nameMatch[0].length - 1;
|
|
2705
|
+
let depth = 0;
|
|
2706
|
+
for (let index = startIndex; index < content.length; index += 1) {
|
|
2707
|
+
const current = content[index];
|
|
2708
|
+
if (current === "{") {
|
|
2709
|
+
depth += 1;
|
|
2710
|
+
} else if (current === "}") {
|
|
2711
|
+
depth -= 1;
|
|
2712
|
+
if (depth === 0) {
|
|
2713
|
+
return content.slice(startIndex + 1, index);
|
|
2812
2714
|
}
|
|
2813
2715
|
}
|
|
2814
|
-
} catch {
|
|
2815
2716
|
}
|
|
2717
|
+
return null;
|
|
2816
2718
|
}
|
|
2817
|
-
function
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
while (index < queue.length) {
|
|
2822
|
-
const currentDir = queue[index++];
|
|
2823
|
-
if (!currentDir) continue;
|
|
2824
|
-
scanDirectory(currentDir, queue, sqlFiles);
|
|
2719
|
+
function extractAllowedDuplicateFunctions(content, normalizers) {
|
|
2720
|
+
const match = content.match(/allowedDuplicateFunctions\s*:\s*\[([\s\S]*?)\]/);
|
|
2721
|
+
if (!match?.[1]) {
|
|
2722
|
+
return [];
|
|
2825
2723
|
}
|
|
2826
|
-
|
|
2827
|
-
|
|
2724
|
+
const entries = [];
|
|
2725
|
+
for (const objectMatch of match[1].matchAll(/\{([\s\S]*?)\}/g)) {
|
|
2726
|
+
const objectBody = objectMatch[1] ?? "";
|
|
2727
|
+
const qualifiedName = extractFirstStringMatch(objectBody, "qualifiedName");
|
|
2728
|
+
const signature = extractFirstStringMatch(objectBody, "signature");
|
|
2729
|
+
const reason = extractFirstStringMatch(objectBody, "reason");
|
|
2730
|
+
if (!qualifiedName || !signature || !reason) {
|
|
2731
|
+
continue;
|
|
2732
|
+
}
|
|
2733
|
+
entries.push({
|
|
2734
|
+
qualifiedName: normalizers.normalizeFunctionQualifiedName(qualifiedName),
|
|
2735
|
+
signature: normalizers.normalizeAllowlistSignature(signature),
|
|
2736
|
+
reason,
|
|
2737
|
+
declarativeFile: extractFirstStringMatch(objectBody, "declarativeFile"),
|
|
2738
|
+
idempotentFile: extractFirstStringMatch(objectBody, "idempotentFile"),
|
|
2739
|
+
expectedBodyHash: extractFirstStringMatch(objectBody, "expectedBodyHash")
|
|
2740
|
+
});
|
|
2828
2741
|
}
|
|
2742
|
+
return entries;
|
|
2743
|
+
}
|
|
2744
|
+
function tryLoadSchemaGuardrailConfigFromText(params) {
|
|
2745
|
+
const configPath = findRunaConfig(params.targetDir);
|
|
2746
|
+
if (!configPath || !existsSync(configPath)) {
|
|
2747
|
+
return null;
|
|
2748
|
+
}
|
|
2749
|
+
try {
|
|
2750
|
+
const content = readFileSync(configPath, "utf-8");
|
|
2751
|
+
const schemaGuardrailsBlock = extractNamedObjectBlock(content, "schemaGuardrails") ?? "";
|
|
2752
|
+
const pgSchemaDiffBlock = extractNamedObjectBlock(content, "pgSchemaDiff") ?? "";
|
|
2753
|
+
return {
|
|
2754
|
+
...params.defaults,
|
|
2755
|
+
declarativeSqlDir: extractFirstStringMatch(schemaGuardrailsBlock, "declarativeSqlDir") ?? params.defaults.declarativeSqlDir,
|
|
2756
|
+
allowedDuplicateFunctions: extractAllowedDuplicateFunctions(
|
|
2757
|
+
schemaGuardrailsBlock,
|
|
2758
|
+
params.normalizers
|
|
2759
|
+
),
|
|
2760
|
+
generatedHeaderRewriteTargets: params.normalizers.normalizeFileList(
|
|
2761
|
+
extractStringArrayMatch(schemaGuardrailsBlock, "generatedHeaderRewriteTargets").map(
|
|
2762
|
+
(value) => params.normalizers.normalizePathForMatch(value)
|
|
2763
|
+
)
|
|
2764
|
+
),
|
|
2765
|
+
semanticWarnings: {
|
|
2766
|
+
threshold: extractFirstNumberMatch(schemaGuardrailsBlock, "threshold") ?? params.defaults.semanticWarnings.threshold,
|
|
2767
|
+
maxCandidates: extractFirstNumberMatch(schemaGuardrailsBlock, "maxCandidates") ?? params.defaults.semanticWarnings.maxCandidates,
|
|
2768
|
+
ignorePairs: new Set(
|
|
2769
|
+
extractStringArrayMatch(schemaGuardrailsBlock, "ignorePairs").map(
|
|
2770
|
+
(value) => params.normalizers.normalizeSuppressionPair(value)
|
|
2771
|
+
)
|
|
2772
|
+
)
|
|
2773
|
+
},
|
|
2774
|
+
tableHeaderMaxWidth: extractFirstNumberMatch(schemaGuardrailsBlock, "tableHeaderMaxWidth") ?? params.defaults.tableHeaderMaxWidth,
|
|
2775
|
+
excludeFromOrphanDetection: extractStringArrayMatch(
|
|
2776
|
+
pgSchemaDiffBlock,
|
|
2777
|
+
"excludeFromOrphanDetection"
|
|
2778
|
+
),
|
|
2779
|
+
idempotentSqlDir: extractFirstStringMatch(pgSchemaDiffBlock, "idempotentSqlDir") ?? params.defaults.idempotentSqlDir
|
|
2780
|
+
};
|
|
2781
|
+
} catch {
|
|
2782
|
+
return null;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// src/commands/db/sync/schema-guardrail-config.ts
|
|
2787
|
+
var DEFAULT_TABLE_HEADER_MAX_WIDTH = 160;
|
|
2788
|
+
var DEFAULT_SEMANTIC_WARNING_THRESHOLD = 0.55;
|
|
2789
|
+
var DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES = 3;
|
|
2790
|
+
var GENERIC_SIMILARITY_COLUMNS = /* @__PURE__ */ new Set([
|
|
2791
|
+
"id",
|
|
2792
|
+
"created_at",
|
|
2793
|
+
"updated_at",
|
|
2794
|
+
"deleted_at",
|
|
2795
|
+
"scope_id"
|
|
2796
|
+
]);
|
|
2797
|
+
function normalizePathForMatch(filePath) {
|
|
2798
|
+
return filePath.replaceAll("\\", "/");
|
|
2799
|
+
}
|
|
2800
|
+
function normalizeFunctionQualifiedName(value) {
|
|
2801
|
+
return value.trim().toLowerCase();
|
|
2802
|
+
}
|
|
2803
|
+
function normalizeAllowlistSignature(value) {
|
|
2804
|
+
return value.replace(/\s+/g, " ").trim();
|
|
2805
|
+
}
|
|
2806
|
+
function normalizeSuppressionPair(value) {
|
|
2807
|
+
return value.split("::").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0).sort((left, right) => left.localeCompare(right)).join("::");
|
|
2808
|
+
}
|
|
2809
|
+
function normalizeFileList2(files) {
|
|
2810
|
+
return [...new Set(files)].sort((a, b) => a.localeCompare(b));
|
|
2811
|
+
}
|
|
2812
|
+
function isSchemaGuardrailTextFallbackAllowed() {
|
|
2813
|
+
return Boolean(process.env.VITEST);
|
|
2814
|
+
}
|
|
2815
|
+
function createDefaultSchemaGuardrailConfig() {
|
|
2816
|
+
return {
|
|
2817
|
+
declarativeSqlDir: "supabase/schemas/declarative",
|
|
2818
|
+
allowedDuplicateFunctions: [],
|
|
2819
|
+
generatedHeaderRewriteTargets: [],
|
|
2820
|
+
semanticWarnings: {
|
|
2821
|
+
threshold: DEFAULT_SEMANTIC_WARNING_THRESHOLD,
|
|
2822
|
+
maxCandidates: DEFAULT_SEMANTIC_WARNING_MAX_CANDIDATES,
|
|
2823
|
+
ignorePairs: /* @__PURE__ */ new Set()
|
|
2824
|
+
},
|
|
2825
|
+
tableHeaderMaxWidth: DEFAULT_TABLE_HEADER_MAX_WIDTH,
|
|
2826
|
+
excludeFromOrphanDetection: [],
|
|
2827
|
+
idempotentSqlDir: "supabase/schemas/idempotent"
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
function loadAllowedDuplicatesFromSchemaOwnership(targetDir) {
|
|
2831
|
+
const candidates = [
|
|
2832
|
+
join(targetDir, "supabase", "schemas", "schema-ownership.json"),
|
|
2833
|
+
join(targetDir, "schema-ownership.json")
|
|
2834
|
+
];
|
|
2835
|
+
for (const filePath of candidates) {
|
|
2836
|
+
if (!existsSync(filePath)) continue;
|
|
2837
|
+
try {
|
|
2838
|
+
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
2839
|
+
const allowedDuplicates = raw?.rules?.allowed_duplicates;
|
|
2840
|
+
if (!Array.isArray(allowedDuplicates)) continue;
|
|
2841
|
+
return allowedDuplicates.filter(
|
|
2842
|
+
(entry) => typeof entry === "object" && entry !== null && "qualifiedName" in entry && typeof entry.qualifiedName === "string"
|
|
2843
|
+
).map((entry) => ({
|
|
2844
|
+
qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
|
|
2845
|
+
signature: normalizeAllowlistSignature(entry.signature ?? ""),
|
|
2846
|
+
reason: entry.reason ?? "Loaded from schema-ownership.json"
|
|
2847
|
+
}));
|
|
2848
|
+
} catch {
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return [];
|
|
2852
|
+
}
|
|
2853
|
+
function loadSchemaGuardrailConfig(targetDir) {
|
|
2854
|
+
const defaults = createDefaultSchemaGuardrailConfig();
|
|
2855
|
+
try {
|
|
2856
|
+
const config = loadRunaConfig(targetDir);
|
|
2857
|
+
const databaseConfig = config.database ?? {};
|
|
2858
|
+
return {
|
|
2859
|
+
declarativeSqlDir: databaseConfig.schemaGuardrails?.declarativeSqlDir ?? defaults.declarativeSqlDir,
|
|
2860
|
+
allowedDuplicateFunctions: [
|
|
2861
|
+
...databaseConfig.schemaGuardrails?.allowedDuplicateFunctions?.map((entry) => ({
|
|
2862
|
+
...entry,
|
|
2863
|
+
qualifiedName: normalizeFunctionQualifiedName(entry.qualifiedName),
|
|
2864
|
+
signature: normalizeAllowlistSignature(entry.signature),
|
|
2865
|
+
declarativeFile: entry.declarativeFile ? normalizePathForMatch(entry.declarativeFile) : void 0,
|
|
2866
|
+
idempotentFile: entry.idempotentFile ? normalizePathForMatch(entry.idempotentFile) : void 0
|
|
2867
|
+
})) ?? [],
|
|
2868
|
+
// Fallback: merge schema-ownership.json allowed_duplicates if present
|
|
2869
|
+
...loadAllowedDuplicatesFromSchemaOwnership(targetDir)
|
|
2870
|
+
],
|
|
2871
|
+
generatedHeaderRewriteTargets: normalizeFileList2(
|
|
2872
|
+
(databaseConfig.schemaGuardrails?.generatedHeaderRewriteTargets ?? []).map(
|
|
2873
|
+
(value) => normalizePathForMatch(value)
|
|
2874
|
+
)
|
|
2875
|
+
),
|
|
2876
|
+
semanticWarnings: {
|
|
2877
|
+
threshold: databaseConfig.schemaGuardrails?.semanticWarnings?.threshold ?? defaults.semanticWarnings.threshold,
|
|
2878
|
+
maxCandidates: databaseConfig.schemaGuardrails?.semanticWarnings?.maxCandidates ?? defaults.semanticWarnings.maxCandidates,
|
|
2879
|
+
ignorePairs: new Set(
|
|
2880
|
+
(databaseConfig.schemaGuardrails?.semanticWarnings?.ignorePairs ?? []).map(
|
|
2881
|
+
(value) => normalizeSuppressionPair(value)
|
|
2882
|
+
)
|
|
2883
|
+
)
|
|
2884
|
+
},
|
|
2885
|
+
tableHeaderMaxWidth: databaseConfig.schemaGuardrails?.tableHeaderMaxWidth ?? defaults.tableHeaderMaxWidth,
|
|
2886
|
+
excludeFromOrphanDetection: databaseConfig.pgSchemaDiff?.excludeFromOrphanDetection ?? [],
|
|
2887
|
+
idempotentSqlDir: databaseConfig.pgSchemaDiff?.idempotentSqlDir ?? defaults.idempotentSqlDir
|
|
2888
|
+
};
|
|
2889
|
+
} catch (error) {
|
|
2890
|
+
if (isSchemaGuardrailTextFallbackAllowed()) {
|
|
2891
|
+
return tryLoadSchemaGuardrailConfigFromText({
|
|
2892
|
+
targetDir,
|
|
2893
|
+
defaults,
|
|
2894
|
+
normalizers: {
|
|
2895
|
+
normalizeAllowlistSignature,
|
|
2896
|
+
normalizeFileList: normalizeFileList2,
|
|
2897
|
+
normalizeFunctionQualifiedName,
|
|
2898
|
+
normalizePathForMatch,
|
|
2899
|
+
normalizeSuppressionPair
|
|
2900
|
+
}
|
|
2901
|
+
}) ?? defaults;
|
|
2902
|
+
}
|
|
2903
|
+
throw error;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
// src/commands/db/utils/duplicate-function-ownership-allowlist.ts
|
|
2908
|
+
function matchesDuplicateFunctionSignature(params) {
|
|
2909
|
+
return normalizeFunctionQualifiedName(params.entry.qualifiedName) === normalizeFunctionQualifiedName(params.finding.qualifiedName) && normalizeAllowlistSignature(params.entry.signature) === normalizeAllowlistSignature(params.finding.signature ?? "");
|
|
2910
|
+
}
|
|
2911
|
+
function matchesOptionalFilePath(params) {
|
|
2912
|
+
if (!params.configuredPath) {
|
|
2913
|
+
return true;
|
|
2914
|
+
}
|
|
2915
|
+
return params.definitionFiles.some(
|
|
2916
|
+
(filePath) => normalizePathForMatch(filePath) === params.configuredPath
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2919
|
+
function matchesExpectedBodyHash(params) {
|
|
2920
|
+
if (!params.entry.expectedBodyHash || !params.bodyHashes) {
|
|
2921
|
+
return true;
|
|
2922
|
+
}
|
|
2923
|
+
const definitions = [
|
|
2924
|
+
...params.finding.declarativeDefinitions,
|
|
2925
|
+
...params.finding.idempotentDefinitions
|
|
2926
|
+
];
|
|
2927
|
+
if (definitions.length === 0) {
|
|
2928
|
+
return false;
|
|
2929
|
+
}
|
|
2930
|
+
return definitions.every((definition) => {
|
|
2931
|
+
const key = `${definition.layer}:${definition.file}:${definition.line}`;
|
|
2932
|
+
return params.bodyHashes?.get(key) === params.entry.expectedBodyHash;
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
function isAllowlistedDuplicateFunction(params) {
|
|
2936
|
+
const { finding, allowlist, bodyHashes } = params;
|
|
2937
|
+
if (!finding.signature) return false;
|
|
2938
|
+
return allowlist.some((entry) => {
|
|
2939
|
+
if (!matchesDuplicateFunctionSignature({ entry, finding })) {
|
|
2940
|
+
return false;
|
|
2941
|
+
}
|
|
2942
|
+
if (!matchesOptionalFilePath({
|
|
2943
|
+
configuredPath: entry.declarativeFile,
|
|
2944
|
+
definitionFiles: finding.declarativeDefinitions.map((definition) => definition.file)
|
|
2945
|
+
})) {
|
|
2946
|
+
return false;
|
|
2947
|
+
}
|
|
2948
|
+
if (!matchesOptionalFilePath({
|
|
2949
|
+
configuredPath: entry.idempotentFile,
|
|
2950
|
+
definitionFiles: finding.idempotentDefinitions.map((definition) => definition.file)
|
|
2951
|
+
})) {
|
|
2952
|
+
return false;
|
|
2953
|
+
}
|
|
2954
|
+
return matchesExpectedBodyHash({ entry, finding, bodyHashes });
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
function filterAllowlistedDuplicateFunctions(params) {
|
|
2958
|
+
return params.findings.filter(
|
|
2959
|
+
(finding) => !isAllowlistedDuplicateFunction({
|
|
2960
|
+
finding,
|
|
2961
|
+
allowlist: params.allowlist,
|
|
2962
|
+
bodyHashes: params.bodyHashes
|
|
2963
|
+
})
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// src/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.ts
|
|
2968
|
+
async function runDuplicateFunctionOwnershipCheck(result, logger4, step) {
|
|
2969
|
+
logger4.step("Checking duplicate function ownership", step.next());
|
|
2970
|
+
const analysis = analyzeDuplicateFunctionOwnership(process.cwd());
|
|
2971
|
+
let findings = analysis.findings;
|
|
2972
|
+
try {
|
|
2973
|
+
const allowlist = loadSchemaGuardrailConfig(process.cwd()).allowedDuplicateFunctions;
|
|
2974
|
+
findings = filterAllowlistedDuplicateFunctions({
|
|
2975
|
+
findings,
|
|
2976
|
+
allowlist
|
|
2977
|
+
});
|
|
2978
|
+
} catch {
|
|
2979
|
+
}
|
|
2980
|
+
if (findings.length === 0) {
|
|
2981
|
+
logger4.success("No duplicate declarative/idempotent function ownership detected");
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
result.passed = false;
|
|
2985
|
+
result.errors.push(`Found ${findings.length} duplicate function ownership finding(s)`);
|
|
2986
|
+
logger4.error(`Found ${findings.length} duplicate function ownership finding(s):`);
|
|
2987
|
+
logger4.info(` ${analysis.contractNote}`);
|
|
2988
|
+
for (const finding of findings) {
|
|
2989
|
+
const formatted = formatDuplicateFunctionOwnershipFinding(finding);
|
|
2990
|
+
logger4.info(` [ERROR] ${formatted.summary}`);
|
|
2991
|
+
for (const location of formatted.declarativeLocations) {
|
|
2992
|
+
logger4.info(` declarative: ${location}`);
|
|
2993
|
+
}
|
|
2994
|
+
for (const location of formatted.idempotentLocations) {
|
|
2995
|
+
logger4.info(` idempotent: ${location}`);
|
|
2996
|
+
}
|
|
2997
|
+
logger4.info(` ${formatted.suggestion}`);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
// src/commands/db/utils/preflight-checks/schema-boundary-checks.ts
|
|
3002
|
+
init_esm_shims();
|
|
3003
|
+
|
|
3004
|
+
// src/commands/db/commands/db-sync/precheck-helpers.ts
|
|
3005
|
+
init_esm_shims();
|
|
3006
|
+
var SHOW_ALLOWLIST_REPORT = process.env.RUNA_DB_PRECHECK_ALLOWLIST_REPORT === "1";
|
|
3007
|
+
var DIRECTORY_PLACEMENT_WARNING_PREFIX = " [misplacement] ";
|
|
3008
|
+
function applyStrictModeToReport(report, strict) {
|
|
3009
|
+
if (!strict) return report;
|
|
3010
|
+
return {
|
|
3011
|
+
blockers: [...report.blockers, ...report.warnings],
|
|
3012
|
+
warnings: []
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
function formatAllowlistReason({
|
|
3016
|
+
label,
|
|
3017
|
+
ruleId,
|
|
3018
|
+
reason,
|
|
3019
|
+
rule
|
|
3020
|
+
}) {
|
|
3021
|
+
const meta = rule ? formatAllowlistMetadata(rule) : "";
|
|
3022
|
+
return meta ? `[allowlist:${label}] ${ruleId}: ${reason} (${meta})` : `[allowlist:${label}] ${ruleId}: ${reason}`;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
// src/commands/db/commands/db-sync/directory-placement-check.ts
|
|
3026
|
+
init_esm_shims();
|
|
3027
|
+
|
|
3028
|
+
// src/commands/db/utils/schema-precheck-budget.ts
|
|
3029
|
+
init_esm_shims();
|
|
3030
|
+
var ONE_MB = 1024 * 1024;
|
|
3031
|
+
var ONE_GB = 1024 * ONE_MB;
|
|
3032
|
+
function parseIntEnv(name, fallback, min, max) {
|
|
3033
|
+
const raw = process.env[name];
|
|
3034
|
+
if (!raw) return fallback;
|
|
3035
|
+
const value = Number.parseInt(raw, 10);
|
|
3036
|
+
if (!Number.isFinite(value) || Number.isNaN(value)) return fallback;
|
|
3037
|
+
const normalized = Math.trunc(value);
|
|
3038
|
+
if (normalized < min) return min;
|
|
3039
|
+
if (normalized > max) return max;
|
|
3040
|
+
return normalized;
|
|
3041
|
+
}
|
|
3042
|
+
var RUNA_SCHEMA_PRECHECK_MAX_FILES = parseIntEnv(
|
|
3043
|
+
"RUNA_SCHEMA_PRECHECK_MAX_FILES",
|
|
3044
|
+
3e3,
|
|
3045
|
+
100,
|
|
3046
|
+
2e4
|
|
3047
|
+
);
|
|
3048
|
+
var RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES = parseIntEnv(
|
|
3049
|
+
"RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES",
|
|
3050
|
+
512 * ONE_MB,
|
|
3051
|
+
32 * ONE_MB,
|
|
3052
|
+
20 * ONE_GB
|
|
3053
|
+
);
|
|
3054
|
+
var RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES = parseIntEnv(
|
|
3055
|
+
"RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES",
|
|
3056
|
+
64 * ONE_MB,
|
|
3057
|
+
1 * ONE_MB,
|
|
3058
|
+
512 * ONE_MB
|
|
3059
|
+
);
|
|
3060
|
+
var RUNA_PLAN_PRECHECK_MAX_BYTES = parseIntEnv(
|
|
3061
|
+
"RUNA_PLAN_PRECHECK_MAX_BYTES",
|
|
3062
|
+
128 * ONE_MB,
|
|
3063
|
+
8 * ONE_MB,
|
|
3064
|
+
4 * ONE_GB
|
|
3065
|
+
);
|
|
3066
|
+
var RUNA_PLAN_PRECHECK_MAX_STATEMENTS = parseIntEnv(
|
|
3067
|
+
"RUNA_PLAN_PRECHECK_MAX_STATEMENTS",
|
|
3068
|
+
4e3,
|
|
3069
|
+
100,
|
|
3070
|
+
5e4
|
|
3071
|
+
);
|
|
3072
|
+
function formatBytes2(value) {
|
|
3073
|
+
if (value >= ONE_MB * 1024) return `${(value / (ONE_MB * 1024)).toFixed(1)} GB`;
|
|
3074
|
+
if (value >= ONE_MB) return `${(value / ONE_MB).toFixed(1)} MB`;
|
|
3075
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
3076
|
+
}
|
|
3077
|
+
function buildBudgetExceededReason(context, files, bytes, maxFiles, maxBytes) {
|
|
3078
|
+
return `${context}: budget exceeded (files=${files}/${maxFiles}, bytes=${formatBytes2(bytes)}/${formatBytes2(maxBytes)}). Stop and review with stricter scoping or increase RUNA_SCHEMA_PRECHECK_MAX_* settings only after approval.`;
|
|
3079
|
+
}
|
|
3080
|
+
function createSchemaPrecheckBudgetState() {
|
|
3081
|
+
return {
|
|
3082
|
+
scannedFiles: 0,
|
|
3083
|
+
scannedBytes: 0,
|
|
3084
|
+
maxFiles: RUNA_SCHEMA_PRECHECK_MAX_FILES,
|
|
3085
|
+
maxBytes: RUNA_SCHEMA_PRECHECK_MAX_TOTAL_BYTES,
|
|
3086
|
+
maxFileBytes: RUNA_SCHEMA_PRECHECK_MAX_FILE_BYTES
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
function shouldAbortSchemaPrecheckForBudget(state, filePath) {
|
|
3090
|
+
const size = statSync(filePath).size;
|
|
3091
|
+
if (size > state.maxFileBytes) {
|
|
3092
|
+
return `Unscanned schema file ${filePath}: ${formatBytes2(size)} exceeds per-file limit ${formatBytes2(
|
|
3093
|
+
state.maxFileBytes
|
|
3094
|
+
)}`;
|
|
3095
|
+
}
|
|
3096
|
+
if (state.scannedFiles + 1 > state.maxFiles) {
|
|
3097
|
+
return `Schema file scan budget exceeds ${state.maxFiles} files at ${path12.basename(filePath)}.`;
|
|
3098
|
+
}
|
|
3099
|
+
const projectedBytes = state.scannedBytes + size;
|
|
3100
|
+
if (projectedBytes > state.maxBytes) {
|
|
3101
|
+
return `Schema scan budget exceeds total size limit ${formatBytes2(state.maxBytes)}.`;
|
|
3102
|
+
}
|
|
3103
|
+
state.scannedBytes = projectedBytes;
|
|
3104
|
+
state.scannedFiles += 1;
|
|
3105
|
+
return null;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
// src/commands/db/utils/sql-file-collector.ts
|
|
3109
|
+
init_esm_shims();
|
|
3110
|
+
var IGNORED_DIRECTORY_NAMES = /* @__PURE__ */ new Set([
|
|
3111
|
+
"compat",
|
|
3112
|
+
"archive",
|
|
3113
|
+
"legacy",
|
|
3114
|
+
"deprecated",
|
|
3115
|
+
"backup",
|
|
3116
|
+
"node_modules",
|
|
3117
|
+
".git",
|
|
3118
|
+
"dist",
|
|
3119
|
+
"build"
|
|
3120
|
+
]);
|
|
3121
|
+
function shouldSkipDirectory(name) {
|
|
3122
|
+
return IGNORED_DIRECTORY_NAMES.has(name.toLowerCase());
|
|
3123
|
+
}
|
|
3124
|
+
function scanDirectory(dir, queue, sqlFiles) {
|
|
3125
|
+
try {
|
|
3126
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
3127
|
+
for (const entry of entries) {
|
|
3128
|
+
const fullPath = path12.join(dir, entry.name);
|
|
3129
|
+
if (entry.isDirectory()) {
|
|
3130
|
+
if (!shouldSkipDirectory(entry.name)) queue.push(fullPath);
|
|
3131
|
+
} else if (entry.isFile() && entry.name.endsWith(".sql")) {
|
|
3132
|
+
sqlFiles.push(fullPath);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
} catch {
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
function* collectSqlFilesRecursively(baseDir) {
|
|
3139
|
+
const queue = [baseDir];
|
|
3140
|
+
const sqlFiles = [];
|
|
3141
|
+
let index = 0;
|
|
3142
|
+
while (index < queue.length) {
|
|
3143
|
+
const currentDir = queue[index++];
|
|
3144
|
+
if (!currentDir) continue;
|
|
3145
|
+
scanDirectory(currentDir, queue, sqlFiles);
|
|
3146
|
+
}
|
|
3147
|
+
for (const file of sqlFiles) {
|
|
3148
|
+
yield file;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/commands/db/commands/db-sync/boundary-classifier.ts
|
|
3153
|
+
init_esm_shims();
|
|
3154
|
+
|
|
3155
|
+
// src/commands/db/commands/db-sync/sql-parser.ts
|
|
3156
|
+
init_esm_shims();
|
|
3157
|
+
function isWordChar(char) {
|
|
3158
|
+
const code = char.charCodeAt(0);
|
|
3159
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "_";
|
|
3160
|
+
}
|
|
3161
|
+
function isWhitespaceChar(char) {
|
|
3162
|
+
return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
|
|
2829
3163
|
}
|
|
2830
|
-
|
|
2831
|
-
// src/commands/db/commands/db-sync/boundary-classifier.ts
|
|
2832
|
-
init_esm_shims();
|
|
2833
|
-
|
|
2834
|
-
// src/commands/db/commands/db-sync/sql-parser.ts
|
|
2835
|
-
init_esm_shims();
|
|
2836
|
-
function isWordChar(char) {
|
|
2837
|
-
const code = char.charCodeAt(0);
|
|
2838
|
-
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || char === "_";
|
|
2839
|
-
}
|
|
2840
|
-
function isWhitespaceChar(char) {
|
|
2841
|
-
return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
|
|
2842
|
-
}
|
|
2843
3164
|
function parseWordAt(statement, start) {
|
|
2844
3165
|
if (start < 0 || start >= statement.length) return null;
|
|
2845
3166
|
const first = statement[start];
|
|
@@ -3884,7 +4205,7 @@ init_esm_shims();
|
|
|
3884
4205
|
var riskDetectorLoader = null;
|
|
3885
4206
|
function loadRiskDetectorModule() {
|
|
3886
4207
|
if (!riskDetectorLoader) {
|
|
3887
|
-
riskDetectorLoader = import('./risk-detector-
|
|
4208
|
+
riskDetectorLoader = import('./risk-detector-GDDLISVE.js').then((module) => ({
|
|
3888
4209
|
detectSchemaRisks: module.detectSchemaRisks
|
|
3889
4210
|
})).catch((error) => {
|
|
3890
4211
|
riskDetectorLoader = null;
|
|
@@ -4933,437 +5254,203 @@ var dbSyncMachine = setup({
|
|
|
4933
5254
|
}
|
|
4934
5255
|
],
|
|
4935
5256
|
onError: {
|
|
4936
|
-
target: "failed",
|
|
4937
|
-
actions: assign({
|
|
4938
|
-
error: ({ event }) => event.error instanceof Error ? event.error.message : "Setup failed"
|
|
4939
|
-
})
|
|
4940
|
-
}
|
|
4941
|
-
}
|
|
4942
|
-
},
|
|
4943
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4944
|
-
// Step 2: Reconcile (optional)
|
|
4945
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4946
|
-
reconcile: {
|
|
4947
|
-
meta: { e2e: e2eMeta.reconcile },
|
|
4948
|
-
invoke: {
|
|
4949
|
-
src: "reconcile",
|
|
4950
|
-
input: ({ context }) => ({
|
|
4951
|
-
ctx: assertStepCtx(context),
|
|
4952
|
-
autoApprove: context.input.autoApprove ?? false,
|
|
4953
|
-
force: context.input.force ?? false
|
|
4954
|
-
}),
|
|
4955
|
-
onDone: [
|
|
4956
|
-
{
|
|
4957
|
-
guard: ({ event }) => event.output.passed === false,
|
|
4958
|
-
target: "failed",
|
|
4959
|
-
actions: assign({
|
|
4960
|
-
error: ({ event }) => event.output.error ?? "Reconcile failed"
|
|
4961
|
-
})
|
|
4962
|
-
},
|
|
4963
|
-
{
|
|
4964
|
-
target: "preflight",
|
|
4965
|
-
actions: assign({ reconciled: true })
|
|
4966
|
-
}
|
|
4967
|
-
],
|
|
4968
|
-
onError: {
|
|
4969
|
-
// Non-critical, continue to preflight
|
|
4970
|
-
target: "preflight",
|
|
4971
|
-
actions: assign({ reconciled: false })
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
},
|
|
4975
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4976
|
-
// Step 3: Preflight Checks
|
|
4977
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
4978
|
-
preflight: {
|
|
4979
|
-
meta: { e2e: e2eMeta.preflight },
|
|
4980
|
-
invoke: {
|
|
4981
|
-
src: "preflight",
|
|
4982
|
-
input: ({ context }) => ({ ctx: assertStepCtx(context) }),
|
|
4983
|
-
onDone: [
|
|
4984
|
-
{
|
|
4985
|
-
guard: ({ event }) => event.output.passed === false,
|
|
4986
|
-
target: "failed",
|
|
4987
|
-
actions: assign({
|
|
4988
|
-
error: ({ event }) => event.output.error ?? "Preflight failed"
|
|
4989
|
-
})
|
|
4990
|
-
},
|
|
4991
|
-
{
|
|
4992
|
-
target: "snapshot",
|
|
4993
|
-
actions: assign({ preflightPassed: true })
|
|
4994
|
-
}
|
|
4995
|
-
],
|
|
4996
|
-
onError: {
|
|
4997
|
-
target: "failed",
|
|
4998
|
-
actions: assign({
|
|
4999
|
-
error: ({ event }) => event.error instanceof Error ? event.error.message : "Preflight failed"
|
|
5000
|
-
})
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
},
|
|
5004
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5005
|
-
// Step 4: Snapshot (optional)
|
|
5006
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5007
|
-
snapshot: {
|
|
5008
|
-
meta: { e2e: e2eMeta.snapshot },
|
|
5009
|
-
invoke: {
|
|
5010
|
-
src: "snapshot",
|
|
5011
|
-
input: ({ context }) => ({
|
|
5012
|
-
ctx: assertStepCtx(context),
|
|
5013
|
-
autoSnapshot: context.stepCtx?.autoSnapshot ?? false
|
|
5014
|
-
}),
|
|
5015
|
-
onDone: {
|
|
5016
|
-
target: "sync",
|
|
5017
|
-
actions: assign({
|
|
5018
|
-
snapshotCreated: ({ event }) => event.output.created
|
|
5019
|
-
})
|
|
5020
|
-
},
|
|
5021
|
-
onError: {
|
|
5022
|
-
// Non-critical, continue to sync
|
|
5023
|
-
target: "sync",
|
|
5024
|
-
actions: assign({ snapshotCreated: false })
|
|
5257
|
+
target: "failed",
|
|
5258
|
+
actions: assign({
|
|
5259
|
+
error: ({ event }) => event.error instanceof Error ? event.error.message : "Setup failed"
|
|
5260
|
+
})
|
|
5025
5261
|
}
|
|
5026
5262
|
}
|
|
5027
5263
|
},
|
|
5028
5264
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5029
|
-
// Step
|
|
5265
|
+
// Step 2: Reconcile (optional)
|
|
5030
5266
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5031
|
-
|
|
5032
|
-
meta: { e2e: e2eMeta.
|
|
5267
|
+
reconcile: {
|
|
5268
|
+
meta: { e2e: e2eMeta.reconcile },
|
|
5033
5269
|
invoke: {
|
|
5034
|
-
src: "
|
|
5035
|
-
input: ({ context }) => ({
|
|
5270
|
+
src: "reconcile",
|
|
5271
|
+
input: ({ context }) => ({
|
|
5272
|
+
ctx: assertStepCtx(context),
|
|
5273
|
+
autoApprove: context.input.autoApprove ?? false,
|
|
5274
|
+
force: context.input.force ?? false
|
|
5275
|
+
}),
|
|
5036
5276
|
onDone: [
|
|
5037
5277
|
{
|
|
5038
|
-
guard: ({ event }) => event.output.
|
|
5039
|
-
target: "
|
|
5278
|
+
guard: ({ event }) => event.output.passed === false,
|
|
5279
|
+
target: "failed",
|
|
5040
5280
|
actions: assign({
|
|
5041
|
-
|
|
5042
|
-
applyCommitted: ({ event }) => event.output.applyCommitted ?? false,
|
|
5043
|
-
stepsCompleted: ({ event }) => event.output.stepsCompleted,
|
|
5044
|
-
error: ({ event }) => event.output.error ?? "Sync failed"
|
|
5281
|
+
error: ({ event }) => event.output.error ?? "Reconcile failed"
|
|
5045
5282
|
})
|
|
5046
5283
|
},
|
|
5047
5284
|
{
|
|
5048
|
-
target: "
|
|
5049
|
-
actions: assign({
|
|
5050
|
-
applied: ({ event }) => event.output.applied,
|
|
5051
|
-
applyCommitted: ({ event }) => event.output.applyCommitted ?? false,
|
|
5052
|
-
stepsCompleted: ({ event }) => event.output.stepsCompleted
|
|
5053
|
-
})
|
|
5285
|
+
target: "preflight",
|
|
5286
|
+
actions: assign({ reconciled: true })
|
|
5054
5287
|
}
|
|
5055
5288
|
],
|
|
5056
5289
|
onError: {
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
applyCommitted: false,
|
|
5061
|
-
error: ({ event }) => event.error instanceof Error ? event.error.message : "Sync failed"
|
|
5062
|
-
})
|
|
5290
|
+
// Non-critical, continue to preflight
|
|
5291
|
+
target: "preflight",
|
|
5292
|
+
actions: assign({ reconciled: false })
|
|
5063
5293
|
}
|
|
5064
5294
|
}
|
|
5065
5295
|
},
|
|
5066
5296
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5067
|
-
// Step
|
|
5297
|
+
// Step 3: Preflight Checks
|
|
5068
5298
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5069
|
-
|
|
5070
|
-
meta: { e2e: e2eMeta.
|
|
5299
|
+
preflight: {
|
|
5300
|
+
meta: { e2e: e2eMeta.preflight },
|
|
5071
5301
|
invoke: {
|
|
5072
|
-
src: "
|
|
5073
|
-
input: ({ context }) => ({
|
|
5074
|
-
ctx: assertStepCtx(context),
|
|
5075
|
-
startTime: context.startTime,
|
|
5076
|
-
stepsCompleted: context.stepsCompleted,
|
|
5077
|
-
success: context.error == null,
|
|
5078
|
-
error: context.error ?? void 0
|
|
5079
|
-
}),
|
|
5302
|
+
src: "preflight",
|
|
5303
|
+
input: ({ context }) => ({ ctx: assertStepCtx(context) }),
|
|
5080
5304
|
onDone: [
|
|
5081
5305
|
{
|
|
5082
|
-
guard: ({
|
|
5306
|
+
guard: ({ event }) => event.output.passed === false,
|
|
5083
5307
|
target: "failed",
|
|
5084
|
-
actions: assign({
|
|
5308
|
+
actions: assign({
|
|
5309
|
+
error: ({ event }) => event.output.error ?? "Preflight failed"
|
|
5310
|
+
})
|
|
5085
5311
|
},
|
|
5086
5312
|
{
|
|
5087
|
-
target: "
|
|
5088
|
-
actions: assign({
|
|
5313
|
+
target: "snapshot",
|
|
5314
|
+
actions: assign({ preflightPassed: true })
|
|
5089
5315
|
}
|
|
5090
5316
|
],
|
|
5091
5317
|
onError: {
|
|
5092
5318
|
target: "failed",
|
|
5093
|
-
actions: assign({
|
|
5319
|
+
actions: assign({
|
|
5320
|
+
error: ({ event }) => event.error instanceof Error ? event.error.message : "Preflight failed"
|
|
5321
|
+
})
|
|
5094
5322
|
}
|
|
5095
5323
|
}
|
|
5096
5324
|
},
|
|
5097
5325
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5098
|
-
//
|
|
5326
|
+
// Step 4: Snapshot (optional)
|
|
5099
5327
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5100
|
-
|
|
5101
|
-
meta: { e2e: e2eMeta.
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
}
|
|
5120
|
-
|
|
5121
|
-
// src/commands/db/sync/guardrail-orchestrator.ts
|
|
5122
|
-
init_esm_shims();
|
|
5123
|
-
|
|
5124
|
-
// src/commands/db/sync/schema-guardrail.ts
|
|
5125
|
-
init_esm_shims();
|
|
5126
|
-
|
|
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()
|
|
5328
|
+
snapshot: {
|
|
5329
|
+
meta: { e2e: e2eMeta.snapshot },
|
|
5330
|
+
invoke: {
|
|
5331
|
+
src: "snapshot",
|
|
5332
|
+
input: ({ context }) => ({
|
|
5333
|
+
ctx: assertStepCtx(context),
|
|
5334
|
+
autoSnapshot: context.stepCtx?.autoSnapshot ?? false
|
|
5335
|
+
}),
|
|
5336
|
+
onDone: {
|
|
5337
|
+
target: "sync",
|
|
5338
|
+
actions: assign({
|
|
5339
|
+
snapshotCreated: ({ event }) => event.output.created
|
|
5340
|
+
})
|
|
5341
|
+
},
|
|
5342
|
+
onError: {
|
|
5343
|
+
// Non-critical, continue to sync
|
|
5344
|
+
target: "sync",
|
|
5345
|
+
actions: assign({ snapshotCreated: false })
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5278
5348
|
},
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
}
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
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
|
|
5349
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5350
|
+
// Step 5: Sync Schema
|
|
5351
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5352
|
+
sync: {
|
|
5353
|
+
meta: { e2e: e2eMeta.sync },
|
|
5354
|
+
invoke: {
|
|
5355
|
+
src: "syncSchema",
|
|
5356
|
+
input: ({ context }) => ({ ctx: assertStepCtx(context) }),
|
|
5357
|
+
onDone: [
|
|
5358
|
+
{
|
|
5359
|
+
guard: ({ event }) => event.output.applied === false && event.output.error != null,
|
|
5360
|
+
target: "report",
|
|
5361
|
+
actions: assign({
|
|
5362
|
+
applied: false,
|
|
5363
|
+
applyCommitted: ({ event }) => event.output.applyCommitted ?? false,
|
|
5364
|
+
stepsCompleted: ({ event }) => event.output.stepsCompleted,
|
|
5365
|
+
error: ({ event }) => event.output.error ?? "Sync failed"
|
|
5366
|
+
})
|
|
5367
|
+
},
|
|
5368
|
+
{
|
|
5369
|
+
target: "report",
|
|
5370
|
+
actions: assign({
|
|
5371
|
+
applied: ({ event }) => event.output.applied,
|
|
5372
|
+
applyCommitted: ({ event }) => event.output.applyCommitted ?? false,
|
|
5373
|
+
stepsCompleted: ({ event }) => event.output.stepsCompleted
|
|
5374
|
+
})
|
|
5375
|
+
}
|
|
5376
|
+
],
|
|
5377
|
+
onError: {
|
|
5378
|
+
target: "report",
|
|
5379
|
+
actions: assign({
|
|
5380
|
+
applied: false,
|
|
5381
|
+
applyCommitted: false,
|
|
5382
|
+
error: ({ event }) => event.error instanceof Error ? event.error.message : "Sync failed"
|
|
5383
|
+
})
|
|
5354
5384
|
}
|
|
5355
|
-
}
|
|
5385
|
+
}
|
|
5386
|
+
},
|
|
5387
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5388
|
+
// Step 6: Write Report
|
|
5389
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5390
|
+
report: {
|
|
5391
|
+
meta: { e2e: e2eMeta.report },
|
|
5392
|
+
invoke: {
|
|
5393
|
+
src: "writeReport",
|
|
5394
|
+
input: ({ context }) => ({
|
|
5395
|
+
ctx: assertStepCtx(context),
|
|
5396
|
+
startTime: context.startTime,
|
|
5397
|
+
stepsCompleted: context.stepsCompleted,
|
|
5398
|
+
success: context.error == null,
|
|
5399
|
+
error: context.error ?? void 0
|
|
5400
|
+
}),
|
|
5401
|
+
onDone: [
|
|
5402
|
+
{
|
|
5403
|
+
guard: ({ context }) => context.error != null,
|
|
5404
|
+
target: "failed",
|
|
5405
|
+
actions: assign({ reportWritten: true })
|
|
5406
|
+
},
|
|
5407
|
+
{
|
|
5408
|
+
target: "done",
|
|
5409
|
+
actions: assign({ reportWritten: true })
|
|
5410
|
+
}
|
|
5411
|
+
],
|
|
5412
|
+
onError: {
|
|
5413
|
+
target: "failed",
|
|
5414
|
+
actions: assign({ reportWritten: false })
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
},
|
|
5418
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5419
|
+
// Final States
|
|
5420
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5421
|
+
done: {
|
|
5422
|
+
meta: { e2e: e2eMeta.done },
|
|
5423
|
+
type: "final"
|
|
5424
|
+
},
|
|
5425
|
+
failed: {
|
|
5426
|
+
meta: { e2e: e2eMeta.failed },
|
|
5427
|
+
type: "final"
|
|
5356
5428
|
}
|
|
5357
|
-
|
|
5358
|
-
}
|
|
5429
|
+
},
|
|
5430
|
+
output: ({ context }) => buildMachineOutput(context)
|
|
5431
|
+
});
|
|
5432
|
+
function getDbSyncStateName(snapshot2) {
|
|
5433
|
+
return snapshot2.value;
|
|
5359
5434
|
}
|
|
5435
|
+
function isDbSyncComplete(snapshot2) {
|
|
5436
|
+
return snapshot2.status === "done";
|
|
5437
|
+
}
|
|
5438
|
+
function getDbSyncError(snapshot2) {
|
|
5439
|
+
return snapshot2.context.error;
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
// src/commands/db/sync/guardrail-orchestrator.ts
|
|
5443
|
+
init_esm_shims();
|
|
5444
|
+
|
|
5445
|
+
// src/commands/db/sync/schema-guardrail.ts
|
|
5446
|
+
init_esm_shims();
|
|
5360
5447
|
|
|
5361
5448
|
// src/commands/db/sync/schema-guardrail-graph.ts
|
|
5362
5449
|
init_esm_shims();
|
|
5363
5450
|
|
|
5364
5451
|
// src/commands/db/sync/schema-guardrail-runtime.ts
|
|
5365
5452
|
init_esm_shims();
|
|
5366
|
-
function
|
|
5453
|
+
function stableSorted2(values, map) {
|
|
5367
5454
|
return [...values].sort((a, b) => map(a).localeCompare(map(b)));
|
|
5368
5455
|
}
|
|
5369
5456
|
function normalizeFkSet(foreignKeys) {
|
|
@@ -5485,8 +5572,8 @@ function reconcileRuntimeGraph(params) {
|
|
|
5485
5572
|
});
|
|
5486
5573
|
}
|
|
5487
5574
|
return {
|
|
5488
|
-
warnings:
|
|
5489
|
-
contradictions:
|
|
5575
|
+
warnings: stableSorted2(warnings, (value) => value.target),
|
|
5576
|
+
contradictions: stableSorted2(contradictions, (value) => `${value.code}.${value.target}`)
|
|
5490
5577
|
};
|
|
5491
5578
|
}
|
|
5492
5579
|
function runSchemaGuardrailRuntime(input) {
|
|
@@ -5516,7 +5603,7 @@ function runSchemaGuardrailRuntime(input) {
|
|
|
5516
5603
|
|
|
5517
5604
|
// src/commands/db/sync/schema-guardrail-local-blockers.ts
|
|
5518
5605
|
init_esm_shims();
|
|
5519
|
-
function
|
|
5606
|
+
function stableSorted3(values, keyOf) {
|
|
5520
5607
|
return [...values].sort((left, right) => keyOf(left).localeCompare(keyOf(right)));
|
|
5521
5608
|
}
|
|
5522
5609
|
function createCrossSchemaHelperSuggestion(_filePath) {
|
|
@@ -5545,7 +5632,7 @@ function buildRawCrossSchemaRlsBlockers(params) {
|
|
|
5545
5632
|
});
|
|
5546
5633
|
}
|
|
5547
5634
|
}
|
|
5548
|
-
return
|
|
5635
|
+
return stableSorted3(
|
|
5549
5636
|
blockers,
|
|
5550
5637
|
(value) => `${value.sourceFile}:${value.line ?? 0}:${value.target}`
|
|
5551
5638
|
);
|
|
@@ -5572,7 +5659,7 @@ function buildExtensionPlacementBlockers(params) {
|
|
|
5572
5659
|
});
|
|
5573
5660
|
}
|
|
5574
5661
|
}
|
|
5575
|
-
return
|
|
5662
|
+
return stableSorted3(
|
|
5576
5663
|
blockers,
|
|
5577
5664
|
(value) => `${value.sourceFile}:${value.line ?? 0}:${value.target}`
|
|
5578
5665
|
);
|
|
@@ -5663,7 +5750,7 @@ function buildDynamicSqlBlockers(params) {
|
|
|
5663
5750
|
blockers.push(...stmtBlockers);
|
|
5664
5751
|
}
|
|
5665
5752
|
}
|
|
5666
|
-
return
|
|
5753
|
+
return stableSorted3(blockers, (value) => `${value.sourceFile}:${value.line ?? 0}:${value.kind}`);
|
|
5667
5754
|
}
|
|
5668
5755
|
function buildLocalBlindSpotBlockers(params) {
|
|
5669
5756
|
const blockers = [
|
|
@@ -5680,7 +5767,7 @@ function buildLocalBlindSpotBlockers(params) {
|
|
|
5680
5767
|
requiredFile: detectExtensionFilePath()
|
|
5681
5768
|
})
|
|
5682
5769
|
];
|
|
5683
|
-
return
|
|
5770
|
+
return stableSorted3(
|
|
5684
5771
|
blockers,
|
|
5685
5772
|
(value) => `${value.kind}:${value.sourceFile}:${value.line ?? 0}:${value.target}`
|
|
5686
5773
|
);
|
|
@@ -5688,7 +5775,7 @@ function buildLocalBlindSpotBlockers(params) {
|
|
|
5688
5775
|
|
|
5689
5776
|
// src/commands/db/sync/schema-guardrail-semantic-warnings.ts
|
|
5690
5777
|
init_esm_shims();
|
|
5691
|
-
function
|
|
5778
|
+
function stableSorted4(values, map) {
|
|
5692
5779
|
return [...values].sort((a, b) => map(a).localeCompare(map(b)));
|
|
5693
5780
|
}
|
|
5694
5781
|
function normalizeFkSet2(foreignKeys) {
|
|
@@ -5893,50 +5980,7 @@ function buildSemanticDuplicateWarnings(params) {
|
|
|
5893
5980
|
suppressionKey: createSuppressionPair(proposed.qualifiedName, primary.qualifiedName)
|
|
5894
5981
|
});
|
|
5895
5982
|
}
|
|
5896
|
-
return
|
|
5897
|
-
}
|
|
5898
|
-
|
|
5899
|
-
// src/commands/db/sync/schema-guardrail-graph-types.ts
|
|
5900
|
-
init_esm_shims();
|
|
5901
|
-
var GENERATOR_VERSION = "1.0.0";
|
|
5902
|
-
var SQL_IDENTIFIER_PATTERN = /"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*/g;
|
|
5903
|
-
var SQL_EXTENSION_IDENTIFIER_PATTERN = /"(?:[^"]|"")+"|[A-Za-z0-9_-]+/g;
|
|
5904
|
-
var QUALIFIED_SQL_OBJECT_RE = /\b([A-Za-z_][A-Za-z0-9_$]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_$]*)\b/g;
|
|
5905
|
-
var SECURITY_DEFINER_RE = /\bSECURITY\s+DEFINER\b/i;
|
|
5906
|
-
var SEARCH_PATH_LOCK_RE = /\bSET\s+search_path\s*(?:=|TO)\s*''/i;
|
|
5907
|
-
var MANAGED_BOUNDARY_SCHEMAS = [
|
|
5908
|
-
"auth",
|
|
5909
|
-
"storage",
|
|
5910
|
-
"extensions",
|
|
5911
|
-
"net",
|
|
5912
|
-
"supabase_functions"
|
|
5913
|
-
];
|
|
5914
|
-
var TRIGGER_GUIDANCE_SUPPRESSED_FUNCTIONS = /* @__PURE__ */ new Set(["public.handle_updated_at()"]);
|
|
5915
|
-
var MAX_SCHEMA_GUIDANCE_TARGETS_PER_FILE = 3;
|
|
5916
|
-
function makeGraphVersion(graph) {
|
|
5917
|
-
const canonical = JSON.stringify(graph);
|
|
5918
|
-
const hash = createHash("sha256").update(canonical).digest("hex");
|
|
5919
|
-
return `sha256:${hash}`;
|
|
5920
|
-
}
|
|
5921
|
-
function currentIsoTimestamp() {
|
|
5922
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5923
|
-
}
|
|
5924
|
-
function stableSorted4(values, map) {
|
|
5925
|
-
return [...values].sort((a, b) => map(a).localeCompare(map(b)));
|
|
5926
|
-
}
|
|
5927
|
-
function normalizeFileList2(files) {
|
|
5928
|
-
return [...new Set(files)].sort((a, b) => a.localeCompare(b));
|
|
5929
|
-
}
|
|
5930
|
-
function normalizePolicyCommand(input) {
|
|
5931
|
-
switch (input.toLowerCase()) {
|
|
5932
|
-
case "select":
|
|
5933
|
-
case "insert":
|
|
5934
|
-
case "update":
|
|
5935
|
-
case "delete":
|
|
5936
|
-
return input.toLowerCase();
|
|
5937
|
-
default:
|
|
5938
|
-
return "all";
|
|
5939
|
-
}
|
|
5983
|
+
return stableSorted4(warnings, (value) => `${value.proposedTable}.${value.suppressionKey}`);
|
|
5940
5984
|
}
|
|
5941
5985
|
|
|
5942
5986
|
// src/commands/db/sync/schema-guardrail-graph-nodes.ts
|
|
@@ -5960,37 +6004,6 @@ function buildFunctionBodyHashMap(files, layer) {
|
|
|
5960
6004
|
}
|
|
5961
6005
|
return hashes;
|
|
5962
6006
|
}
|
|
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
6007
|
function normalizeQualifiedObjectRef(schema, name, isFunctionCall) {
|
|
5995
6008
|
return `${schema}.${name}${isFunctionCall ? "()" : ""}`;
|
|
5996
6009
|
}
|
|
@@ -6013,8 +6026,8 @@ function collectQualifiedObjectRefs(params) {
|
|
|
6013
6026
|
schemas.add(schema);
|
|
6014
6027
|
}
|
|
6015
6028
|
return {
|
|
6016
|
-
refs:
|
|
6017
|
-
schemas:
|
|
6029
|
+
refs: stableSorted(refs, (value) => value),
|
|
6030
|
+
schemas: stableSorted(schemas, (value) => value)
|
|
6018
6031
|
};
|
|
6019
6032
|
}
|
|
6020
6033
|
function isSearchPathLocked(statement) {
|
|
@@ -6052,7 +6065,7 @@ function buildDefinedFunctionMetadataByFile(params) {
|
|
|
6052
6065
|
}
|
|
6053
6066
|
metadataByFile.set(
|
|
6054
6067
|
file.relativePath,
|
|
6055
|
-
|
|
6068
|
+
stableSorted(fileMetadata, (value) => value.qualifiedSignature)
|
|
6056
6069
|
);
|
|
6057
6070
|
}
|
|
6058
6071
|
return metadataByFile;
|
|
@@ -6146,13 +6159,13 @@ async function buildDeclarativeFileRecord(file) {
|
|
|
6146
6159
|
file,
|
|
6147
6160
|
declaredSchemas: [...declaredSchemas].sort((a, b) => a.localeCompare(b)),
|
|
6148
6161
|
createSchemaClaims: [...createSchemaClaims].sort((a, b) => a.localeCompare(b)),
|
|
6149
|
-
tables:
|
|
6162
|
+
tables: stableSorted(
|
|
6150
6163
|
document.tables.map((table) => ({
|
|
6151
6164
|
schema: table.schema,
|
|
6152
6165
|
name: table.name,
|
|
6153
6166
|
qualifiedName: table.qualifiedName,
|
|
6154
6167
|
lineNumber: table.lineNumber,
|
|
6155
|
-
columns:
|
|
6168
|
+
columns: stableSorted(table.columns, (value) => value.name).map((value) => value.name),
|
|
6156
6169
|
outboundForeignKeys: table.foreignKeys.map((fk) => ({
|
|
6157
6170
|
column: fk.column,
|
|
6158
6171
|
referencesTable: fk.referencesTable,
|
|
@@ -6163,7 +6176,7 @@ async function buildDeclarativeFileRecord(file) {
|
|
|
6163
6176
|
})),
|
|
6164
6177
|
(value) => value.qualifiedName
|
|
6165
6178
|
),
|
|
6166
|
-
policies:
|
|
6179
|
+
policies: stableSorted(
|
|
6167
6180
|
document.policies.map((policy) => ({
|
|
6168
6181
|
name: policy.name,
|
|
6169
6182
|
targetTable: `${policy.schema}.${policy.table}`,
|
|
@@ -6217,10 +6230,10 @@ function collectDeclarativeClaims(records) {
|
|
|
6217
6230
|
};
|
|
6218
6231
|
}
|
|
6219
6232
|
function createDuplicateTableOwners(tableOwnerClaims) {
|
|
6220
|
-
return
|
|
6233
|
+
return stableSorted(
|
|
6221
6234
|
[...tableOwnerClaims.entries()].filter(([, files]) => files.size > 1).map(([qualifiedName, files]) => ({
|
|
6222
6235
|
qualifiedName,
|
|
6223
|
-
files:
|
|
6236
|
+
files: normalizeFileList(files)
|
|
6224
6237
|
})),
|
|
6225
6238
|
(value) => value.qualifiedName
|
|
6226
6239
|
);
|
|
@@ -6234,7 +6247,7 @@ function buildFunctionValidationArtifacts(params) {
|
|
|
6234
6247
|
...buildFunctionBodyHashMap(params.declarativeFiles, "declarative"),
|
|
6235
6248
|
...buildFunctionBodyHashMap(params.idempotentFiles, "idempotent")
|
|
6236
6249
|
]);
|
|
6237
|
-
const duplicateFunctionOwners =
|
|
6250
|
+
const duplicateFunctionOwners = stableSorted(
|
|
6238
6251
|
functionAnalysis.findings.filter(
|
|
6239
6252
|
(finding) => !isAllowlistedDuplicateFunction({
|
|
6240
6253
|
finding,
|
|
@@ -6244,16 +6257,16 @@ function buildFunctionValidationArtifacts(params) {
|
|
|
6244
6257
|
).map((finding) => ({
|
|
6245
6258
|
qualifiedName: finding.qualifiedName,
|
|
6246
6259
|
signature: finding.signature,
|
|
6247
|
-
declarativeFiles:
|
|
6260
|
+
declarativeFiles: normalizeFileList(
|
|
6248
6261
|
finding.declarativeDefinitions.map((value) => value.file)
|
|
6249
6262
|
),
|
|
6250
|
-
idempotentFiles:
|
|
6263
|
+
idempotentFiles: normalizeFileList(
|
|
6251
6264
|
finding.idempotentDefinitions.map((value) => value.file)
|
|
6252
6265
|
)
|
|
6253
6266
|
})),
|
|
6254
6267
|
(value) => `${value.qualifiedName}.${value.signature ?? ""}`
|
|
6255
6268
|
);
|
|
6256
|
-
const functionClaims =
|
|
6269
|
+
const functionClaims = stableSorted(
|
|
6257
6270
|
[
|
|
6258
6271
|
...functionAnalysis.definitions.declarative.map((definition) => ({
|
|
6259
6272
|
qualifiedName: definition.qualifiedName,
|
|
@@ -6282,7 +6295,7 @@ function buildFunctionValidationArtifacts(params) {
|
|
|
6282
6295
|
function buildOwnerFileByTable(tableOwnerClaims) {
|
|
6283
6296
|
const ownerFileByTable = /* @__PURE__ */ new Map();
|
|
6284
6297
|
for (const [qualifiedName, files] of tableOwnerClaims.entries()) {
|
|
6285
|
-
const normalizedFiles =
|
|
6298
|
+
const normalizedFiles = normalizeFileList(files);
|
|
6286
6299
|
if (normalizedFiles[0]) {
|
|
6287
6300
|
ownerFileByTable.set(qualifiedName, normalizedFiles[0]);
|
|
6288
6301
|
}
|
|
@@ -6302,7 +6315,7 @@ function buildPolicyOwnershipConflicts(params) {
|
|
|
6302
6315
|
conflicts.push({
|
|
6303
6316
|
policyName: policyClaim.name,
|
|
6304
6317
|
targetTable: policyClaim.targetTable,
|
|
6305
|
-
files:
|
|
6318
|
+
files: normalizeFileList([policyClaim.sourceFile]),
|
|
6306
6319
|
tableOwnerFile: ownerFile
|
|
6307
6320
|
});
|
|
6308
6321
|
}
|
|
@@ -6313,11 +6326,11 @@ function buildPolicyOwnershipConflicts(params) {
|
|
|
6313
6326
|
conflicts.push({
|
|
6314
6327
|
policyName,
|
|
6315
6328
|
targetTable,
|
|
6316
|
-
files:
|
|
6329
|
+
files: normalizeFileList(files),
|
|
6317
6330
|
tableOwnerFile: params.ownerFileByTable.get(targetTable) ?? ""
|
|
6318
6331
|
});
|
|
6319
6332
|
}
|
|
6320
|
-
return
|
|
6333
|
+
return stableSorted(
|
|
6321
6334
|
conflicts,
|
|
6322
6335
|
(value) => `${value.targetTable}.${value.policyName}.${value.files.join(",")}`
|
|
6323
6336
|
);
|
|
@@ -6333,16 +6346,16 @@ function buildTableNodesByName(params) {
|
|
|
6333
6346
|
ownerFile: record.file.relativePath,
|
|
6334
6347
|
lineNumber: table.lineNumber,
|
|
6335
6348
|
columns: table.columns,
|
|
6336
|
-
outboundForeignKeys:
|
|
6349
|
+
outboundForeignKeys: stableSorted(
|
|
6337
6350
|
table.outboundForeignKeys,
|
|
6338
6351
|
(value) => `${value.referencesTable}.${value.column}`
|
|
6339
6352
|
),
|
|
6340
6353
|
inboundForeignKeys: [],
|
|
6341
|
-
policies:
|
|
6354
|
+
policies: stableSorted(
|
|
6342
6355
|
params.tablePolicies.get(table.qualifiedName) ?? [],
|
|
6343
6356
|
(value) => value.name
|
|
6344
6357
|
),
|
|
6345
|
-
triggers:
|
|
6358
|
+
triggers: stableSorted(
|
|
6346
6359
|
record.triggersByTable.get(table.qualifiedName) ?? [],
|
|
6347
6360
|
(value) => value.name
|
|
6348
6361
|
)
|
|
@@ -6366,7 +6379,7 @@ function attachInboundForeignKeys(tableNodesByName) {
|
|
|
6366
6379
|
}
|
|
6367
6380
|
}
|
|
6368
6381
|
for (const tableNode of tableNodesByName.values()) {
|
|
6369
|
-
tableNode.inboundForeignKeys =
|
|
6382
|
+
tableNode.inboundForeignKeys = stableSorted(
|
|
6370
6383
|
tableNode.inboundForeignKeys,
|
|
6371
6384
|
(value) => `${value.fromTable}.${value.fromColumn}`
|
|
6372
6385
|
);
|
|
@@ -6385,12 +6398,12 @@ function buildFileDependencies(params) {
|
|
|
6385
6398
|
fileDependencyMap.set(tableNode.ownerFile, edges);
|
|
6386
6399
|
}
|
|
6387
6400
|
}
|
|
6388
|
-
return
|
|
6401
|
+
return stableSorted(
|
|
6389
6402
|
[...fileDependencyMap.entries()].flatMap(
|
|
6390
6403
|
([fromFile, toMap]) => [...toMap.entries()].map(([toFile, viaTables]) => ({
|
|
6391
6404
|
fromFile,
|
|
6392
6405
|
toFile,
|
|
6393
|
-
viaTables:
|
|
6406
|
+
viaTables: normalizeFileList(viaTables)
|
|
6394
6407
|
}))
|
|
6395
6408
|
),
|
|
6396
6409
|
(value) => `${value.fromFile}->${value.toFile}`
|
|
@@ -6412,7 +6425,7 @@ function buildFileNodes(params) {
|
|
|
6412
6425
|
["22_observability_cron.sql", "cron-registration"],
|
|
6413
6426
|
["25_storage_seed_bucket.sql", "storage-bootstrap"]
|
|
6414
6427
|
]);
|
|
6415
|
-
return
|
|
6428
|
+
return stableSorted(
|
|
6416
6429
|
[
|
|
6417
6430
|
...params.records.map((record) => {
|
|
6418
6431
|
const definedFunctions = params.definedFunctionMetadataByFile.get(record.file.relativePath) ?? [];
|
|
@@ -6422,7 +6435,7 @@ function buildFileNodes(params) {
|
|
|
6422
6435
|
schemas: [],
|
|
6423
6436
|
refs: []
|
|
6424
6437
|
};
|
|
6425
|
-
const triggerFunctions =
|
|
6438
|
+
const triggerFunctions = stableSorted(
|
|
6426
6439
|
new Set(
|
|
6427
6440
|
[...record.triggersByTable.values()].flat().map((trigger) => normalizeTriggerFunctionToken(trigger.functionName)).filter((value) => value !== null)
|
|
6428
6441
|
),
|
|
@@ -6435,7 +6448,7 @@ function buildFileNodes(params) {
|
|
|
6435
6448
|
domainName: path12.basename(record.file.relativePath, ".sql"),
|
|
6436
6449
|
declaredSchemas: record.declaredSchemas,
|
|
6437
6450
|
ownedTables: record.tables.map((table) => table.qualifiedName),
|
|
6438
|
-
forwardDependsOnFiles:
|
|
6451
|
+
forwardDependsOnFiles: stableSorted(
|
|
6439
6452
|
params.fileDependencies.filter((edge) => edge.fromFile === record.file.relativePath).map((edge) => edge.toFile),
|
|
6440
6453
|
(value) => value
|
|
6441
6454
|
),
|
|
@@ -6443,7 +6456,7 @@ function buildFileNodes(params) {
|
|
|
6443
6456
|
securityDefinerFunctions: definedFunctions.filter((value) => value.securityDefiner).map((value) => value.qualifiedSignature),
|
|
6444
6457
|
securityDefinerContracts: definedFunctions.filter((value) => value.securityDefiner && value.searchPathLocked).map((value) => value.qualifiedSignature),
|
|
6445
6458
|
triggerFunctions,
|
|
6446
|
-
functionCrossSchemaRefs:
|
|
6459
|
+
functionCrossSchemaRefs: stableSorted(
|
|
6447
6460
|
new Set(definedFunctions.flatMap((value) => value.crossSchemaRefs)),
|
|
6448
6461
|
(value) => value
|
|
6449
6462
|
),
|
|
@@ -6453,7 +6466,7 @@ function buildFileNodes(params) {
|
|
|
6453
6466
|
touchedExtensions: [],
|
|
6454
6467
|
touchedFunctions: [],
|
|
6455
6468
|
touchedPolicies: [],
|
|
6456
|
-
policyCrossSchemaRefs:
|
|
6469
|
+
policyCrossSchemaRefs: stableSorted(
|
|
6457
6470
|
new Set(
|
|
6458
6471
|
collectPolicyCrossSchemaReferences({
|
|
6459
6472
|
content: record.file.content,
|
|
@@ -6490,7 +6503,7 @@ function buildFileNodes(params) {
|
|
|
6490
6503
|
securityDefinerFunctions: definedFunctions.filter((value) => value.securityDefiner).map((value) => value.qualifiedSignature),
|
|
6491
6504
|
securityDefinerContracts: definedFunctions.filter((value) => value.securityDefiner && value.searchPathLocked).map((value) => value.qualifiedSignature),
|
|
6492
6505
|
triggerFunctions: [],
|
|
6493
|
-
functionCrossSchemaRefs:
|
|
6506
|
+
functionCrossSchemaRefs: stableSorted(
|
|
6494
6507
|
new Set(definedFunctions.flatMap((value) => value.crossSchemaRefs)),
|
|
6495
6508
|
(value) => value
|
|
6496
6509
|
),
|
|
@@ -6653,7 +6666,7 @@ function extractTouchedSchemas(content) {
|
|
|
6653
6666
|
}
|
|
6654
6667
|
}
|
|
6655
6668
|
}
|
|
6656
|
-
return
|
|
6669
|
+
return stableSorted(touched, (value) => value);
|
|
6657
6670
|
}
|
|
6658
6671
|
function extractTouchedFunctions(content) {
|
|
6659
6672
|
const touched = /* @__PURE__ */ new Set();
|
|
@@ -6670,7 +6683,7 @@ function extractTouchedFunctions(content) {
|
|
|
6670
6683
|
}
|
|
6671
6684
|
}
|
|
6672
6685
|
}
|
|
6673
|
-
return
|
|
6686
|
+
return stableSorted(touched, (value) => value);
|
|
6674
6687
|
}
|
|
6675
6688
|
function normalizeExtensionIdentifier(identifier) {
|
|
6676
6689
|
const trimmed = identifier.trim();
|
|
@@ -6693,7 +6706,7 @@ function extractTouchedExtensions(content) {
|
|
|
6693
6706
|
}
|
|
6694
6707
|
touched.add(normalizeExtensionIdentifier(identifier));
|
|
6695
6708
|
}
|
|
6696
|
-
return
|
|
6709
|
+
return stableSorted(touched, (value) => value);
|
|
6697
6710
|
}
|
|
6698
6711
|
function extractTouchedPolicies(content) {
|
|
6699
6712
|
const touched = /* @__PURE__ */ new Set();
|
|
@@ -6707,7 +6720,7 @@ function extractTouchedPolicies(content) {
|
|
|
6707
6720
|
}
|
|
6708
6721
|
touched.add(`${schemaName}.${tableName}.${policyName}`);
|
|
6709
6722
|
}
|
|
6710
|
-
return
|
|
6723
|
+
return stableSorted(touched, (value) => value);
|
|
6711
6724
|
}
|
|
6712
6725
|
function extractIdempotentTouchMetadata(file) {
|
|
6713
6726
|
const sanitizedContent = sanitizeIdempotentTouchScanContent(file.content);
|
|
@@ -6725,7 +6738,7 @@ function buildIdempotentTouchMetadata(files) {
|
|
|
6725
6738
|
// src/commands/db/sync/schema-guardrail-graph-guidance.ts
|
|
6726
6739
|
init_esm_shims();
|
|
6727
6740
|
function summarizeBoundaryTargets(targets) {
|
|
6728
|
-
const sortedTargets =
|
|
6741
|
+
const sortedTargets = stableSorted(new Set(targets), (value) => value);
|
|
6729
6742
|
if (sortedTargets.length <= 2) {
|
|
6730
6743
|
return sortedTargets.join(", ");
|
|
6731
6744
|
}
|
|
@@ -6878,7 +6891,7 @@ function buildUnlockedSecurityDefinerGuidanceWarnings(params) {
|
|
|
6878
6891
|
return [];
|
|
6879
6892
|
}
|
|
6880
6893
|
const lockedContracts = new Set(params.fileNode.securityDefinerContracts);
|
|
6881
|
-
const unlockedFunctions =
|
|
6894
|
+
const unlockedFunctions = stableSorted(
|
|
6882
6895
|
params.fileNode.securityDefinerFunctions.filter(
|
|
6883
6896
|
(qualifiedName) => !lockedContracts.has(qualifiedName)
|
|
6884
6897
|
),
|
|
@@ -6942,7 +6955,7 @@ function buildSecurityDefinerGuidanceWarnings(params) {
|
|
|
6942
6955
|
if (!suggestedIdempotentFile || !targets) {
|
|
6943
6956
|
return [];
|
|
6944
6957
|
}
|
|
6945
|
-
const touchedSecurityDefiners =
|
|
6958
|
+
const touchedSecurityDefiners = stableSorted(
|
|
6946
6959
|
new Set(targets.filter((target) => securityDefinerSet.has(target))),
|
|
6947
6960
|
(value) => value
|
|
6948
6961
|
);
|
|
@@ -7030,7 +7043,7 @@ function buildBoundaryGuidanceWarnings(params) {
|
|
|
7030
7043
|
);
|
|
7031
7044
|
continue;
|
|
7032
7045
|
}
|
|
7033
|
-
const touchedSchemas =
|
|
7046
|
+
const touchedSchemas = stableSorted(new Set(fileNode.touchedSchemas), (value) => value);
|
|
7034
7047
|
if (touchedSchemas.length <= MAX_SCHEMA_GUIDANCE_TARGETS_PER_FILE) {
|
|
7035
7048
|
for (const schema of touchedSchemas) {
|
|
7036
7049
|
const ownerFile = schemaOwnerByName.get(schema);
|
|
@@ -7067,7 +7080,7 @@ function buildBoundaryGuidanceWarnings(params) {
|
|
|
7067
7080
|
reason: "This idempotent file applies policy DDL against a table that is structurally owned in declarative SQL."
|
|
7068
7081
|
});
|
|
7069
7082
|
}
|
|
7070
|
-
return
|
|
7083
|
+
return stableSorted(
|
|
7071
7084
|
warnings,
|
|
7072
7085
|
(value) => `${value.sourceFile}.${value.kind}.${value.suggestedDeclarativeFile ?? ""}.${value.suggestedIdempotentFile ?? ""}.${value.target}`
|
|
7073
7086
|
);
|
|
@@ -7143,7 +7156,7 @@ function validateDispatchCoverage(argsByFunction, sources) {
|
|
|
7143
7156
|
}
|
|
7144
7157
|
}
|
|
7145
7158
|
}
|
|
7146
|
-
return
|
|
7159
|
+
return stableSorted(warnings, (w) => `${w.sourceFile}:${w.target}`);
|
|
7147
7160
|
}
|
|
7148
7161
|
var CREATE_INDEX_PATTERN = /^\s*CREATE\s+(?:UNIQUE\s+)?(?:INDEX\s+)?(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?(?:"([^"]+)"|([A-Za-z_]\w*))\s+ON\b/gim;
|
|
7149
7162
|
function extractIndexNames(content) {
|
|
@@ -7178,7 +7191,7 @@ function buildCrossLayerDuplicateIndexWarnings(sources) {
|
|
|
7178
7191
|
}
|
|
7179
7192
|
}
|
|
7180
7193
|
}
|
|
7181
|
-
return
|
|
7194
|
+
return stableSorted(warnings, (w) => `${w.sourceFile}:${w.target}`);
|
|
7182
7195
|
}
|
|
7183
7196
|
|
|
7184
7197
|
// src/commands/db/sync/schema-guardrail-graph.ts
|
|
@@ -7189,11 +7202,11 @@ function loadSqlSources(targetDir, config) {
|
|
|
7189
7202
|
};
|
|
7190
7203
|
}
|
|
7191
7204
|
function buildSchemaNodes(params) {
|
|
7192
|
-
return
|
|
7205
|
+
return stableSorted(
|
|
7193
7206
|
[...params.schemaClaims.entries()].map(([name, files]) => ({
|
|
7194
7207
|
name,
|
|
7195
|
-
claimFiles:
|
|
7196
|
-
createSchemaFiles:
|
|
7208
|
+
claimFiles: normalizeFileList(files),
|
|
7209
|
+
createSchemaFiles: normalizeFileList(params.createSchemaClaims.get(name) ?? [])
|
|
7197
7210
|
})),
|
|
7198
7211
|
(value) => value.name
|
|
7199
7212
|
);
|
|
@@ -7206,9 +7219,9 @@ function createSchemaGraphManifest(params) {
|
|
|
7206
7219
|
generatorVersion: GENERATOR_VERSION,
|
|
7207
7220
|
files: params.fileNodes,
|
|
7208
7221
|
schemas: params.schemaNodes,
|
|
7209
|
-
tables:
|
|
7222
|
+
tables: stableSorted(params.tableNodesByName.values(), (value) => value.qualifiedName),
|
|
7210
7223
|
functionClaims: params.functionClaims,
|
|
7211
|
-
policyClaims:
|
|
7224
|
+
policyClaims: stableSorted(
|
|
7212
7225
|
params.policyClaims,
|
|
7213
7226
|
(value) => `${value.targetTable}.${value.name}.${value.sourceFile}`
|
|
7214
7227
|
),
|
|
@@ -7235,7 +7248,12 @@ async function buildStaticGraph(targetDir, config, sources) {
|
|
|
7235
7248
|
const records = await Promise.all(
|
|
7236
7249
|
declarativeFiles.map((file) => buildDeclarativeFileRecord(file))
|
|
7237
7250
|
);
|
|
7251
|
+
const functionAclManifest = buildFunctionAclManifestFromSqlFiles(declarativeFiles);
|
|
7238
7252
|
const idempotentTouchMetadata = buildIdempotentTouchMetadata(idempotentFiles);
|
|
7253
|
+
idempotentTouchMetadata.set(
|
|
7254
|
+
FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH,
|
|
7255
|
+
buildFunctionAclIdempotentTouchMetadata(functionAclManifest)
|
|
7256
|
+
);
|
|
7239
7257
|
const { tableOwnerClaims, schemaClaims, createSchemaClaims, tablePolicies, policyClaims } = collectDeclarativeClaims(records);
|
|
7240
7258
|
const duplicateTableOwners = createDuplicateTableOwners(tableOwnerClaims);
|
|
7241
7259
|
const { duplicateFunctionOwners, functionClaims } = buildFunctionValidationArtifacts({
|
|
@@ -7297,7 +7315,7 @@ async function buildStaticGraph(targetDir, config, sources) {
|
|
|
7297
7315
|
policyClaims,
|
|
7298
7316
|
fileDependencies
|
|
7299
7317
|
});
|
|
7300
|
-
const multiFileSchemas =
|
|
7318
|
+
const multiFileSchemas = stableSorted(
|
|
7301
7319
|
graph.schemas.filter((schemaNode) => schemaNode.claimFiles.length > 1).map((schemaNode) => ({
|
|
7302
7320
|
schema: schemaNode.name,
|
|
7303
7321
|
files: schemaNode.claimFiles
|
|
@@ -7329,6 +7347,7 @@ async function buildStaticGraph(targetDir, config, sources) {
|
|
|
7329
7347
|
});
|
|
7330
7348
|
return {
|
|
7331
7349
|
graph,
|
|
7350
|
+
functionAclManifest,
|
|
7332
7351
|
duplicateTableOwners,
|
|
7333
7352
|
duplicateFunctionOwners,
|
|
7334
7353
|
policyOwnershipConflicts,
|
|
@@ -7345,8 +7364,8 @@ var GUARDRAIL_PHASE_LABELS = {
|
|
|
7345
7364
|
load_sources: "Load declarative SQL sources",
|
|
7346
7365
|
build_static_graph: "Build static SQL graph",
|
|
7347
7366
|
validate_ownership: "Validate ownership",
|
|
7348
|
-
compare_generated_headers: "Compare generated
|
|
7349
|
-
refresh_generated_headers: "Refresh generated
|
|
7367
|
+
compare_generated_headers: "Compare generated SQL artifacts",
|
|
7368
|
+
refresh_generated_headers: "Refresh generated SQL artifacts",
|
|
7350
7369
|
handoff_db_sync: "Hand off to db sync",
|
|
7351
7370
|
runtime_reconcile: "Runtime reconcile",
|
|
7352
7371
|
publish_report: "Publish report"
|
|
@@ -7435,7 +7454,7 @@ function createCheckModePhases(report) {
|
|
|
7435
7454
|
details: {
|
|
7436
7455
|
file: block.file,
|
|
7437
7456
|
target: block.target,
|
|
7438
|
-
repair: "Run `runa db sync` to auto-regenerate
|
|
7457
|
+
repair: "Run `runa db sync` to auto-regenerate managed SQL artifacts"
|
|
7439
7458
|
}
|
|
7440
7459
|
}))
|
|
7441
7460
|
})
|
|
@@ -7937,12 +7956,35 @@ function buildHeaderRewritePlans(params) {
|
|
|
7937
7956
|
}
|
|
7938
7957
|
function loadHeaderRewritePlans(params) {
|
|
7939
7958
|
try {
|
|
7940
|
-
|
|
7959
|
+
const headerPlans = buildHeaderRewritePlans({
|
|
7941
7960
|
targetDir: params.targetDir,
|
|
7942
7961
|
graph: params.graph,
|
|
7943
7962
|
tableHeaderMaxWidth: params.config.tableHeaderMaxWidth,
|
|
7944
7963
|
generatedHeaderRewriteTargets: params.config.generatedHeaderRewriteTargets
|
|
7945
7964
|
});
|
|
7965
|
+
const generatedFileResult = buildGeneratedFileRewritePlans({
|
|
7966
|
+
targetDir: params.targetDir,
|
|
7967
|
+
manifest: params.functionAclManifest
|
|
7968
|
+
});
|
|
7969
|
+
if ("failure" in generatedFileResult) {
|
|
7970
|
+
const failureMessage = generatedFileResult.failure ?? "Unknown function ACL migration failure";
|
|
7971
|
+
return {
|
|
7972
|
+
failure: {
|
|
7973
|
+
graph: params.graph,
|
|
7974
|
+
report: setFailure2(
|
|
7975
|
+
params.report,
|
|
7976
|
+
"compare_generated_headers",
|
|
7977
|
+
"function_acl_migration_required",
|
|
7978
|
+
failureMessage
|
|
7979
|
+
)
|
|
7980
|
+
}
|
|
7981
|
+
};
|
|
7982
|
+
}
|
|
7983
|
+
return {
|
|
7984
|
+
staleBlocks: [...headerPlans.staleBlocks, ...generatedFileResult.staleBlocks],
|
|
7985
|
+
rewritePlans: headerPlans.rewritePlans,
|
|
7986
|
+
generatedFileRewritePlans: generatedFileResult.rewritePlans
|
|
7987
|
+
};
|
|
7946
7988
|
} catch (error) {
|
|
7947
7989
|
const message = error instanceof Error ? error.message : String(error);
|
|
7948
7990
|
return {
|
|
@@ -7971,7 +8013,7 @@ function finalizeCheckModeReport(params) {
|
|
|
7971
8013
|
params.report,
|
|
7972
8014
|
"compare_generated_headers",
|
|
7973
8015
|
"stale_generated_header",
|
|
7974
|
-
`Generated
|
|
8016
|
+
`Generated SQL artifacts are stale in ${params.report.staleBlocks.map((value) => `${value.file}:${value.kind}`).join(", ")}`
|
|
7975
8017
|
)
|
|
7976
8018
|
};
|
|
7977
8019
|
}
|
|
@@ -7990,6 +8032,15 @@ function rewriteManagedHeaders(params) {
|
|
|
7990
8032
|
writeFileSync(path12.join(params.targetDir, plan.filePath), rewrittenSql, "utf-8");
|
|
7991
8033
|
params.report.headersRewritten.push(plan.filePath);
|
|
7992
8034
|
}
|
|
8035
|
+
for (const plan of params.generatedFileRewritePlans) {
|
|
8036
|
+
const absolutePath = path12.join(params.targetDir, plan.filePath);
|
|
8037
|
+
const currentSql = existsSync(absolutePath) ? readFileSync(absolutePath, "utf-8") : "";
|
|
8038
|
+
if (normalizeMultilineText(currentSql) === normalizeMultilineText(plan.expectedSql)) {
|
|
8039
|
+
continue;
|
|
8040
|
+
}
|
|
8041
|
+
writeFileSync(absolutePath, plan.expectedSql, "utf-8");
|
|
8042
|
+
params.report.headersRewritten.push(plan.filePath);
|
|
8043
|
+
}
|
|
7993
8044
|
} catch (error) {
|
|
7994
8045
|
const message = error instanceof Error ? error.message : String(error);
|
|
7995
8046
|
return {
|
|
@@ -8007,6 +8058,41 @@ function rewriteManagedHeaders(params) {
|
|
|
8007
8058
|
params.report.staleBlocks = [];
|
|
8008
8059
|
return null;
|
|
8009
8060
|
}
|
|
8061
|
+
function buildGeneratedFileRewritePlans(params) {
|
|
8062
|
+
const absolutePath = path12.join(params.targetDir, FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH);
|
|
8063
|
+
const fileExists = existsSync(absolutePath);
|
|
8064
|
+
const existingSql = fileExists ? readFileSync(absolutePath, "utf-8") : "";
|
|
8065
|
+
if (!fileExists && !functionAclManifestHasEntries(params.manifest)) {
|
|
8066
|
+
return {
|
|
8067
|
+
staleBlocks: [],
|
|
8068
|
+
rewritePlans: []
|
|
8069
|
+
};
|
|
8070
|
+
}
|
|
8071
|
+
if (fileExists) {
|
|
8072
|
+
const migrationGaps = validateFunctionAclMigration(params.manifest, existingSql);
|
|
8073
|
+
if (migrationGaps.length > 0) {
|
|
8074
|
+
return {
|
|
8075
|
+
failure: `Function ACL migration is required before auto-generation can proceed. Add declarative annotations for: ${migrationGaps.join(", ")}`
|
|
8076
|
+
};
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
const staleBlocks = !fileExists || isManagedFunctionAclFileContentStale(params.manifest, existingSql) ? [
|
|
8080
|
+
{
|
|
8081
|
+
file: FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH,
|
|
8082
|
+
kind: "generated-file",
|
|
8083
|
+
target: `file:${FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH}`
|
|
8084
|
+
}
|
|
8085
|
+
] : [];
|
|
8086
|
+
return {
|
|
8087
|
+
staleBlocks,
|
|
8088
|
+
rewritePlans: [
|
|
8089
|
+
{
|
|
8090
|
+
filePath: FUNCTION_ACL_RECONCILIATION_RELATIVE_PATH,
|
|
8091
|
+
expectedSql: renderFunctionAclFile(params.manifest)
|
|
8092
|
+
}
|
|
8093
|
+
]
|
|
8094
|
+
};
|
|
8095
|
+
}
|
|
8010
8096
|
|
|
8011
8097
|
// src/commands/db/sync/schema-guardrail.ts
|
|
8012
8098
|
function createEmptyReport(mode) {
|
|
@@ -8078,7 +8164,7 @@ function validateLocalBlindSpotBlockers(report, graph) {
|
|
|
8078
8164
|
if (!primary) {
|
|
8079
8165
|
return null;
|
|
8080
8166
|
}
|
|
8081
|
-
const failureCode = primary.kind === "cross-schema-rls" ? "raw_cross_schema_rls_blocked" : primary.kind === "dynamic-sql" ? "dynamic_sql_blocked" : "extension_placement_blocked";
|
|
8167
|
+
const failureCode = primary.kind === "cross-schema-rls" ? "raw_cross_schema_rls_blocked" : primary.kind === "dynamic-sql" || primary.kind === "dynamic-sql-infra" ? "dynamic_sql_blocked" : "extension_placement_blocked";
|
|
8082
8168
|
return {
|
|
8083
8169
|
graph,
|
|
8084
8170
|
report: setFailure3(report, "validate_ownership", failureCode, primary.details)
|
|
@@ -8169,6 +8255,7 @@ async function runSchemaGuardrailStatic(input) {
|
|
|
8169
8255
|
const headerPlanResult = loadHeaderRewritePlans({
|
|
8170
8256
|
targetDir: input.targetDir,
|
|
8171
8257
|
graph: staticGraphResult.graph,
|
|
8258
|
+
functionAclManifest: staticGraphResult.functionAclManifest,
|
|
8172
8259
|
config,
|
|
8173
8260
|
report
|
|
8174
8261
|
});
|
|
@@ -8190,6 +8277,7 @@ async function runSchemaGuardrailStatic(input) {
|
|
|
8190
8277
|
const rewriteFailure = rewriteManagedHeaders({
|
|
8191
8278
|
targetDir: input.targetDir,
|
|
8192
8279
|
rewritePlans: headerPlanResult.rewritePlans,
|
|
8280
|
+
generatedFileRewritePlans: headerPlanResult.generatedFileRewritePlans,
|
|
8193
8281
|
report
|
|
8194
8282
|
});
|
|
8195
8283
|
if (rewriteFailure) {
|
|
@@ -12069,14 +12157,10 @@ async function collectLocalPrecheckBundle(strict) {
|
|
|
12069
12157
|
guardrailAllowlist = loadSchemaGuardrailConfig(process.cwd()).allowedDuplicateFunctions;
|
|
12070
12158
|
} catch {
|
|
12071
12159
|
}
|
|
12072
|
-
const duplicateOwnershipBlockers =
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
bodyHashes: /* @__PURE__ */ new Map()
|
|
12077
|
-
// Hash check skipped; name + signature matching still works
|
|
12078
|
-
})
|
|
12079
|
-
).map((finding) => {
|
|
12160
|
+
const duplicateOwnershipBlockers = filterAllowlistedDuplicateFunctions({
|
|
12161
|
+
findings: duplicateOwnershipAnalysis.findings,
|
|
12162
|
+
allowlist: guardrailAllowlist
|
|
12163
|
+
}).map((finding) => {
|
|
12080
12164
|
const formatted = formatDuplicateFunctionOwnershipFinding(finding);
|
|
12081
12165
|
return [
|
|
12082
12166
|
formatted.summary,
|