@@ -8,28 +8,39 @@ import path from "path"
88import {
99 type Finding ,
1010 type CheckCategoryResult ,
11- type CheckOutput ,
1211 type Severity ,
13- SEVERITY_RANK ,
1412 VALID_CHECKS ,
1513 normalizeSeverity ,
1614 filterBySeverity ,
1715 toCategoryResult ,
1816 formatText ,
17+ buildCheckOutput ,
1918} from "./check-helpers"
2019
2120// ---------------------------------------------------------------------------
2221// Check runners — each calls Dispatcher.call() and normalizes to Finding[]
22+ // On Dispatcher failure, emit an error-severity finding so CI doesn't false-pass.
2323// ---------------------------------------------------------------------------
2424
25+ function dispatcherErrorFinding ( check : string , file : string , e : unknown ) : Finding {
26+ return {
27+ file,
28+ rule : `${ check } -error` ,
29+ severity : "error" ,
30+ message : `[${ check } ] check failed: ${ e instanceof Error ? e . message : String ( e ) } ` ,
31+ }
32+ }
33+
2534async function runLint ( sql : string , file : string , schemaPath ?: string ) : Promise < Finding [ ] > {
2635 try {
2736 const result = await Dispatcher . call ( "altimate_core.lint" , {
2837 sql,
2938 schema_path : schemaPath ?? "" ,
3039 schema_context : undefined as any ,
3140 } )
32- if ( ! result . success ) return [ ]
41+ if ( ! result . success ) {
42+ return [ dispatcherErrorFinding ( "lint" , file , result . error ?? "altimate_core.lint failed" ) ]
43+ }
3344 const violations = ( result . data . violations ?? result . data . findings ?? [ ] ) as Array < Record < string , unknown > >
3445 return violations . map ( ( f ) => ( {
3546 file,
@@ -43,7 +54,7 @@ async function runLint(sql: string, file: string, schemaPath?: string): Promise<
4354 } ) )
4455 } catch ( e ) {
4556 console . error ( `[lint] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
46- return [ ]
57+ return [ dispatcherErrorFinding ( "lint" , file , e ) ]
4758 }
4859}
4960
@@ -63,7 +74,7 @@ async function runValidate(sql: string, file: string, schemaPath?: string): Prom
6374 column : f . column as number | undefined ,
6475 code : f . code as string | undefined ,
6576 rule : "validate" ,
66- severity : normalizeSeverity ( f . severity as string ) || ( "error" as const ) ,
77+ severity : normalizeSeverity ( f . severity as string ) ,
6778 message : ( f . message ?? f . description ?? "" ) as string ,
6879 suggestion : f . suggestion as string | undefined ,
6980 } ) )
@@ -80,7 +91,7 @@ async function runValidate(sql: string, file: string, schemaPath?: string): Prom
8091 ]
8192 } catch ( e ) {
8293 console . error ( `[validate] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
83- return [ ]
94+ return [ dispatcherErrorFinding ( "validate" , file , e ) ]
8495 }
8596}
8697
@@ -96,7 +107,7 @@ async function runSafety(sql: string, file: string): Promise<Finding[]> {
96107 column : f . column as number | undefined ,
97108 code : f . code as string | undefined ,
98109 rule : ( f . rule ?? f . category ?? "safety" ) as string ,
99- severity : normalizeSeverity ( f . severity as string ) || ( "warning" as const ) ,
110+ severity : normalizeSeverity ( f . severity as string ) ,
100111 message : ( f . message ?? f . description ?? "" ) as string ,
101112 suggestion : f . suggestion as string | undefined ,
102113 } ) )
@@ -114,7 +125,7 @@ async function runSafety(sql: string, file: string): Promise<Finding[]> {
114125 return [ ]
115126 } catch ( e ) {
116127 console . error ( `[safety] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
117- return [ ]
128+ return [ dispatcherErrorFinding ( "safety" , file , e ) ]
118129 }
119130}
120131
@@ -135,7 +146,7 @@ async function runPolicy(sql: string, file: string, policyJson: string, schemaPa
135146 column : f . column as number | undefined ,
136147 code : f . code as string | undefined ,
137148 rule : ( f . rule ?? f . policy ?? "policy" ) as string ,
138- severity : normalizeSeverity ( f . severity as string ) || ( "error" as const ) ,
149+ severity : normalizeSeverity ( f . severity as string ) ,
139150 message : ( f . message ?? f . description ?? "" ) as string ,
140151 suggestion : f . suggestion as string | undefined ,
141152 } ) )
@@ -153,7 +164,7 @@ async function runPolicy(sql: string, file: string, policyJson: string, schemaPa
153164 return [ ]
154165 } catch ( e ) {
155166 console . error ( `[policy] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
156- return [ ]
167+ return [ dispatcherErrorFinding ( "policy" , file , e ) ]
157168 }
158169}
159170
@@ -164,6 +175,9 @@ async function runPii(sql: string, file: string, schemaPath?: string): Promise<F
164175 schema_path : schemaPath ?? "" ,
165176 schema_context : undefined as any ,
166177 } )
178+ if ( ! result . success ) {
179+ return [ dispatcherErrorFinding ( "pii" , file , result . error ?? "altimate_core.query_pii failed" ) ]
180+ }
167181 const piiFindings = ( result . data . pii_columns ?? result . data . findings ?? [ ] ) as Array < Record < string , unknown > >
168182 return piiFindings . map ( ( f ) => ( {
169183 file,
@@ -177,7 +191,7 @@ async function runPii(sql: string, file: string, schemaPath?: string): Promise<F
177191 } ) )
178192 } catch ( e ) {
179193 console . error ( `[pii] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
180- return [ ]
194+ return [ dispatcherErrorFinding ( "pii" , file , e ) ]
181195 }
182196}
183197
@@ -197,7 +211,7 @@ async function runSemantic(sql: string, file: string, schemaPath?: string): Prom
197211 column : f . column as number | undefined ,
198212 code : f . code as string | undefined ,
199213 rule : ( f . rule ?? "semantic" ) as string ,
200- severity : normalizeSeverity ( f . severity as string ) || ( "warning" as const ) ,
214+ severity : normalizeSeverity ( f . severity as string ) ,
201215 message : ( f . message ?? f . description ?? "" ) as string ,
202216 suggestion : f . suggestion as string | undefined ,
203217 } ) )
@@ -215,7 +229,7 @@ async function runSemantic(sql: string, file: string, schemaPath?: string): Prom
215229 return [ ]
216230 } catch ( e ) {
217231 console . error ( `[semantic] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
218- return [ ]
232+ return [ dispatcherErrorFinding ( "semantic" , file , e ) ]
219233 }
220234}
221235
@@ -235,13 +249,13 @@ async function runGrade(sql: string, file: string, schemaPath?: string): Promise
235249 column : f . column as number | undefined ,
236250 code : f . code as string | undefined ,
237251 rule : ( f . rule ?? f . category ?? "grade" ) as string ,
238- severity : normalizeSeverity ( f . severity as string ) || ( "info" as const ) ,
252+ severity : normalizeSeverity ( f . severity as string ) ,
239253 message : ( f . message ?? f . description ?? "" ) as string ,
240254 suggestion : f . suggestion as string | undefined ,
241255 } ) )
242256 } catch ( e ) {
243257 console . error ( `[grade] error processing ${ file } : ${ e instanceof Error ? e . message : String ( e ) } ` )
244- return [ ]
258+ return [ dispatcherErrorFinding ( "grade" , file , e ) ]
245259 }
246260}
247261
@@ -273,10 +287,6 @@ export const CheckCommand = cmd({
273287 describe : "path to policy JSON file for policy checks" ,
274288 type : "string" ,
275289 } )
276- . option ( "dialect" , {
277- describe : "SQL dialect (snowflake, bigquery, postgres, etc.)" ,
278- type : "string" ,
279- } )
280290 . option ( "severity" , {
281291 describe : "minimum severity level to report" ,
282292 choices : [ "info" , "warning" , "error" ] as const ,
@@ -286,14 +296,6 @@ export const CheckCommand = cmd({
286296 describe : "exit 1 if findings at this level or above are found" ,
287297 choices : [ "none" , "warning" , "error" ] as const ,
288298 default : "none" as const ,
289- } )
290- . option ( "dbt-project" , {
291- describe : "path to dbt project directory" ,
292- type : "string" ,
293- } )
294- . option ( "manifest" , {
295- describe : "path to dbt manifest.json" ,
296- type : "string" ,
297299 } ) ,
298300
299301 handler : async ( args : {
@@ -302,12 +304,9 @@ export const CheckCommand = cmd({
302304 checks ?: string
303305 schema ?: string
304306 policy ?: string
305- dialect ?: string
306307 severity ?: "info" | "warning" | "error"
307308 "fail-on" ?: "none" | "warning" | "error"
308309 failOn ?: "none" | "warning" | "error"
309- "dbt-project" ?: string
310- manifest ?: string
311310 } ) => {
312311 const startTime = Date . now ( )
313312
@@ -436,40 +435,35 @@ export const CheckCommand = cmd({
436435 await Promise . all ( batchPromises )
437436 }
438437
439- // 6. Filter by severity
438+ // 6. Compute pass/fail on UNFILTERED findings (before severity filtering)
439+ // This ensures --severity only controls output display, not exit code logic.
440+ const failOn = args [ "fail-on" ] ?? args . failOn ?? "none"
441+ const allUnfiltered = Object . values ( allResults ) . flat ( )
442+ const unfilteredErrors = allUnfiltered . filter ( ( f ) => f . severity === "error" ) . length
443+ const unfilteredWarnings = allUnfiltered . filter ( ( f ) => f . severity === "warning" ) . length
444+ let pass = true
445+ if ( failOn === "error" && unfilteredErrors > 0 ) pass = false
446+ if ( failOn === "warning" && ( unfilteredErrors > 0 || unfilteredWarnings > 0 ) ) pass = false
447+
448+ // 7. Filter by severity for display
440449 const minSeverity = args . severity as Severity
441450 const results : Record < string , CheckCategoryResult > = { }
442451 for ( const [ check , findings ] of Object . entries ( allResults ) ) {
443452 results [ check ] = toCategoryResult ( filterBySeverity ( findings , minSeverity ) )
444453 }
445454
446- // 7. Build output
447- const allFindings = Object . values ( results ) . flatMap ( ( r ) => r . findings )
448- const errors = allFindings . filter ( ( f ) => f . severity === "error" ) . length
449- const warnings = allFindings . filter ( ( f ) => f . severity === "warning" ) . length
450- const info = allFindings . filter ( ( f ) => f . severity === "info" ) . length
451-
452- const failOn = args [ "fail-on" ] ?? args . failOn ?? "none"
453- let pass = true
454- if ( failOn === "error" && errors > 0 ) pass = false
455- if ( failOn === "warning" && ( errors > 0 || warnings > 0 ) ) pass = false
456-
457- const output : CheckOutput = {
458- version : 1 ,
459- files_checked : files . length ,
460- checks_run : checks ,
461- schema_resolved : schemaPath !== undefined ,
455+ // 8. Build output using the helper
456+ const output = buildCheckOutput ( {
457+ filesChecked : files . length ,
458+ checksRun : checks ,
459+ schemaResolved : schemaPath !== undefined ,
462460 results,
463- summary : {
464- total_findings : allFindings . length ,
465- errors,
466- warnings,
467- info,
468- pass,
469- } ,
470- }
461+ failOn : "none" , // pass is already computed above from unfiltered findings
462+ } )
463+ // Override pass with our pre-computed value from unfiltered findings
464+ output . summary . pass = pass
471465
472- // 8 . Output
466+ // 9 . Output
473467 const duration = Date . now ( ) - startTime
474468 if ( args . format === "json" ) {
475469 process . stdout . write ( JSON . stringify ( output , null , 2 ) + "\n" )
@@ -478,7 +472,7 @@ export const CheckCommand = cmd({
478472 }
479473 console . error ( `Completed in ${ duration } ms` )
480474
481- // 9 . Exit code
475+ // 10 . Exit code
482476 if ( ! pass ) {
483477 process . exit ( 1 )
484478 }
0 commit comments