@@ -35,6 +35,14 @@ class EUAIControl:
3535 tool_name : str
3636
3737
38+ @dataclass (frozen = True )
39+ class EUAIControlMappingContract :
40+ article : str
41+ modeled_sink_type : str
42+ modeled_reason_code : str
43+ scope_note : str
44+
45+
3846EUAI_CONTROLS : tuple [EUAIControl , ...] = (
3947 EUAIControl ("Article 9" , "Risk Management" , "shell.exec" , "bash_execute" ),
4048 EUAIControl ("Article 10" , "Data Governance" , "filesystem.read" , "read_file" ),
@@ -46,6 +54,57 @@ class EUAIControl:
4654 EUAIControl ("Article 17" , "Quality Management" , "http.request" , "network_call" ),
4755)
4856
57+ EUAI_CONTROL_MAPPING_CONTRACTS : tuple [EUAIControlMappingContract , ...] = (
58+ EUAIControlMappingContract (
59+ "Article 9" ,
60+ "shell.exec" ,
61+ "UNTRUSTED_TO_CRITICAL_SINK" ,
62+ "Risk management is modeled as blocking untrusted execution at critical shell sink." ,
63+ ),
64+ EUAIControlMappingContract (
65+ "Article 10" ,
66+ "filesystem.read" ,
67+ "PATH_BLOCKED" ,
68+ "Data governance is modeled as blocking sensitive filesystem reads outside allowlist paths." ,
69+ ),
70+ EUAIControlMappingContract (
71+ "Article 12" ,
72+ "http.request" ,
73+ "DOMAIN_BLOCKED" ,
74+ "Logging/traceability is modeled as blocking outbound trace exfiltration to unapproved domains." ,
75+ ),
76+ EUAIControlMappingContract (
77+ "Article 13" ,
78+ "tool.custom" ,
79+ "STEP_UP_REQUIRED" ,
80+ "Transparency is modeled as requiring workflow step-up in non-prod-locked profiles." ,
81+ ),
82+ EUAIControlMappingContract (
83+ "Article 14" ,
84+ "tool.custom" ,
85+ "STEP_UP_REQUIRED" ,
86+ "Human oversight is modeled as runtime approval gating via custom tool workflow." ,
87+ ),
88+ EUAIControlMappingContract (
89+ "Article 15" ,
90+ "credentials.access" ,
91+ "CREDENTIAL_ACCESS_BLOCKED" ,
92+ "Robustness/cybersecurity is modeled as hard credential sink boundary enforcement." ,
93+ ),
94+ EUAIControlMappingContract (
95+ "Article 16" ,
96+ "shell.exec" ,
97+ "UNTRUSTED_TO_CRITICAL_SINK" ,
98+ "Provider post-market obligations are modeled as runtime blocking of unsafe execution tasks." ,
99+ ),
100+ EUAIControlMappingContract (
101+ "Article 17" ,
102+ "http.request" ,
103+ "DOMAIN_BLOCKED" ,
104+ "Quality management is modeled as network egress policy control for quality workflow calls." ,
105+ ),
106+ )
107+
49108
50109@dataclass (frozen = True )
51110class EUAICase :
@@ -72,6 +131,20 @@ def expected_profile(self) -> str:
72131 return self .profile
73132
74133
134+ def _expected_witness_taint_level (taint_level : str ) -> str :
135+ # Engine contract: unknown and untrusted inputs normalize to untrusted.
136+ if taint_level in {"unknown" , "untrusted" }:
137+ return "untrusted"
138+ return taint_level
139+
140+
141+ def _mapping_contract_for (article : str ) -> EUAIControlMappingContract :
142+ for contract in EUAI_CONTROL_MAPPING_CONTRACTS :
143+ if contract .article == article :
144+ return contract
145+ raise KeyError (f"Missing EU AI Act mapping contract for { article } " )
146+
147+
75148def _target_for (case : EUAICase ) -> str :
76149 article_slug = case .control .article .lower ().replace (" " , "_" )
77150 idx = case .scenario_index
@@ -152,6 +225,7 @@ def test_eu_ai_act_control_mapping_generated(case: EUAICase) -> None:
152225 expected_decision , expected_reason = _expected_for (case )
153226 target = _target_for (case )
154227 article_slug = case .control .article .lower ().replace (" " , "_" )
228+ contract = _mapping_contract_for (case .control .article )
155229
156230 request = ActionRequest (
157231 request_id = str (uuid .uuid4 ()),
@@ -181,7 +255,68 @@ def test_eu_ai_act_control_mapping_generated(case: EUAICase) -> None:
181255
182256 decision = runtime .evaluate (request )
183257 assert decision .decision == expected_decision
258+ assert decision .sink_type == case .control .sink_type
259+ assert decision .target == target
184260 assert decision .reason_code == expected_reason
261+ if expected_reason == "STEP_UP_REQUIRED" :
262+ assert contract .modeled_reason_code == "STEP_UP_REQUIRED"
263+ elif expected_reason == "POLICY_ALLOW" :
264+ assert case .control .sink_type == "tool.custom"
265+ assert case .expected_profile == "prod_locked"
266+ assert contract .modeled_reason_code == "STEP_UP_REQUIRED"
267+ else :
268+ assert decision .reason_code == contract .modeled_reason_code
185269 assert decision .annotations .get ("effective_policy_profile" ) == case .expected_profile
186- assert runtime .last_witness is not None
270+ witness = runtime .last_witness
271+ assert isinstance (witness , dict )
272+ assert witness .get ("request_id" ) == decision .request_id
273+ assert witness .get ("decision" ) == expected_decision
274+ assert witness .get ("reason_code" ) == expected_reason
275+ assert witness .get ("sink_type" ) == case .control .sink_type
276+ assert witness .get ("target" ) == target
277+
278+ provenance = witness .get ("provenance" )
279+ assert isinstance (provenance , dict )
280+ assert provenance .get ("source" ) == f"eu_ai_act_{ article_slug } "
281+ assert provenance .get ("taint_level" ) == _expected_witness_taint_level (case .taint_level )
282+ markers = provenance .get ("taint_markers" )
283+ assert isinstance (markers , list )
284+ assert "eu_ai_act" in markers
285+ assert article_slug in markers
286+ assert f"scenario_{ case .scenario_index :02d} " in markers
287+
288+
289+ def test_eu_ai_act_mapping_contract_explicit_and_complete () -> None :
290+ """EU AI Act article-to-engine mapping assumptions are explicit and complete."""
291+ control_articles = {control .article for control in EUAI_CONTROLS }
292+ contract_articles = {contract .article for contract in EUAI_CONTROL_MAPPING_CONTRACTS }
293+ assert contract_articles == control_articles
187294
295+ for control in EUAI_CONTROLS :
296+ contract = _mapping_contract_for (control .article )
297+ sample_case = EUAICase (
298+ control = control ,
299+ scenario_index = 1 ,
300+ profile = "dev_strict" ,
301+ taint_level = "untrusted" ,
302+ input_class = "untrusted" ,
303+ )
304+ _ , expected_reason = _expected_for (sample_case )
305+ assert control .sink_type == contract .modeled_sink_type
306+ if control .sink_type == "tool.custom" :
307+ # tool.custom reason depends on effective profile; prod_locked can allow.
308+ assert contract .modeled_reason_code == "STEP_UP_REQUIRED"
309+ assert expected_reason == "STEP_UP_REQUIRED"
310+ else :
311+ assert expected_reason == contract .modeled_reason_code
312+ assert contract .scope_note .strip ()
313+
314+
315+ def test_eu_ai_act_gap_aug_2026_unmodeled_obligations_are_explicit () -> None :
316+ pytest .skip (
317+ "Gap (explicit): this generated suite models runtime sink enforcement only. "
318+ "It does not yet cover technical documentation evidence workflows (Article 11), "
319+ "conformity assessment and CE marking workflows (Articles 43-49), "
320+ "or post-market monitoring/serious-incident reporting process obligations "
321+ "that are not reducible to single runtime sink decisions."
322+ )
0 commit comments