code-auditor-mcp 4.0.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +124 -0
- package/README.md +5 -5
- package/configs/hhra-compat.json +2 -2
- package/dist/analyzers/applicability.d.ts +25 -2
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +44 -50
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -49
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +22 -240
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -14
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +0 -104
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/orgFilterTiers.d.ts +89 -0
- package/dist/analyzers/orgFilterTiers.d.ts.map +1 -0
- package/dist/analyzers/orgFilterTiers.js +96 -0
- package/dist/analyzers/orgFilterTiers.js.map +1 -0
- package/dist/analyzers/ruleRegistry.d.ts +15 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +139 -185
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +39 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +62 -41
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +2 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +2 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +86 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +606 -0
- package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +14 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +35 -18
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/migrations.d.ts +14 -1
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/migrations.js +58 -14
- package/dist/analyzers/universal/schema/migrations.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +70 -13
- package/dist/auditRunner.js.map +1 -1
- package/dist/churn/churnExtractor.js +5 -5
- package/dist/churn/churnExtractor.js.map +1 -1
- package/dist/cli.js +40 -9
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts +6 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +33 -1
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configGeneratePath.d.ts +19 -0
- package/dist/config/configGeneratePath.d.ts.map +1 -0
- package/dist/config/configGeneratePath.js +29 -0
- package/dist/config/configGeneratePath.js.map +1 -0
- package/dist/config/configLoader.d.ts +36 -4
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +136 -15
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/lintConfigReader.d.ts +4 -0
- package/dist/config/lintConfigReader.d.ts.map +1 -1
- package/dist/config/lintConfigReader.js +35 -36
- package/dist/config/lintConfigReader.js.map +1 -1
- package/dist/config/staticObjectExtract.d.ts +35 -0
- package/dist/config/staticObjectExtract.d.ts.map +1 -0
- package/dist/config/staticObjectExtract.js +312 -0
- package/dist/config/staticObjectExtract.js.map +1 -0
- package/dist/constants.d.ts +1 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -2
- package/dist/constants.js.map +1 -1
- package/dist/daemon/core.js +1 -1
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/main.js +1 -1
- package/dist/daemon/main.js.map +1 -1
- package/dist/dataPaths.d.ts +11 -0
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +24 -2
- package/dist/dataPaths.js.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts +0 -4
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js +1 -7
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/installConfig.d.ts +3 -1
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +15 -2
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts +10 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +14 -2
- package/dist/installer.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +6 -6
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +4 -2
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +77 -44
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/go/{analyzer → analyzer-darwin-amd64} +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/goBinary.d.ts +8 -7
- package/dist/languages/goBinary.d.ts.map +1 -1
- package/dist/languages/goBinary.js +8 -7
- package/dist/languages/goBinary.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +7 -2
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +5 -1
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +81 -29
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.js +19 -4
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +1 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/nextFileIncremental.js +1 -1
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +13 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +15 -0
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +140 -6
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +2 -2
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +45 -0
- package/dist/ruleAliases.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +4 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +54 -36
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/types.d.ts +55 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/examples/.auditrc.example.json +2 -2
- package/examples/.auditrc.json +3 -3
- package/examples/nextjs.auditrc.json +1 -1
- package/examples/node-api.auditrc.json +1 -1
- package/examples/react.auditrc.json +2 -2
- package/examples/tolerant-solid.auditrc.json +1 -1
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-common.sh +27 -27
- package/plugin/skills/code-auditor/SKILL.md +1 -1
- package/server.json +2 -2
package/dist/pipelineAdapters.js
CHANGED
|
@@ -26,6 +26,7 @@ import { isReactComponent, detectComponentType, getComponentName, } from './util
|
|
|
26
26
|
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
|
|
27
27
|
import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
|
|
28
28
|
import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
|
|
29
|
+
import { buildOrgFilterTierSet, tableRequiresOrgFilter, } from './analyzers/orgFilterTiers.js';
|
|
29
30
|
// ── Rule ID helpers ──────────────────────────────────────────────────────────
|
|
30
31
|
function getRuleIdsFor(name) {
|
|
31
32
|
return Object.entries(RULE_REGISTRY)
|
|
@@ -120,14 +121,91 @@ export function createDataAccessVisitor() {
|
|
|
120
121
|
getRuleIds: () => getRuleIdsFor('data-access'),
|
|
121
122
|
async visit(ast, adapter, context, sourceCode) {
|
|
122
123
|
const a = await getAnalyzer();
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
// Spec 62 Amendment B — emit the extracted query facts (per-file) for the
|
|
125
|
+
// Stage-4 missing-org-filter reducer to join against the tenant tiers.
|
|
126
|
+
// The violations returned here exclude missing-org-filter (moved to the
|
|
127
|
+
// derived reducer); the calls carry resolved table + filter presence +
|
|
128
|
+
// DB provenance + the query-site location.
|
|
129
|
+
const { violations, calls } = await a.analyzeWithFacts(ast, adapter, context.config, sourceCode);
|
|
130
|
+
const facts = {};
|
|
131
|
+
if (calls.length > 0) {
|
|
132
|
+
facts[context.filePath] = { calls };
|
|
133
|
+
}
|
|
134
|
+
return { violations, facts };
|
|
125
135
|
},
|
|
126
136
|
defaultConfig: {},
|
|
127
137
|
description: 'Analyzes database access patterns and data layer interactions',
|
|
128
138
|
category: 'security',
|
|
129
139
|
};
|
|
130
140
|
}
|
|
141
|
+
// ── Data-Access org-filter reducer (Spec 62 Amendment B) ─────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* Stage-4 derived reducer for `missing-org-filter`. It joins the data-access
|
|
144
|
+
* visitor's per-query facts (resolved tables + org-filter presence + query-site
|
|
145
|
+
* location) against the tenant tiers resolved from config + the schema
|
|
146
|
+
* reducer's corpus-wide DDL column catalog. Moving the rule here (out of the
|
|
147
|
+
* Stage-2 analyzer) is the fix for the Amendment B defect: the firing predicate
|
|
148
|
+
* needed the DDL tier only Stage 3 produces, but the Stage-2 analyzer could only
|
|
149
|
+
* read config tiers — so a DDL-declared multi-tenant codebase was un-suppressed
|
|
150
|
+
* without ever firing, and reported `clean` on a real leak.
|
|
151
|
+
*
|
|
152
|
+
* Firing and applicability both derive from {@link buildOrgFilterTierSet}, so
|
|
153
|
+
* they read the identical tier set (Tier 1 config / Tier 2 schema / Tier 3 DDL).
|
|
154
|
+
*/
|
|
155
|
+
export function createOrgFilterReducer() {
|
|
156
|
+
return {
|
|
157
|
+
name: 'data-access-org-filter',
|
|
158
|
+
stage: 'derivedReducer',
|
|
159
|
+
// Hard dependency on the data-access visitor's per-query facts. The schema
|
|
160
|
+
// table catalog (`allFacts['schema'].tableColumns`) is read opportunistically
|
|
161
|
+
// so an MCP-default run (which omits `schema`) degrades to Tier 1–2
|
|
162
|
+
// (config-only) instead of throwing — see buildOrgFilterTierSet.
|
|
163
|
+
consumes: ['data-access'],
|
|
164
|
+
getRuleIds: () => getRuleIdsFor('data-access-org-filter'),
|
|
165
|
+
async reduce(allFacts, context) {
|
|
166
|
+
const violations = [];
|
|
167
|
+
const schemaFacts = allFacts['schema'];
|
|
168
|
+
const ddlTableColumns = schemaFacts?.tableColumns;
|
|
169
|
+
// One tier source of truth — the same function the applicability predicate
|
|
170
|
+
// reads, so firing and applicability cannot drift to different tier sets.
|
|
171
|
+
const tierSet = buildOrgFilterTierSet(context.config, ddlTableColumns);
|
|
172
|
+
const daFacts = (allFacts['data-access'] ?? {});
|
|
173
|
+
for (const fileFacts of Object.values(daFacts)) {
|
|
174
|
+
for (const call of fileFacts.calls ?? []) {
|
|
175
|
+
if (call.hasOrganizationFilter)
|
|
176
|
+
continue;
|
|
177
|
+
if (!call.tables || call.tables.length === 0)
|
|
178
|
+
continue;
|
|
179
|
+
if (!tableRequiresOrgFilter(call.tables, tierSet))
|
|
180
|
+
continue;
|
|
181
|
+
const symbol = `${call.enclosingFunction ?? 'top-level'}:${call.method}`;
|
|
182
|
+
violations.push({
|
|
183
|
+
file: call.file,
|
|
184
|
+
line: call.line,
|
|
185
|
+
column: call.column,
|
|
186
|
+
severity: 'critical',
|
|
187
|
+
message: `Query on ${call.tables.join(', ')} has no organization/tenant predicate`,
|
|
188
|
+
suggestion: 'Add the tenant column (organization_id / org_id) to the WHERE predicate so this query is scoped to the current organization, not just by primary key.',
|
|
189
|
+
// Structured next action (Spec 37 R1) — the rule is `resolvable`, so
|
|
190
|
+
// the gate requires a `resolution`, not just the `suggestion` string.
|
|
191
|
+
resolution: {
|
|
192
|
+
action: 'add-tenant-predicate',
|
|
193
|
+
summary: `Add the tenant column (organization_id / org_id) to the WHERE predicate on ${call.tables.join(', ')} so this query is scoped to the current organization, not just by primary key.`,
|
|
194
|
+
symbols: call.tables,
|
|
195
|
+
},
|
|
196
|
+
rule: 'missing-org-filter',
|
|
197
|
+
analyzer: 'data-access',
|
|
198
|
+
functionName: symbol,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { violations, facts: {} };
|
|
203
|
+
},
|
|
204
|
+
defaultConfig: {},
|
|
205
|
+
description: 'Detects queries on tenant-scoped tables that omit an organization/tenant predicate',
|
|
206
|
+
category: 'security',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
131
209
|
// ── Documentation visitor ────────────────────────────────────────────────────
|
|
132
210
|
export function createDocumentationVisitor() {
|
|
133
211
|
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDocumentationAnalyzer.js').then((m) => new m.UniversalDocumentationAnalyzer()));
|
|
@@ -162,6 +240,23 @@ export function createSecretsVisitor() {
|
|
|
162
240
|
category: 'security',
|
|
163
241
|
};
|
|
164
242
|
}
|
|
243
|
+
// ── Security visitor (Spec 61 R6) ────────────────────────────────────────────
|
|
244
|
+
export function createSecurityVisitor() {
|
|
245
|
+
const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecurityAnalyzer.js').then((m) => new m.UniversalSecurityAnalyzer()));
|
|
246
|
+
return {
|
|
247
|
+
name: 'security',
|
|
248
|
+
stage: 'visitor',
|
|
249
|
+
getRuleIds: () => getRuleIdsFor('security'),
|
|
250
|
+
async visit(ast, adapter, context, sourceCode) {
|
|
251
|
+
const a = await getAnalyzer();
|
|
252
|
+
const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
|
|
253
|
+
return { violations, facts: {} };
|
|
254
|
+
},
|
|
255
|
+
defaultConfig: {},
|
|
256
|
+
description: 'Detects command injection, dynamic require of project paths, and unescaped HTML interpolation',
|
|
257
|
+
category: 'security',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
165
260
|
// ── Function-Index visitor (infrastructure) ──────────────────────────────────
|
|
166
261
|
// Always-on Stage 2 visitor that populates the `functions` table with
|
|
167
262
|
// per-file definitions so the conventions and cross-domain reducers have data
|
|
@@ -609,6 +704,11 @@ export function createStylesCssVisitor() {
|
|
|
609
704
|
* styles and (b) skip re-writing files whose stored rows are already current.
|
|
610
705
|
*/
|
|
611
706
|
export function createStylesSourceVisitor() {
|
|
707
|
+
// Spec 61 R3.4 — a project tailwind config that exists but cannot be read
|
|
708
|
+
// statically is a `cannot-fire` coverage diagnostic. Emit it exactly once per
|
|
709
|
+
// visitor instance (the config is project-global, not per-file) so a config
|
|
710
|
+
// gap reads once, not once per source file.
|
|
711
|
+
let emittedTailwindDiagnostic = false;
|
|
612
712
|
return {
|
|
613
713
|
name: 'styles-source',
|
|
614
714
|
stage: 'visitor',
|
|
@@ -624,15 +724,22 @@ export function createStylesSourceVisitor() {
|
|
|
624
724
|
const filePath = context.filePath;
|
|
625
725
|
// Memoized per projectRoot (the style-index sync already called it this
|
|
626
726
|
// run), so this is a cache hit — never a re-walk of the project tree.
|
|
627
|
-
const
|
|
727
|
+
const tailwindConfig = loadTailwindConfig(context.projectRoot);
|
|
728
|
+
const tailwindTokens = tailwindConfig.tokens;
|
|
628
729
|
const declarations = extractDeclarations(filePath, tsAdapter, sourceCode, tsAst, tailwindTokens);
|
|
629
730
|
const classUsage = extractClassUsage(filePath, sourceCode);
|
|
630
731
|
const contentHash = createHash('sha256').update(sourceCode).digest('hex');
|
|
732
|
+
const diagnostic = tailwindConfig.diagnostic && !emittedTailwindDiagnostic
|
|
733
|
+
? tailwindConfig.diagnostic
|
|
734
|
+
: undefined;
|
|
735
|
+
if (diagnostic)
|
|
736
|
+
emittedTailwindDiagnostic = true;
|
|
631
737
|
return {
|
|
632
738
|
violations: [],
|
|
633
739
|
facts: {
|
|
634
740
|
[filePath]: { declarations, classUsage, contentHash },
|
|
635
741
|
},
|
|
742
|
+
...(diagnostic ? { diagnostics: [diagnostic] } : {}),
|
|
636
743
|
};
|
|
637
744
|
},
|
|
638
745
|
defaultConfig: {},
|
|
@@ -2124,13 +2231,14 @@ export function createSchemaSqlVisitor() {
|
|
|
2124
2231
|
getRuleIds: () => [],
|
|
2125
2232
|
async visit(_ast, _adapter, context, sourceCode) {
|
|
2126
2233
|
const { extractMigrationOpsFromFile } = await getMigrationExtractor();
|
|
2127
|
-
const { ops, columns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode);
|
|
2234
|
+
const { ops, columns, tableColumns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode);
|
|
2128
2235
|
return {
|
|
2129
2236
|
violations: [],
|
|
2130
2237
|
facts: {
|
|
2131
2238
|
[context.filePath]: {
|
|
2132
2239
|
ddlOps: ops,
|
|
2133
2240
|
...(columns.length > 0 && { ddlColumns: columns }),
|
|
2241
|
+
...(Object.keys(tableColumns).length > 0 && { ddlTableColumns: tableColumns }),
|
|
2134
2242
|
...(skipped && { skipped: true, bytes }),
|
|
2135
2243
|
},
|
|
2136
2244
|
},
|
|
@@ -2151,6 +2259,7 @@ export function createSchemaCodeVisitor() {
|
|
|
2151
2259
|
defaults: m.DEFAULT_SCHEMA_CONFIG,
|
|
2152
2260
|
parseMigrationOps: m.parseMigrationOps,
|
|
2153
2261
|
extractDdlColumnNames: m.extractDdlColumnNames,
|
|
2262
|
+
extractDdlTableColumns: m.extractDdlTableColumns,
|
|
2154
2263
|
})));
|
|
2155
2264
|
return {
|
|
2156
2265
|
name: 'schema-code',
|
|
@@ -2158,7 +2267,7 @@ export function createSchemaCodeVisitor() {
|
|
|
2158
2267
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],
|
|
2159
2268
|
getRuleIds: () => getRuleIdsFor('schema'),
|
|
2160
2269
|
async visit(ast, adapter, context, sourceCode) {
|
|
2161
|
-
const { analyzer: a, defaults, parseMigrationOps, extractDdlColumnNames } = await getAnalyzer();
|
|
2270
|
+
const { analyzer: a, defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getAnalyzer();
|
|
2162
2271
|
const pm = await _getProvenanceModule();
|
|
2163
2272
|
const violations = [];
|
|
2164
2273
|
const diagnostics = [];
|
|
@@ -2208,6 +2317,7 @@ export function createSchemaCodeVisitor() {
|
|
|
2208
2317
|
}
|
|
2209
2318
|
const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
|
|
2210
2319
|
const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql) : [];
|
|
2320
|
+
const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql) : {};
|
|
2211
2321
|
// Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
|
|
2212
2322
|
const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
|
|
2213
2323
|
const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
|
|
@@ -2224,6 +2334,8 @@ export function createSchemaCodeVisitor() {
|
|
|
2224
2334
|
facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql);
|
|
2225
2335
|
if (doDDLColumns.length > 0)
|
|
2226
2336
|
facts[context.filePath].ddlColumns = doDDLColumns;
|
|
2337
|
+
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2338
|
+
facts[context.filePath].ddlTableColumns = doDDLTableColumns;
|
|
2227
2339
|
}
|
|
2228
2340
|
return { violations: [], facts };
|
|
2229
2341
|
}
|
|
@@ -2302,6 +2414,8 @@ export function createSchemaCodeVisitor() {
|
|
|
2302
2414
|
fileFacts.ddlOps = parseMigrationOps(doDDLSql);
|
|
2303
2415
|
if (doDDLColumns.length > 0)
|
|
2304
2416
|
fileFacts.ddlColumns = doDDLColumns;
|
|
2417
|
+
if (Object.keys(doDDLTableColumns).length > 0)
|
|
2418
|
+
fileFacts.ddlTableColumns = doDDLTableColumns;
|
|
2305
2419
|
}
|
|
2306
2420
|
return {
|
|
2307
2421
|
violations,
|
|
@@ -2523,12 +2637,32 @@ export function createSchemaReducer() {
|
|
|
2523
2637
|
// the reducer folds them into a single case-insensitive, deduplicated
|
|
2524
2638
|
// set so downstream applicability predicates can ask "does ANY table
|
|
2525
2639
|
// carry a tenant-scoping column?" without per-table column lists.
|
|
2640
|
+
//
|
|
2641
|
+
// Spec 62 Amendment B — additionally fold the per-table
|
|
2642
|
+
// `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
|
|
2643
|
+
// string[]>) so the Stage-4 missing-org-filter reducer can answer the
|
|
2644
|
+
// per-query question "does THIS table carry a tenant column?" that the
|
|
2645
|
+
// flat set cannot.
|
|
2526
2646
|
const ddlColumns = new Set();
|
|
2647
|
+
const tableColumnsMap = new Map();
|
|
2527
2648
|
for (const [filePath, fact] of perFile()) {
|
|
2528
2649
|
const cols = fact.ddlColumns ?? [];
|
|
2529
2650
|
for (const c of cols)
|
|
2530
2651
|
ddlColumns.add(String(c).toLowerCase());
|
|
2652
|
+
const perTable = fact.ddlTableColumns ?? {};
|
|
2653
|
+
for (const [table, tableCols] of Object.entries(perTable)) {
|
|
2654
|
+
let set = tableColumnsMap.get(table);
|
|
2655
|
+
if (!set) {
|
|
2656
|
+
set = new Set();
|
|
2657
|
+
tableColumnsMap.set(table, set);
|
|
2658
|
+
}
|
|
2659
|
+
for (const c of tableCols)
|
|
2660
|
+
set.add(String(c).toLowerCase());
|
|
2661
|
+
}
|
|
2531
2662
|
}
|
|
2663
|
+
const tableColumns = {};
|
|
2664
|
+
for (const [table, set] of tableColumnsMap)
|
|
2665
|
+
tableColumns[table] = [...set];
|
|
2532
2666
|
// ── 2. Unknown-table detection ────────────────────────────────────────
|
|
2533
2667
|
//
|
|
2534
2668
|
// Fail-open guardrail: we can only accuse when the table catalog is
|
|
@@ -2678,7 +2812,7 @@ export function createSchemaReducer() {
|
|
|
2678
2812
|
}
|
|
2679
2813
|
return {
|
|
2680
2814
|
violations,
|
|
2681
|
-
facts: { tableCatalog: catalogEntries, ddlColumns: [...ddlColumns].sort() },
|
|
2815
|
+
facts: { tableCatalog: catalogEntries, ddlColumns: [...ddlColumns].sort(), tableColumns },
|
|
2682
2816
|
factsConsumed: [...perFile()].length,
|
|
2683
2817
|
};
|
|
2684
2818
|
},
|