66import argparse
77import ast
88import json
9+ import os
10+ import subprocess
11+ import sys
912from dataclasses import asdict , dataclass
1013from pathlib import Path
1114from typing import Any , Iterable , Sequence
4346 Path ("cppmega_v4/_tilelang/linear_attention_path_d.py" ): "_fla_chunk_kernel_importable" ,
4447 Path ("cppmega_v4/_tilelang/kda_path_d.py" ): "_fla_kda_chunk_importable" ,
4548}
49+ PATH_D_UNSAFE_IMPORT_ENV = "CPPMEGA_V4_ENABLE_UNSAFE_TRITON_IMPORT"
50+ PATH_D_DEFAULT_STATUS_TIMEOUT_SECONDS = 20
51+ PATH_D_DEFAULT_STATUS_PROBE_CODE = r"""
52+ import json
53+ import os
54+ import sys
55+
56+ UNSAFE_ENV = "CPPMEGA_V4_ENABLE_UNSAFE_TRITON_IMPORT"
57+ os.environ.pop(UNSAFE_ENV, None)
58+
59+ from cppmega_v4._tilelang.kda_path_d import (
60+ _fla_kda_chunk_importable,
61+ _path_d_runtime_status as kda_runtime_status,
62+ _triton_frontend_importable as kda_triton_importable,
63+ )
64+ from cppmega_v4._tilelang.linear_attention_path_d import (
65+ _fla_chunk_kernel_importable,
66+ _path_d_runtime_status as gdn_runtime_status,
67+ _triton_frontend_importable as gdn_triton_importable,
68+ )
69+
70+
71+ def call(label, fn):
72+ ok, reason = fn()
73+ return {"label": label, "ok": bool(ok), "reason": str(reason)}
74+
75+
76+ payload = {
77+ "probes": [
78+ call("gdn.runtime_status", gdn_runtime_status),
79+ call("gdn.triton_frontend", gdn_triton_importable),
80+ call("gdn.fla_chunk", _fla_chunk_kernel_importable),
81+ call("kda.runtime_status", kda_runtime_status),
82+ call("kda.triton_frontend", kda_triton_importable),
83+ call("kda.fla_chunk", _fla_kda_chunk_importable),
84+ ],
85+ "unsafe_modules": sorted(
86+ name
87+ for name in sys.modules
88+ if name == "triton"
89+ or name.startswith("triton.")
90+ or name == "fla"
91+ or name.startswith("fla.")
92+ ),
93+ }
94+ print(json.dumps(payload, sort_keys=True))
95+ """
4696PATH_E_ADAPTER_MODULES = {
4797 "v4.gdn.path_e" : Path ("cppmega_v4/nn/_external/mlx_lm_gated_delta_update.py" ),
4898 "v4.kda.path_e" : Path ("cppmega_v4/nn/_external/mlx_lm_kda_update.py" ),
@@ -289,6 +339,18 @@ def _first_import_line(function: ast.FunctionDef, module_name: str) -> int | Non
289339 return min (lines ) if lines else None
290340
291341
342+ def _first_triton_import_probe_line (function : ast .FunctionDef ) -> int | None :
343+ lines = [
344+ line
345+ for line in (
346+ _first_import_line (function , "triton" ),
347+ _first_call_line (function , "import_triton_with_local_symbols" ),
348+ )
349+ if line is not None
350+ ]
351+ return min (lines ) if lines else None
352+
353+
292354def _first_import_prefix_line (function : ast .FunctionDef , module_prefix : str ) -> int | None :
293355 lines : list [int ] = []
294356 for node in ast .walk (function ):
@@ -376,7 +438,7 @@ def _check_v4_path_d_import_guards(repo_root: Path) -> list[Finding]:
376438 function ,
377439 "unsafe_triton_frontend_import_enabled" ,
378440 )
379- import_line = _first_import_line (function , "triton" )
441+ import_line = _first_triton_import_probe_line (function )
380442 if import_line is None :
381443 findings .append (
382444 Finding (
@@ -385,7 +447,7 @@ def _check_v4_path_d_import_guards(repo_root: Path) -> list[Finding]:
385447 location = location ,
386448 detail = (
387449 "_triton_frontend_importable() must explicitly probe "
388- "Triton after the unsafe-import guard"
450+ "Triton with local native symbols after the unsafe-import guard"
389451 ),
390452 )
391453 )
@@ -445,6 +507,161 @@ def _check_v4_path_d_import_guards(repo_root: Path) -> list[Finding]:
445507 return findings
446508
447509
510+ def _path_d_disabled_reason_is_clear (reason : str ) -> bool :
511+ return (
512+ "unsafe" in reason .lower ()
513+ and "runtime adapter not reached" in reason
514+ and PATH_D_UNSAFE_IMPORT_ENV in reason
515+ )
516+
517+
518+ def check_path_d_default_status_no_unsafe_imports (
519+ repo_root : Path = ROOT ,
520+ ) -> list [Finding ]:
521+ """Run default Path D status probes in a subprocess.
522+
523+ Local Triton/FLA imports can abort the interpreter. This guard keeps that
524+ failure out of the main test process and verifies the default path stays
525+ fail-closed with an actionable disabled reason.
526+ """
527+
528+ env = os .environ .copy ()
529+ env .pop (PATH_D_UNSAFE_IMPORT_ENV , None )
530+ pythonpath = env .get ("PYTHONPATH" )
531+ env ["PYTHONPATH" ] = (
532+ f"{ repo_root } { os .pathsep } { pythonpath } " if pythonpath else str (repo_root )
533+ )
534+ try :
535+ proc = subprocess .run (
536+ [sys .executable , "-c" , PATH_D_DEFAULT_STATUS_PROBE_CODE ],
537+ cwd = repo_root ,
538+ env = env ,
539+ text = True ,
540+ capture_output = True ,
541+ timeout = PATH_D_DEFAULT_STATUS_TIMEOUT_SECONDS ,
542+ check = False ,
543+ )
544+ except subprocess .TimeoutExpired :
545+ return [
546+ Finding (
547+ code = "path_d_default_status_probe_timeout" ,
548+ severity = "error" ,
549+ location = "v4.path_d" ,
550+ detail = (
551+ "default Path D status subprocess timed out before proving "
552+ "unsafe Triton/FLA imports stay disabled"
553+ ),
554+ )
555+ ]
556+
557+ if proc .returncode != 0 :
558+ detail = _first_line (proc .stderr or proc .stdout or "no output" )
559+ return [
560+ Finding (
561+ code = "path_d_default_status_probe_crashed" ,
562+ severity = "error" ,
563+ location = "v4.path_d" ,
564+ detail = (
565+ "default Path D status subprocess exited with "
566+ f"{ proc .returncode } : { detail } "
567+ ),
568+ )
569+ ]
570+
571+ try :
572+ last_line = next (
573+ line for line in reversed (proc .stdout .splitlines ()) if line .strip ()
574+ )
575+ payload = json .loads (last_line )
576+ except (StopIteration , json .JSONDecodeError ) as exc :
577+ return [
578+ Finding (
579+ code = "path_d_default_status_probe_bad_output" ,
580+ severity = "error" ,
581+ location = "v4.path_d" ,
582+ detail = f"default Path D status subprocess did not emit JSON: { exc } " ,
583+ )
584+ ]
585+
586+ findings : list [Finding ] = []
587+ unsafe_modules = payload .get ("unsafe_modules" )
588+ if not isinstance (unsafe_modules , list ):
589+ findings .append (
590+ Finding (
591+ code = "path_d_default_status_probe_bad_output" ,
592+ severity = "error" ,
593+ location = "v4.path_d" ,
594+ detail = "default Path D status subprocess omitted unsafe_modules list" ,
595+ )
596+ )
597+ elif unsafe_modules :
598+ findings .append (
599+ Finding (
600+ code = "path_d_default_status_imported_unsafe_deps" ,
601+ severity = "error" ,
602+ location = "v4.path_d" ,
603+ detail = (
604+ "default Path D status probe imported unsafe modules in "
605+ f"the subprocess: { unsafe_modules [:8 ]!r} "
606+ ),
607+ )
608+ )
609+
610+ probes = payload .get ("probes" )
611+ if not isinstance (probes , list ):
612+ findings .append (
613+ Finding (
614+ code = "path_d_default_status_probe_bad_output" ,
615+ severity = "error" ,
616+ location = "v4.path_d" ,
617+ detail = "default Path D status subprocess omitted probes list" ,
618+ )
619+ )
620+ return findings
621+
622+ for probe in probes :
623+ if not isinstance (probe , dict ):
624+ findings .append (
625+ Finding (
626+ code = "path_d_default_status_probe_bad_output" ,
627+ severity = "error" ,
628+ location = "v4.path_d" ,
629+ detail = (
630+ "default Path D status probe payload item is invalid: "
631+ f"{ probe !r} "
632+ ),
633+ )
634+ )
635+ continue
636+ label = str (probe .get ("label" ) or "unknown" )
637+ reason = str (probe .get ("reason" ) or "" )
638+ if bool (probe .get ("ok" )):
639+ findings .append (
640+ Finding (
641+ code = "path_d_default_status_marked_available" ,
642+ severity = "error" ,
643+ location = label ,
644+ detail = (
645+ "Path D default status probe reported available "
646+ "without unsafe import opt-in"
647+ ),
648+ )
649+ )
650+ if not _path_d_disabled_reason_is_clear (reason ):
651+ findings .append (
652+ Finding (
653+ code = "path_d_default_status_missing_disabled_reason" ,
654+ severity = "error" ,
655+ location = label ,
656+ detail = (
657+ "Path D default status probe did not return the "
658+ f"unsafe-import disabled reason: { _first_line (reason )} "
659+ ),
660+ )
661+ )
662+ return findings
663+
664+
448665def _check_v4_path_e_adapters (repo_root : Path ) -> list [Finding ]:
449666 findings : list [Finding ] = []
450667 for location , rel_path in PATH_E_ADAPTER_MODULES .items ():
@@ -553,6 +770,7 @@ def check_path_contracts(repo_root: Path = ROOT) -> list[Finding]:
553770 )
554771 )
555772 findings .extend (_check_v4_path_d_import_guards (repo_root ))
773+ findings .extend (check_path_d_default_status_no_unsafe_imports (repo_root ))
556774 findings .extend (_check_v4_path_e_adapters (repo_root ))
557775 return findings
558776
0 commit comments