@@ -400,9 +400,25 @@ pub fn translate_all(
400400 // Skip weak points suppressed by panic-attack's context-aware FP engine.
401401 // Suppressed = the logic engine found a defensive pattern (e.g. mutex guard,
402402 // RAII, schema validation) that makes this finding likely a false positive.
403+ //
404+ // v2.5.5+ classifications (test_context field):
405+ // * `"test_only"` / `"doc"` → typically also `suppressed: true`
406+ // (set by `apply_v255_context_suppression` in panic-attack
407+ // after the kanren rule pass). Defensive: even if a future
408+ // scanner version preserves `suppressed: false` while
409+ // classifying as TestOnly, drop the finding here so test code
410+ // never reaches the fleet.
411+ // * `"production"` → forwarded normally.
412+ // * `None` → predates v2.5.5; treat as production.
403413 if wp. suppressed {
404414 return None ;
405415 }
416+ if matches ! (
417+ wp. test_context. as_deref( ) ,
418+ Some ( "test_only" ) | Some ( "doc" )
419+ ) {
420+ return None ;
421+ }
406422
407423 // Apply severity filter
408424 let severity_value = match wp. severity . to_lowercase ( ) . as_str ( ) {
@@ -498,6 +514,7 @@ mod tests {
498514 description : "AWS_SECRET_KEY found in source" . to_string ( ) ,
499515 recommended_attack : vec ! [ ] ,
500516 suppressed : false ,
517+ test_context : None ,
501518 } ;
502519 let config = PanicbotConfig :: default ( ) ;
503520 let finding = translate_weak_point ( & wp, & config) ;
@@ -522,6 +539,7 @@ mod tests {
522539 description : "Something new" . to_string ( ) ,
523540 recommended_attack : vec ! [ ] ,
524541 suppressed : false ,
542+ test_context : None ,
525543 } ;
526544 let config = PanicbotConfig :: default ( ) ;
527545 let finding = translate_weak_point ( & wp, & config) ;
@@ -541,6 +559,7 @@ mod tests {
541559 description : "unsafe block" . to_string ( ) ,
542560 recommended_attack : vec ! [ ] ,
543561 suppressed : false ,
562+ test_context : None ,
544563 } ;
545564 let config = PanicbotConfig {
546565 confidence_overrides : vec ! [ crate :: config:: ConfidenceOverride {
@@ -563,6 +582,7 @@ mod tests {
563582 description: "low severity" . to_string( ) ,
564583 recommended_attack: vec![ ] ,
565584 suppressed: false ,
585+ test_context: None ,
566586 } ,
567587 WeakPoint {
568588 category: "CommandInjection" . to_string( ) ,
@@ -571,6 +591,7 @@ mod tests {
571591 description: "critical severity" . to_string( ) ,
572592 recommended_attack: vec![ ] ,
573593 suppressed: false ,
594+ test_context: None ,
574595 } ,
575596 ] ;
576597 let config = PanicbotConfig {
@@ -596,6 +617,7 @@ mod tests {
596617 description: "fixable" . to_string( ) ,
597618 recommended_attack: vec![ ] ,
598619 suppressed: false ,
620+ test_context: None ,
599621 } ,
600622 & config,
601623 ) ,
@@ -607,6 +629,7 @@ mod tests {
607629 description: "not fixable" . to_string( ) ,
608630 recommended_attack: vec![ ] ,
609631 suppressed: false ,
632+ test_context: None ,
610633 } ,
611634 & config,
612635 ) ,
@@ -643,4 +666,75 @@ mod tests {
643666 }
644667 assert_eq ! ( rule_ids. len( ) , 25 ) ;
645668 }
669+
670+ // ─────────────────────────────────────────────────────────────────────
671+ // v2.5.5 test_context tests
672+ // ─────────────────────────────────────────────────────────────────────
673+
674+ fn make_wp ( category : & str , test_context : Option < & str > , suppressed : bool ) -> WeakPoint {
675+ WeakPoint {
676+ category : category. to_string ( ) ,
677+ location : Some ( "src/foo.rs:1" . to_string ( ) ) ,
678+ severity : "Critical" . to_string ( ) ,
679+ description : "test" . to_string ( ) ,
680+ recommended_attack : vec ! [ ] ,
681+ suppressed,
682+ test_context : test_context. map ( String :: from) ,
683+ }
684+ }
685+
686+ #[ test]
687+ fn translate_all_drops_test_only_findings_even_if_not_suppressed ( ) {
688+ // Defensive: even when `suppressed: false`, a `test_context: test_only`
689+ // finding should not reach the fleet.
690+ let wps = vec ! [
691+ make_wp( "UnsafeCode" , Some ( "test_only" ) , false ) ,
692+ make_wp( "UnsafeCode" , Some ( "production" ) , false ) ,
693+ ] ;
694+ let config = PanicbotConfig :: default ( ) ;
695+ let findings = translate_all ( & wps, & config) ;
696+ assert_eq ! (
697+ findings. len( ) ,
698+ 1 ,
699+ "test_only finding must be dropped regardless of suppressed flag"
700+ ) ;
701+ }
702+
703+ #[ test]
704+ fn translate_all_drops_doc_findings_even_if_not_suppressed ( ) {
705+ let wps = vec ! [
706+ make_wp( "PanicPath" , Some ( "doc" ) , false ) ,
707+ make_wp( "PanicPath" , Some ( "production" ) , false ) ,
708+ ] ;
709+ let config = PanicbotConfig :: default ( ) ;
710+ let findings = translate_all ( & wps, & config) ;
711+ assert_eq ! ( findings. len( ) , 1 , "doc finding must be dropped" ) ;
712+ }
713+
714+ #[ test]
715+ fn translate_all_keeps_production_test_context ( ) {
716+ let wps = vec ! [ make_wp( "UnsafeCode" , Some ( "production" ) , false ) ] ;
717+ let config = PanicbotConfig :: default ( ) ;
718+ let findings = translate_all ( & wps, & config) ;
719+ assert_eq ! ( findings. len( ) , 1 ) ;
720+ }
721+
722+ #[ test]
723+ fn translate_all_treats_none_test_context_as_production ( ) {
724+ // Pre-v2.5.5 panic-attack reports don't have the field; they should
725+ // still be routed.
726+ let wps = vec ! [ make_wp( "UnsafeCode" , None , false ) ] ;
727+ let config = PanicbotConfig :: default ( ) ;
728+ let findings = translate_all ( & wps, & config) ;
729+ assert_eq ! ( findings. len( ) , 1 ) ;
730+ }
731+
732+ #[ test]
733+ fn translate_all_respects_suppressed_independently ( ) {
734+ // `suppressed: true` should drop regardless of test_context.
735+ let wps = vec ! [ make_wp( "UnsafeCode" , Some ( "production" ) , true ) ] ;
736+ let config = PanicbotConfig :: default ( ) ;
737+ let findings = translate_all ( & wps, & config) ;
738+ assert_eq ! ( findings. len( ) , 0 ) ;
739+ }
646740}
0 commit comments