@@ -35,7 +35,8 @@ def load_samples(path: str | Path) -> list[dict[str, Any]]:
3535 sample_path = Path (path )
3636 loaded = yaml .safe_load (sample_path .read_text (encoding = "utf-8" ))
3737 if not isinstance (loaded , list ):
38- raise ValueError ("samples YAML must contain a list of sample mappings." )
38+ raise ValueError (
39+ "samples YAML must contain a list of sample mappings." )
3940
4041 samples : list [dict [str , Any ]] = []
4142 seen_ids : set [str ] = set ()
@@ -44,23 +45,32 @@ def load_samples(path: str | Path) -> list[dict[str, Any]]:
4445 raise ValueError (f"sample at index { index } must be a mapping." )
4546 sample_id = _string_field (item , "id" )
4647 if not sample_id :
47- raise ValueError (f"sample at index { index } must have a non-empty id." )
48+ raise ValueError (
49+ f"sample at index { index } must have a non-empty id." )
4850 if sample_id in seen_ids :
4951 raise ValueError (f"duplicate sample id: { sample_id } " )
5052 seen_ids .add (sample_id )
5153
5254 if not _string_field (item , "description" ):
53- raise ValueError (f"sample { sample_id } must have a non-empty description." )
55+ raise ValueError (
56+ f"sample { sample_id } must have a non-empty description." )
5457 if not _string_field (item , "language" ):
55- raise ValueError (f"sample { sample_id } must have a non-empty language." )
56- if not (_string_field (item , "content" ) or _string_field (item , "command" )):
57- raise ValueError (f"sample { sample_id } must define content or command." )
58+ raise ValueError (
59+ f"sample { sample_id } must have a non-empty language." )
60+ if not (_string_field (item , "content" )
61+ or _string_field (item , "command" )):
62+ raise ValueError (
63+ f"sample { sample_id } must define content or command." )
5864 if not _string_field (item , "expected_decision" ):
59- raise ValueError (f"sample { sample_id } must define expected_decision." )
65+ raise ValueError (
66+ f"sample { sample_id } must define expected_decision." )
6067
6168 expected_rules = item .get ("expected_rules" , [])
62- if not isinstance (expected_rules , list ) or not all (isinstance (rule , str ) for rule in expected_rules ):
63- raise ValueError (f"sample { sample_id } expected_rules must be a list of strings." )
69+ if not isinstance (expected_rules , list ) or not all (
70+ isinstance (rule , str ) for rule in expected_rules ):
71+ raise ValueError (
72+ f"sample { sample_id } expected_rules must be a list of strings."
73+ )
6474
6575 samples .append (dict (item ))
6676 return samples
@@ -77,11 +87,11 @@ def load_policy(path: str | Path | None) -> SafetyPolicy:
7787
7888
7989def scan_samples (
80- samples : list [dict [str , Any ]],
81- policy : SafetyPolicy ,
82- * ,
83- generated_at : str = FIXTURE_GENERATED_AT ,
84- stable_elapsed_ms : float | None = None ,
90+ samples : list [dict [str , Any ]],
91+ policy : SafetyPolicy ,
92+ * ,
93+ generated_at : str = FIXTURE_GENERATED_AT ,
94+ stable_elapsed_ms : float | None = None ,
8595) -> tuple [dict [str , Any ], list [SafetyAuditEvent ], list [str ]]:
8696 """Scan sample mappings and return aggregate report, audit events, and mismatches."""
8797
@@ -93,10 +103,14 @@ def scan_samples(
93103 for sample in samples :
94104 report = scanner .scan (build_target_from_sample (sample ))
95105 if stable_elapsed_ms is not None :
96- report = report .model_copy (update = {"elapsed_ms" : stable_elapsed_ms })
106+ report = report .model_copy (
107+ update = {"elapsed_ms" : stable_elapsed_ms })
97108 result = build_sample_result (sample , report )
98109 results .append (result )
99- audit_events .append (build_audit_event (result ["sample_id" ], report , generated_at = generated_at ))
110+ audit_events .append (
111+ build_audit_event (result ["sample_id" ],
112+ report ,
113+ generated_at = generated_at ))
100114 if not result ["match" ]:
101115 mismatches .append (_mismatch_message (result ))
102116
@@ -108,11 +122,11 @@ def scan_samples(
108122
109123
110124def scan_file (
111- path : str | Path ,
112- policy : SafetyPolicy ,
113- * ,
114- language : str = "unknown" ,
115- generated_at : str = FIXTURE_GENERATED_AT ,
125+ path : str | Path ,
126+ policy : SafetyPolicy ,
127+ * ,
128+ language : str = "unknown" ,
129+ generated_at : str = FIXTURE_GENERATED_AT ,
116130) -> tuple [dict [str , Any ], list [SafetyAuditEvent ]]:
117131 """Scan one local file and return an aggregate report with one result."""
118132
@@ -122,7 +136,10 @@ def scan_file(
122136 content = content ,
123137 language = parse_language (language ),
124138 tool_name = TOOL_NAME ,
125- tool_metadata = {"source" : "file" , "file_name" : file_path .name },
139+ tool_metadata = {
140+ "source" : "file" ,
141+ "file_name" : file_path .name
142+ },
126143 )
127144 report = SafetyScanner (policy ).scan (target )
128145 sample = {
@@ -132,7 +149,9 @@ def scan_file(
132149 "expected_rules" : [finding .rule_id for finding in report .findings ],
133150 }
134151 result = build_sample_result (sample , report )
135- event = build_audit_event (result ["sample_id" ], report , generated_at = generated_at )
152+ event = build_audit_event (result ["sample_id" ],
153+ report ,
154+ generated_at = generated_at )
136155 aggregate = build_aggregate_report (
137156 policy_name = policy .name ,
138157 results = [result ],
@@ -147,14 +166,18 @@ def build_target_from_sample(sample: dict[str, Any]) -> ScanTarget:
147166 env_keys = sample .get ("env_keys" , [])
148167 if env_keys is None :
149168 env_keys = []
150- if not isinstance (env_keys , list ) or not all (isinstance (key , str ) for key in env_keys ):
151- raise ValueError (f"sample { sample .get ('id' , '<unknown>' )} env_keys must be a list of strings." )
169+ if not isinstance (env_keys , list ) or not all (
170+ isinstance (key , str ) for key in env_keys ):
171+ raise ValueError (
172+ f"sample { sample .get ('id' , '<unknown>' )} env_keys must be a list of strings."
173+ )
152174
153175 return ScanTarget (
154176 content = _string_field (sample , "content" ),
155177 command = _string_field (sample , "command" ),
156178 language = parse_language (_string_field (sample , "language" )),
157- env = {key : "" for key in env_keys },
179+ env = {key : ""
180+ for key in env_keys },
158181 tool_name = TOOL_NAME ,
159182 tool_metadata = {"sample_id" : _string_field (sample , "id" )},
160183 )
@@ -173,41 +196,53 @@ def parse_language(value: str) -> ScriptLanguage:
173196 return ScriptLanguage .UNKNOWN
174197
175198
176- def build_sample_result (sample : dict [str , Any ], report : SafetyReport ) -> dict [str , Any ]:
199+ def build_sample_result (sample : dict [str , Any ],
200+ report : SafetyReport ) -> dict [str , Any ]:
177201 """Build the report entry for one sample without including full source text."""
178202
179203 expected_decision = _string_field (sample , "expected_decision" )
180204 expected_rules = list (sample .get ("expected_rules" ) or [])
181205 actual_rules = [finding .rule_id for finding in report .findings ]
182206 return {
183- "sample_id" : _string_field (sample , "id" ),
184- "description" : _string_field (sample , "description" ),
185- "expected_decision" : expected_decision ,
186- "expected_rules" : expected_rules ,
187- "match" : expected_decision == report .decision .value and _is_subset (expected_rules , actual_rules ),
188- "report" : report .model_dump (mode = "json" ),
207+ "sample_id" :
208+ _string_field (sample , "id" ),
209+ "description" :
210+ _string_field (sample , "description" ),
211+ "expected_decision" :
212+ expected_decision ,
213+ "expected_rules" :
214+ expected_rules ,
215+ "match" :
216+ expected_decision == report .decision .value
217+ and _is_subset (expected_rules , actual_rules ),
218+ "report" :
219+ report .model_dump (mode = "json" ),
189220 }
190221
191222
192223def build_aggregate_report (
193- * ,
194- policy_name : str ,
195- results : list [dict [str , Any ]],
196- generated_at : str = FIXTURE_GENERATED_AT ,
224+ * ,
225+ policy_name : str ,
226+ results : list [dict [str , Any ]],
227+ generated_at : str = FIXTURE_GENERATED_AT ,
197228) -> dict [str , Any ]:
198229 """Build the stable public JSON report shape."""
199230
200- decisions = Counter (str (result ["report" ]["decision" ]) for result in results )
231+ decisions = Counter (
232+ str (result ["report" ]["decision" ]) for result in results )
201233 return {
202234 "policy_name" : policy_name ,
203235 "generated_at" : generated_at ,
204236 "sample_count" : len (results ),
205- "decision_summary" : {decision : decisions .get (decision , 0 ) for decision in DECISION_VALUES },
237+ "decision_summary" :
238+ {decision : decisions .get (decision , 0 )
239+ for decision in DECISION_VALUES },
206240 "results" : results ,
207241 }
208242
209243
210- def build_audit_event (sample_id : str , report : SafetyReport , * , generated_at : str ) -> SafetyAuditEvent :
244+ def build_audit_event (sample_id : str , report : SafetyReport , * ,
245+ generated_at : str ) -> SafetyAuditEvent :
211246 """Build a stable audit event for sample/file scans."""
212247
213248 event = build_safety_audit_event (
@@ -223,10 +258,8 @@ def write_json_report(report: dict[str, Any], path: str | Path) -> None:
223258
224259 report_path = Path (path )
225260 report_path .parent .mkdir (parents = True , exist_ok = True )
226- report_path .write_text (
227- json .dumps (report , ensure_ascii = False , indent = 2 , sort_keys = False ) + "\n " ,
228- encoding = "utf-8" ,
229- )
261+ payload = json .dumps (report , ensure_ascii = False , indent = 2 , sort_keys = False )
262+ report_path .write_text (f"{ payload } \n " , encoding = "utf-8" )
230263
231264
232265def write_audit_log (events : list [SafetyAuditEvent ], path : str | Path ) -> None :
@@ -258,7 +291,9 @@ def _is_subset(expected_rules: list[str], actual_rules: list[str]) -> bool:
258291
259292def _mismatch_message (result : dict [str , Any ]) -> str :
260293 report = result ["report" ]
261- actual_rules = [finding ["rule_id" ] for finding in report .get ("findings" , [])]
294+ actual_rules = [
295+ finding ["rule_id" ] for finding in report .get ("findings" , [])
296+ ]
262297 return (
263298 f"{ result ['sample_id' ]} : expected decision={ result ['expected_decision' ]} "
264299 f"rules={ result ['expected_rules' ]} , got decision={ report ['decision' ]} rules={ actual_rules } "
0 commit comments