pybinaryguard 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. pybinaryguard/__init__.py +78 -0
  2. pybinaryguard/__main__.py +7 -0
  3. pybinaryguard/_compat/__init__.py +0 -0
  4. pybinaryguard/agent/__init__.py +63 -0
  5. pybinaryguard/agent/guard.py +232 -0
  6. pybinaryguard/agent/recommender.py +283 -0
  7. pybinaryguard/agent/schema.py +200 -0
  8. pybinaryguard/agent/simulator.py +430 -0
  9. pybinaryguard/agent/tool_interface.py +474 -0
  10. pybinaryguard/analyzers/__init__.py +209 -0
  11. pybinaryguard/analyzers/base.py +40 -0
  12. pybinaryguard/analyzers/dependency_analyzer.py +336 -0
  13. pybinaryguard/analyzers/elf_analyzer.py +754 -0
  14. pybinaryguard/analyzers/symbol_analyzer.py +280 -0
  15. pybinaryguard/analyzers/wheel_analyzer.py +308 -0
  16. pybinaryguard/cli/__init__.py +5 -0
  17. pybinaryguard/cli/commands.py +414 -0
  18. pybinaryguard/cli/formatters.py +720 -0
  19. pybinaryguard/cli/main.py +250 -0
  20. pybinaryguard/diagnostics/__init__.py +13 -0
  21. pybinaryguard/diagnostics/explainer.py +356 -0
  22. pybinaryguard/diagnostics/findings.py +146 -0
  23. pybinaryguard/diagnostics/suggestions.py +508 -0
  24. pybinaryguard/frameworks/__init__.py +20 -0
  25. pybinaryguard/frameworks/onnxruntime.py +214 -0
  26. pybinaryguard/frameworks/pytorch.py +223 -0
  27. pybinaryguard/frameworks/tensorflow.py +266 -0
  28. pybinaryguard/frameworks/tensorrt.py +189 -0
  29. pybinaryguard/models/__init__.py +19 -0
  30. pybinaryguard/models/enums.py +102 -0
  31. pybinaryguard/models/finding.py +109 -0
  32. pybinaryguard/models/package.py +121 -0
  33. pybinaryguard/models/system.py +118 -0
  34. pybinaryguard/plugins/__init__.py +19 -0
  35. pybinaryguard/plugins/contrib/__init__.py +7 -0
  36. pybinaryguard/plugins/contrib/gstreamer.py +109 -0
  37. pybinaryguard/plugins/contrib/jetson.py +385 -0
  38. pybinaryguard/plugins/contrib/opencv.py +306 -0
  39. pybinaryguard/plugins/contrib/tensorrt.py +426 -0
  40. pybinaryguard/plugins/hooks.py +273 -0
  41. pybinaryguard/plugins/loader.py +190 -0
  42. pybinaryguard/predictor/__init__.py +23 -0
  43. pybinaryguard/predictor/dependency_graph.py +226 -0
  44. pybinaryguard/predictor/linker_simulator.py +161 -0
  45. pybinaryguard/predictor/predictor.py +144 -0
  46. pybinaryguard/predictor/resolver.py +282 -0
  47. pybinaryguard/probes/__init__.py +73 -0
  48. pybinaryguard/probes/base.py +45 -0
  49. pybinaryguard/probes/board_probe.py +248 -0
  50. pybinaryguard/probes/cpu_probe.py +176 -0
  51. pybinaryguard/probes/glibc_probe.py +215 -0
  52. pybinaryguard/probes/gpu_probe.py +430 -0
  53. pybinaryguard/probes/library_probe.py +132 -0
  54. pybinaryguard/probes/os_probe.py +227 -0
  55. pybinaryguard/probes/python_probe.py +135 -0
  56. pybinaryguard/probes/toolchain_probe.py +89 -0
  57. pybinaryguard/probes/venv_probe.py +111 -0
  58. pybinaryguard/profiles/__init__.py +12 -0
  59. pybinaryguard/profiles/engine.py +343 -0
  60. pybinaryguard/rules/__init__.py +24 -0
  61. pybinaryguard/rules/base.py +57 -0
  62. pybinaryguard/rules/builtin/__init__.py +136 -0
  63. pybinaryguard/rules/builtin/arch_rules.py +86 -0
  64. pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
  65. pybinaryguard/rules/builtin/container_rules.py +170 -0
  66. pybinaryguard/rules/builtin/cpu_rules.py +204 -0
  67. pybinaryguard/rules/builtin/cuda_rules.py +760 -0
  68. pybinaryguard/rules/builtin/dependency_rules.py +278 -0
  69. pybinaryguard/rules/builtin/framework_rules.py +252 -0
  70. pybinaryguard/rules/builtin/glibc_rules.py +320 -0
  71. pybinaryguard/rules/builtin/numpy_rules.py +110 -0
  72. pybinaryguard/rules/builtin/predictive_rules.py +165 -0
  73. pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
  74. pybinaryguard/rules/builtin/source_build_rules.py +150 -0
  75. pybinaryguard/rules/builtin/venv_rules.py +159 -0
  76. pybinaryguard/rules/engine.py +123 -0
  77. pybinaryguard/scanner.py +904 -0
  78. pybinaryguard/scoring/__init__.py +19 -0
  79. pybinaryguard/scoring/engine.py +416 -0
  80. pybinaryguard/snapshot/__init__.py +20 -0
  81. pybinaryguard/snapshot/generator.py +168 -0
  82. pybinaryguard/snapshot/lockfile.py +152 -0
  83. pybinaryguard/snapshot/verifier.py +315 -0
  84. pybinaryguard/validators/__init__.py +7 -0
  85. pybinaryguard/validators/import_validator.py +231 -0
  86. pybinaryguard-1.0.0.dist-info/METADATA +876 -0
  87. pybinaryguard-1.0.0.dist-info/RECORD +91 -0
  88. pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
  89. pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
  90. pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
  91. pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,278 @@
1
+ """Dependency conflict detection rules.
2
+
3
+ Reads installed packages' METADATA to detect version conflicts between
4
+ what's actually installed. Unlike pip/Poetry which resolve dependencies
5
+ before install, this catches conflicts that exist right now in the
6
+ live environment — including conflicts caused by manual pip installs,
7
+ conda mixing, or stale venvs.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ from typing import Dict, List, Optional, Set, Tuple
15
+
16
+ from pybinaryguard.models.enums import Severity
17
+ from pybinaryguard.models.finding import Finding
18
+ from pybinaryguard.models.package import PackageBinaryInfo
19
+ from pybinaryguard.models.system import SystemProfile
20
+ from pybinaryguard.rules.base import Rule
21
+
22
+
23
+ def _parse_version(version_str: str) -> Tuple[int, ...]:
24
+ """Parse a PEP 440 version into a comparable tuple."""
25
+ # Strip pre/post/dev suffixes for comparison
26
+ clean = re.split(r"[^0-9.]", version_str.strip())[0]
27
+ parts = []
28
+ for part in clean.split("."):
29
+ try:
30
+ parts.append(int(part))
31
+ except ValueError:
32
+ break
33
+ return tuple(parts) if parts else (0,)
34
+
35
+
36
+ def _version_matches(installed: str, spec: str) -> bool:
37
+ """Check if installed version matches a version specifier.
38
+
39
+ Handles ==, >=, <=, !=, >, <, ~=, and bare versions.
40
+ """
41
+ spec = spec.strip()
42
+ if not spec:
43
+ return True
44
+
45
+ installed_t = _parse_version(installed)
46
+
47
+ # Handle compound specs (>=1.0,<2.0)
48
+ if "," in spec:
49
+ return all(_version_matches(installed, s.strip()) for s in spec.split(","))
50
+
51
+ # Parse operator and version
52
+ match = re.match(r"(~=|==|!=|>=|<=|>|<)\s*(.+)", spec)
53
+ if not match:
54
+ # Bare version = equality
55
+ return installed_t == _parse_version(spec)
56
+
57
+ op, ver_str = match.group(1), match.group(2)
58
+
59
+ # Handle wildcard ==1.0.*
60
+ if ver_str.endswith(".*"):
61
+ prefix = _parse_version(ver_str[:-2])
62
+ if op == "==":
63
+ return installed_t[:len(prefix)] == prefix
64
+ elif op == "!=":
65
+ return installed_t[:len(prefix)] != prefix
66
+
67
+ ver_t = _parse_version(ver_str)
68
+
69
+ if op == "==":
70
+ return installed_t == ver_t
71
+ elif op == "!=":
72
+ return installed_t != ver_t
73
+ elif op == ">=":
74
+ return installed_t >= ver_t
75
+ elif op == "<=":
76
+ return installed_t <= ver_t
77
+ elif op == ">":
78
+ return installed_t > ver_t
79
+ elif op == "<":
80
+ return installed_t < ver_t
81
+ elif op == "~=":
82
+ # ~=1.4.2 means >=1.4.2,<1.5.0
83
+ return installed_t >= ver_t and installed_t[:len(ver_t) - 1] == ver_t[:len(ver_t) - 1]
84
+
85
+ return True
86
+
87
+
88
+ def _read_requires(dist_info_path: str) -> List[str]:
89
+ """Read Requires-Dist from METADATA file."""
90
+ metadata_path = os.path.join(dist_info_path, "METADATA")
91
+ if not os.path.isfile(metadata_path):
92
+ return []
93
+
94
+ requires = []
95
+ try:
96
+ with open(metadata_path, "r", encoding="utf-8", errors="replace") as f:
97
+ for line in f:
98
+ line = line.strip()
99
+ if line.startswith("Requires-Dist:"):
100
+ req = line[len("Requires-Dist:"):].strip()
101
+ requires.append(req)
102
+ elif line == "" and requires:
103
+ # End of headers
104
+ break
105
+ except OSError:
106
+ pass
107
+ return requires
108
+
109
+
110
+ def _parse_requirement(req_str: str) -> Optional[Tuple[str, str, str]]:
111
+ """Parse a Requires-Dist string into (name, version_spec, extras/markers).
112
+
113
+ Returns None if the requirement has markers that exclude current env.
114
+ """
115
+ # Strip inline comments
116
+ req_str = req_str.split("#")[0].strip()
117
+
118
+ # Handle markers (e.g., ; python_version >= "3.8")
119
+ marker = ""
120
+ if ";" in req_str:
121
+ req_str, marker = req_str.split(";", 1)
122
+ req_str = req_str.strip()
123
+ marker = marker.strip()
124
+
125
+ # Skip requirements with extra markers (optional deps)
126
+ if 'extra ==' in marker or 'extra ==' in marker:
127
+ return None
128
+
129
+ # Handle extras (e.g., package[extra])
130
+ extras = ""
131
+ if "[" in req_str:
132
+ base, rest = req_str.split("[", 1)
133
+ extras = rest.split("]")[0]
134
+ req_str = base + rest.split("]")[1] if "]" in rest else base
135
+
136
+ # Parse name and version spec
137
+ match = re.match(r"([A-Za-z0-9][\w.-]*)\s*(.*)", req_str.strip())
138
+ if not match:
139
+ return None
140
+
141
+ name = match.group(1).strip().lower().replace("-", "_").replace(".", "_")
142
+ version_spec = match.group(2).strip().strip("()")
143
+
144
+ return (name, version_spec, marker)
145
+
146
+
147
+ class DependencyConflictRule(Rule):
148
+ """Detect version conflicts between installed packages.
149
+
150
+ Reads each package's Requires-Dist metadata and checks whether
151
+ the actually installed version satisfies the requirement. Catches
152
+ conflicts that pip missed, or that were introduced by manual installs.
153
+ """
154
+
155
+ rule_id = "DEPENDENCY_VERSION_CONFLICT"
156
+ description = "Detect version conflicts between installed packages"
157
+
158
+ def evaluate(
159
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
160
+ ) -> List[Finding]:
161
+ findings: List[Finding] = []
162
+
163
+ # Build installed package index: normalized_name -> version
164
+ installed: Dict[str, str] = {}
165
+ dist_info_dirs: Dict[str, str] = {}
166
+
167
+ for pkg in packages:
168
+ norm_name = pkg.package_name.lower().replace("-", "_").replace(".", "_")
169
+ installed[norm_name] = pkg.package_version
170
+ dist_info_dirs[norm_name] = pkg.install_path
171
+
172
+ # Check each package's requirements
173
+ seen_conflicts: Set[str] = set()
174
+
175
+ for pkg in packages:
176
+ dist_info = pkg.install_path
177
+ if not dist_info or not os.path.isdir(dist_info):
178
+ continue
179
+
180
+ requires = _read_requires(dist_info)
181
+
182
+ for req_str in requires:
183
+ parsed = _parse_requirement(req_str)
184
+ if parsed is None:
185
+ continue
186
+
187
+ dep_name, version_spec, marker = parsed
188
+
189
+ if dep_name not in installed:
190
+ continue # Not installed — a different kind of issue
191
+
192
+ if not version_spec:
193
+ continue # No version constraint
194
+
195
+ actual_version = installed[dep_name]
196
+
197
+ if not _version_matches(actual_version, version_spec):
198
+ conflict_key = f"{pkg.package_name}->{dep_name}"
199
+ if conflict_key in seen_conflicts:
200
+ continue
201
+ seen_conflicts.add(conflict_key)
202
+
203
+ findings.append(Finding(
204
+ rule_id=self.rule_id,
205
+ severity=Severity.WARNING,
206
+ title=f"Version conflict: {pkg.package_name} requires "
207
+ f"{dep_name} {version_spec}",
208
+ explanation=(
209
+ f"{pkg.package_name}=={pkg.package_version} requires "
210
+ f"{dep_name}{version_spec} but "
211
+ f"{dep_name}=={actual_version} is installed. "
212
+ f"This version mismatch may cause ImportError or "
213
+ f"unexpected behavior at runtime."
214
+ ),
215
+ package=pkg.package_name,
216
+ package_version=pkg.package_version,
217
+ suggestion=(
218
+ f"pip install '{dep_name}{version_spec}' "
219
+ f"or pip install --upgrade {pkg.package_name}"
220
+ ),
221
+ confidence=0.95,
222
+ ))
223
+
224
+ return findings
225
+
226
+
227
+ class MissingDependencyRule(Rule):
228
+ """Detect required dependencies that are not installed at all."""
229
+
230
+ rule_id = "DEPENDENCY_MISSING"
231
+ description = "Detect required packages that are not installed"
232
+
233
+ def evaluate(
234
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
235
+ ) -> List[Finding]:
236
+ findings: List[Finding] = []
237
+
238
+ # Build installed index
239
+ installed: Set[str] = set()
240
+ for pkg in packages:
241
+ norm = pkg.package_name.lower().replace("-", "_").replace(".", "_")
242
+ installed.add(norm)
243
+
244
+ seen: Set[str] = set()
245
+
246
+ for pkg in packages:
247
+ dist_info = pkg.install_path
248
+ if not dist_info or not os.path.isdir(dist_info):
249
+ continue
250
+
251
+ requires = _read_requires(dist_info)
252
+
253
+ for req_str in requires:
254
+ parsed = _parse_requirement(req_str)
255
+ if parsed is None:
256
+ continue
257
+
258
+ dep_name, version_spec, marker = parsed
259
+
260
+ if dep_name not in installed and dep_name not in seen:
261
+ seen.add(dep_name)
262
+ findings.append(Finding(
263
+ rule_id=self.rule_id,
264
+ severity=Severity.WARNING,
265
+ title=f"Missing dependency: {pkg.package_name} requires "
266
+ f"{dep_name}",
267
+ explanation=(
268
+ f"{pkg.package_name}=={pkg.package_version} requires "
269
+ f"{dep_name} but it is not installed. This will cause "
270
+ f"an ImportError when {pkg.package_name} tries to use it."
271
+ ),
272
+ package=pkg.package_name,
273
+ package_version=pkg.package_version,
274
+ suggestion=f"pip install {dep_name}",
275
+ confidence=0.9,
276
+ ))
277
+
278
+ return findings
@@ -0,0 +1,252 @@
1
+ """AI framework-specific compatibility rules.
2
+
3
+ Deep inspection rules for PyTorch, TensorFlow, TensorRT, and ONNX Runtime
4
+ that go beyond generic binary checks.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import List
10
+
11
+ from pybinaryguard.models.enums import Severity
12
+ from pybinaryguard.models.finding import Finding
13
+ from pybinaryguard.models.package import PackageBinaryInfo
14
+ from pybinaryguard.models.system import SystemProfile
15
+ from pybinaryguard.rules.base import Rule
16
+
17
+
18
+ class PYTORCH_CUDA_ABI_MISMATCH(Rule):
19
+ """Detects PyTorch CUDA ABI mismatches using deep inspection."""
20
+
21
+ rule_id = "PYTORCH_CUDA_ABI_MISMATCH"
22
+ description = "Checks PyTorch CUDA ABI compatibility"
23
+
24
+ def is_applicable(self, profile: SystemProfile) -> bool:
25
+ return True # Checked per-package inside evaluate
26
+
27
+ def evaluate(
28
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
29
+ ) -> List[Finding]:
30
+ from pybinaryguard.frameworks.pytorch import check_pytorch_cuda_abi
31
+
32
+ findings: List[Finding] = []
33
+ for package in packages:
34
+ if package.package_name.lower() != "torch":
35
+ continue
36
+
37
+ issues = check_pytorch_cuda_abi(package, profile)
38
+ for issue in issues:
39
+ sev_map = {
40
+ "critical": Severity.CRITICAL,
41
+ "warning": Severity.WARNING,
42
+ "info": Severity.INFO,
43
+ }
44
+ severity = sev_map.get(
45
+ issue.get("severity", "warning"), Severity.WARNING
46
+ )
47
+ findings.append(
48
+ Finding(
49
+ rule_id=self.rule_id,
50
+ severity=severity,
51
+ title=f"PyTorch: {issue['issue'].replace('_', ' ').title()}",
52
+ explanation=issue["message"],
53
+ package=package.package_name,
54
+ suggestion=issue.get("recommendation", ""),
55
+ confidence=0.95,
56
+ )
57
+ )
58
+ return findings
59
+
60
+
61
+ class PYTORCH_TORCHVISION_INCOMPATIBLE(Rule):
62
+ """Detects incompatible PyTorch and torchvision versions."""
63
+
64
+ rule_id = "PYTORCH_TORCHVISION_INCOMPATIBLE"
65
+ description = "Checks PyTorch and torchvision version compatibility"
66
+
67
+ def is_applicable(self, profile: SystemProfile) -> bool:
68
+ return True
69
+
70
+ def evaluate(
71
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
72
+ ) -> List[Finding]:
73
+ from pybinaryguard.frameworks.pytorch import (
74
+ check_pytorch_torchvision_compatibility,
75
+ )
76
+
77
+ findings: List[Finding] = []
78
+
79
+ # Find torch and torchvision versions
80
+ torch_version = None
81
+ torchvision_pkg = None
82
+ for package in packages:
83
+ name_lower = package.package_name.lower()
84
+ if name_lower == "torch" and package.package_version:
85
+ torch_version = package.package_version
86
+ elif name_lower == "torchvision":
87
+ torchvision_pkg = package
88
+
89
+ if torch_version is None or torchvision_pkg is None:
90
+ return findings
91
+
92
+ error = check_pytorch_torchvision_compatibility(
93
+ torch_version, torchvision_pkg.package_version
94
+ )
95
+ if error:
96
+ findings.append(
97
+ Finding(
98
+ rule_id=self.rule_id,
99
+ severity=Severity.WARNING,
100
+ title="Incompatible PyTorch and torchvision versions",
101
+ explanation=error,
102
+ package=torchvision_pkg.package_name,
103
+ suggestion=(
104
+ "Install matching torchvision version for your "
105
+ "PyTorch version"
106
+ ),
107
+ confidence=0.9,
108
+ )
109
+ )
110
+ return findings
111
+
112
+
113
+ class TENSORFLOW_COMPUTE_CAPABILITY_LOW(Rule):
114
+ """Detects TensorFlow compute capability requirements not met."""
115
+
116
+ rule_id = "TENSORFLOW_COMPUTE_CAPABILITY_LOW"
117
+ description = "Checks GPU compute capability for TensorFlow"
118
+
119
+ def is_applicable(self, profile: SystemProfile) -> bool:
120
+ return True
121
+
122
+ def evaluate(
123
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
124
+ ) -> List[Finding]:
125
+ from pybinaryguard.frameworks.tensorflow import (
126
+ check_tensorflow_compute_capability,
127
+ )
128
+
129
+ findings: List[Finding] = []
130
+ for package in packages:
131
+ if not package.package_name.lower().startswith("tensorflow"):
132
+ continue
133
+
134
+ issues = check_tensorflow_compute_capability(package, profile)
135
+ for issue in issues:
136
+ sev_map = {
137
+ "critical": Severity.CRITICAL,
138
+ "warning": Severity.WARNING,
139
+ "info": Severity.INFO,
140
+ }
141
+ severity = sev_map.get(
142
+ issue.get("severity", "warning"), Severity.WARNING
143
+ )
144
+ findings.append(
145
+ Finding(
146
+ rule_id=self.rule_id,
147
+ severity=severity,
148
+ title=f"TensorFlow: {issue['issue'].replace('_', ' ').title()}",
149
+ explanation=issue["message"],
150
+ package=package.package_name,
151
+ suggestion=issue.get("recommendation", ""),
152
+ confidence=0.95,
153
+ )
154
+ )
155
+ return findings
156
+
157
+
158
+ class TENSORRT_INCOMPATIBLE(Rule):
159
+ """Detects TensorRT compatibility issues."""
160
+
161
+ rule_id = "TENSORRT_INCOMPATIBLE"
162
+ description = "Checks TensorRT compatibility with CUDA/cuDNN"
163
+
164
+ def is_applicable(self, profile: SystemProfile) -> bool:
165
+ return True
166
+
167
+ def evaluate(
168
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
169
+ ) -> List[Finding]:
170
+ from pybinaryguard.frameworks.tensorrt import validate_tensorrt_engine
171
+
172
+ findings: List[Finding] = []
173
+ for package in packages:
174
+ if not package.package_name.lower().startswith("tensorrt"):
175
+ continue
176
+
177
+ issues = validate_tensorrt_engine(package, profile)
178
+ for issue in issues:
179
+ sev_map = {
180
+ "critical": Severity.CRITICAL,
181
+ "warning": Severity.WARNING,
182
+ "info": Severity.INFO,
183
+ }
184
+ severity = sev_map.get(
185
+ issue.get("severity", "warning"), Severity.WARNING
186
+ )
187
+ findings.append(
188
+ Finding(
189
+ rule_id=self.rule_id,
190
+ severity=severity,
191
+ title=f"TensorRT: {issue['issue'].replace('_', ' ').title()}",
192
+ explanation=issue["message"],
193
+ package=package.package_name,
194
+ suggestion=issue.get("recommendation", ""),
195
+ confidence=0.9,
196
+ )
197
+ )
198
+ return findings
199
+
200
+
201
+ class ONNX_RUNTIME_PROVIDER_MISMATCH(Rule):
202
+ """Detects ONNX Runtime execution provider mismatches."""
203
+
204
+ rule_id = "ONNX_RUNTIME_PROVIDER_MISMATCH"
205
+ description = "Checks ONNX Runtime execution provider compatibility"
206
+
207
+ def is_applicable(self, profile: SystemProfile) -> bool:
208
+ return True
209
+
210
+ def evaluate(
211
+ self, profile: SystemProfile, packages: List[PackageBinaryInfo]
212
+ ) -> List[Finding]:
213
+ from pybinaryguard.frameworks.onnxruntime import (
214
+ check_onnx_runtime_providers,
215
+ )
216
+
217
+ findings: List[Finding] = []
218
+ for package in packages:
219
+ if not package.package_name.lower().startswith("onnxruntime"):
220
+ continue
221
+
222
+ issues = check_onnx_runtime_providers(package, profile)
223
+ for issue in issues:
224
+ sev_map = {
225
+ "critical": Severity.CRITICAL,
226
+ "warning": Severity.WARNING,
227
+ "info": Severity.INFO,
228
+ }
229
+ severity = sev_map.get(
230
+ issue.get("severity", "warning"), Severity.WARNING
231
+ )
232
+ findings.append(
233
+ Finding(
234
+ rule_id=self.rule_id,
235
+ severity=severity,
236
+ title=f"ONNX Runtime: {issue['issue'].replace('_', ' ').title()}",
237
+ explanation=issue["message"],
238
+ package=package.package_name,
239
+ suggestion=issue.get("recommendation", ""),
240
+ confidence=0.85,
241
+ )
242
+ )
243
+ return findings
244
+
245
+
246
+ __all__ = [
247
+ "PYTORCH_CUDA_ABI_MISMATCH",
248
+ "PYTORCH_TORCHVISION_INCOMPATIBLE",
249
+ "TENSORFLOW_COMPUTE_CAPABILITY_LOW",
250
+ "TENSORRT_INCOMPATIBLE",
251
+ "ONNX_RUNTIME_PROVIDER_MISMATCH",
252
+ ]