@@ -17,6 +17,7 @@ import {
1717 requiredScreenshotMatrixPairs ,
1818 type ScreenshotMatrixPair ,
1919} from "../../packages/loopover-engine/src/review/screenshot-table-gate" ;
20+ import { matchesAnyWithExclusions } from "../../packages/loopover-engine/src/signals/change-guardrail" ;
2021import type { ScreenshotTableGateConfig } from "../../packages/loopover-engine/src/types/manifest-deps-types" ;
2122
2223function config ( overrides : Partial < ScreenshotTableGateConfig > = { } ) : ScreenshotTableGateConfig {
@@ -795,3 +796,27 @@ describe("extractTableRowImageUrls (#4366)", () => {
795796 ] ) ;
796797 } ) ;
797798} ) ;
799+
800+ // #9434: the exclusion matcher exercised through the ENGINE source path (change-guardrail.test.ts reaches the
801+ // same function through the `src/signals/change-guardrail` re-export shim, a distinct coverage identity).
802+ // Both paths are asserted so the lines stay attributed under either identity regardless of how CI shards the
803+ // suites — the misattribution class vitest.config.ts's coverage-include comment documents.
804+ describe ( "matchesAnyWithExclusions via the engine path (#9434)" , ( ) => {
805+ it ( "excludes a generated file from a directory glob, and leaves the rest matching" , ( ) => {
806+ const globs = [ "apps/ui/public/**" , "!apps/ui/public/openapi.json" ] ;
807+ expect ( matchesAnyWithExclusions ( "apps/ui/public/openapi.json" , globs ) ) . toBe ( false ) ;
808+ expect ( matchesAnyWithExclusions ( "apps/ui/public/hero.png" , globs ) ) . toBe ( true ) ;
809+ } ) ;
810+
811+ it ( "requires an include: an all-exclude list never matches, and a non-matching path stays false" , ( ) => {
812+ expect ( matchesAnyWithExclusions ( "apps/ui/public/x.png" , [ "!apps/ui/public/**" ] ) ) . toBe ( false ) ;
813+ expect ( matchesAnyWithExclusions ( "docs/readme.md" , [ "apps/ui/**" , "!apps/ui/public/**" ] ) ) . toBe ( false ) ;
814+ } ) ;
815+
816+ it ( "SECURITY: an over-complex exclude glob excludes NOTHING — it can only widen gate scope, never shrink it" , ( ) => {
817+ // Opposite fail direction from an include: an unsafe exclude resolving to 'matches everything' would
818+ // silently shrink a safety gate's coverage.
819+ const result = matchesAnyWithExclusions ( "apps/ui/public/openapi.json" , [ "apps/ui/public/**" , "!apps/*-*-*-x.json" ] ) ;
820+ expect ( result ) . toBe ( true ) ;
821+ } ) ;
822+ } ) ;
0 commit comments