@@ -312,6 +312,64 @@ def test_working_tree_skips_bundle_scope(self) -> None:
312312 self .assertNotIn ("b" , {r ["rule_id" ] for r in result ["rows" ]})
313313
314314
315+ class DelegatedGates (unittest .TestCase ):
316+ """Delegated gates (issue #67): a host runner single-sources the gates; a check's
317+ bare `subcmd` runs as `<runner> <subcmd>`, so PDCA orchestrates without re-declaring."""
318+
319+ def setUp (self ) -> None :
320+ self .tmp = Path (tempfile .mkdtemp ())
321+
322+ def tearDown (self ) -> None :
323+ shutil .rmtree (self .tmp , ignore_errors = True )
324+
325+ def _cfg (self , checks : list [dict ], runner : str = "" ) -> Config :
326+ cfg = _stub_config (self .tmp )
327+ cfg .gates_checks = checks
328+ cfg .gates_runner = runner
329+ return cfg
330+
331+ def test_subcmd_resolved_against_runner (self ) -> None :
332+ # `subcmd` is run as `<runner> <subcmd>`; the resolved command is the oracle.
333+ cfg = self ._cfg (
334+ [{"id" : "ci" , "tier" : "T1" , "label" : "host ci" , "subcmd" : "ok-step" ,
335+ "gating" : True , "scope" : "repo" }],
336+ runner = "echo" )
337+ result = gates .run_working_tree (cfg )
338+ row = next (r for r in result ["rows" ] if r ["rule_id" ] == "ci" )
339+ self .assertEqual (row ["result" ], "pass" ) # `echo ok-step` exits 0
340+ self .assertEqual (row ["oracle" ], "echo ok-step" ) # runner prefixed
341+
342+ def test_missing_runner_is_a_clear_failing_row_not_a_crash (self ) -> None :
343+ cfg = self ._cfg (
344+ [{"id" : "x" , "tier" : "T1" , "label" : "host ci" , "subcmd" : "build" ,
345+ "gating" : True , "scope" : "repo" }],
346+ runner = "definitely-not-a-real-binary-zzz xtask" )
347+ result = gates .run_working_tree (cfg ) # must not raise
348+ row = next (r for r in result ["rows" ] if r ["rule_id" ] == "x" )
349+ self .assertEqual (row ["result" ], "fail" )
350+ self .assertIn ("not found on PATH" , row ["path_line" ])
351+
352+ def test_subcmd_without_runner_is_flagged (self ) -> None :
353+ cfg = self ._cfg (
354+ [{"id" : "y" , "tier" : "T1" , "label" : "host ci" , "subcmd" : "build" ,
355+ "gating" : True , "scope" : "repo" }],
356+ runner = "" ) # subcmd declared but no runner configured
357+ result = gates .run_working_tree (cfg )
358+ row = next (r for r in result ["rows" ] if r ["rule_id" ] == "y" )
359+ self .assertEqual (row ["result" ], "fail" )
360+ self .assertIn ("runner is unset" , row ["path_line" ])
361+
362+ def test_inline_cmd_unaffected_by_runner (self ) -> None :
363+ # A full `cmd` still runs verbatim even when a runner is configured.
364+ cfg = self ._cfg (
365+ [{"id" : "z" , "tier" : "T1" , "label" : "inline" , "cmd" : "true" ,
366+ "gating" : True , "scope" : "repo" }],
367+ runner = "echo" )
368+ row = next (r for r in gates .run_working_tree (cfg )["rows" ] if r ["rule_id" ] == "z" )
369+ self .assertEqual (row ["result" ], "pass" )
370+ self .assertEqual (row ["oracle" ], "true" ) # not prefixed with the runner
371+
372+
315373class BuilderGuard (unittest .TestCase ):
316374 """The PreToolUse hook enforcing the builder's STOP discipline."""
317375
0 commit comments