raxe 0.4.6__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 (668) hide show
  1. raxe/__init__.py +101 -0
  2. raxe/application/__init__.py +48 -0
  3. raxe/application/ab_testing.py +170 -0
  4. raxe/application/analytics/__init__.py +30 -0
  5. raxe/application/analytics/achievement_service.py +444 -0
  6. raxe/application/analytics/repositories.py +172 -0
  7. raxe/application/analytics/retention_service.py +267 -0
  8. raxe/application/analytics/statistics_service.py +419 -0
  9. raxe/application/analytics/streak_service.py +283 -0
  10. raxe/application/apply_policy.py +291 -0
  11. raxe/application/eager_l2.py +503 -0
  12. raxe/application/preloader.py +353 -0
  13. raxe/application/scan_merger.py +321 -0
  14. raxe/application/scan_pipeline.py +1059 -0
  15. raxe/application/scan_pipeline_async.py +403 -0
  16. raxe/application/session_tracker.py +458 -0
  17. raxe/application/telemetry_manager.py +357 -0
  18. raxe/application/telemetry_orchestrator.py +1210 -0
  19. raxe/async_sdk/__init__.py +34 -0
  20. raxe/async_sdk/cache.py +286 -0
  21. raxe/async_sdk/client.py +556 -0
  22. raxe/async_sdk/wrappers/__init__.py +23 -0
  23. raxe/async_sdk/wrappers/openai.py +238 -0
  24. raxe/cli/__init__.py +21 -0
  25. raxe/cli/auth.py +1047 -0
  26. raxe/cli/branding.py +235 -0
  27. raxe/cli/config.py +334 -0
  28. raxe/cli/custom_rules.py +458 -0
  29. raxe/cli/doctor.py +686 -0
  30. raxe/cli/error_handler.py +665 -0
  31. raxe/cli/event.py +648 -0
  32. raxe/cli/exit_codes.py +57 -0
  33. raxe/cli/expiry_warning.py +302 -0
  34. raxe/cli/export.py +183 -0
  35. raxe/cli/history.py +247 -0
  36. raxe/cli/l2_formatter.py +872 -0
  37. raxe/cli/main.py +1137 -0
  38. raxe/cli/models.py +590 -0
  39. raxe/cli/output.py +403 -0
  40. raxe/cli/privacy.py +84 -0
  41. raxe/cli/profiler.py +262 -0
  42. raxe/cli/progress.py +379 -0
  43. raxe/cli/progress_context.py +101 -0
  44. raxe/cli/repl.py +394 -0
  45. raxe/cli/rules.py +542 -0
  46. raxe/cli/setup_wizard.py +721 -0
  47. raxe/cli/stats.py +292 -0
  48. raxe/cli/suppress.py +501 -0
  49. raxe/cli/telemetry.py +1384 -0
  50. raxe/cli/test.py +130 -0
  51. raxe/cli/tune.py +315 -0
  52. raxe/cli/validate.py +218 -0
  53. raxe/domain/__init__.py +30 -0
  54. raxe/domain/analytics/__init__.py +97 -0
  55. raxe/domain/analytics/achievements.py +306 -0
  56. raxe/domain/analytics/models.py +120 -0
  57. raxe/domain/analytics/retention.py +168 -0
  58. raxe/domain/analytics/statistics.py +207 -0
  59. raxe/domain/analytics/streaks.py +173 -0
  60. raxe/domain/engine/__init__.py +15 -0
  61. raxe/domain/engine/executor.py +396 -0
  62. raxe/domain/engine/matcher.py +212 -0
  63. raxe/domain/inline_suppression.py +176 -0
  64. raxe/domain/ml/__init__.py +133 -0
  65. raxe/domain/ml/embedding_cache.py +309 -0
  66. raxe/domain/ml/gemma_detector.py +921 -0
  67. raxe/domain/ml/gemma_models.py +346 -0
  68. raxe/domain/ml/l2_config.py +428 -0
  69. raxe/domain/ml/l2_output_schema.py +443 -0
  70. raxe/domain/ml/manifest_loader.py +309 -0
  71. raxe/domain/ml/manifest_schema.py +345 -0
  72. raxe/domain/ml/model_metadata.py +263 -0
  73. raxe/domain/ml/model_registry.py +786 -0
  74. raxe/domain/ml/protocol.py +282 -0
  75. raxe/domain/ml/scoring_models.py +419 -0
  76. raxe/domain/ml/stub_detector.py +397 -0
  77. raxe/domain/ml/threat_scorer.py +757 -0
  78. raxe/domain/ml/tokenizer_registry.py +372 -0
  79. raxe/domain/ml/voting/__init__.py +89 -0
  80. raxe/domain/ml/voting/config.py +595 -0
  81. raxe/domain/ml/voting/engine.py +465 -0
  82. raxe/domain/ml/voting/head_voters.py +378 -0
  83. raxe/domain/ml/voting/models.py +222 -0
  84. raxe/domain/models.py +82 -0
  85. raxe/domain/packs/__init__.py +17 -0
  86. raxe/domain/packs/models.py +304 -0
  87. raxe/domain/policies/__init__.py +20 -0
  88. raxe/domain/policies/evaluator.py +212 -0
  89. raxe/domain/policies/models.py +223 -0
  90. raxe/domain/rules/__init__.py +32 -0
  91. raxe/domain/rules/custom.py +286 -0
  92. raxe/domain/rules/models.py +273 -0
  93. raxe/domain/rules/schema.py +166 -0
  94. raxe/domain/rules/validator.py +556 -0
  95. raxe/domain/suppression.py +801 -0
  96. raxe/domain/suppression_factory.py +174 -0
  97. raxe/domain/telemetry/__init__.py +116 -0
  98. raxe/domain/telemetry/backpressure.py +424 -0
  99. raxe/domain/telemetry/event_creator.py +362 -0
  100. raxe/domain/telemetry/events.py +1282 -0
  101. raxe/domain/telemetry/priority.py +263 -0
  102. raxe/domain/telemetry/scan_telemetry_builder.py +670 -0
  103. raxe/infrastructure/__init__.py +25 -0
  104. raxe/infrastructure/analytics/__init__.py +18 -0
  105. raxe/infrastructure/analytics/aggregator.py +484 -0
  106. raxe/infrastructure/analytics/aggregator_optimized.py +184 -0
  107. raxe/infrastructure/analytics/engine.py +748 -0
  108. raxe/infrastructure/analytics/repository.py +409 -0
  109. raxe/infrastructure/analytics/streaks.py +467 -0
  110. raxe/infrastructure/analytics/views.py +178 -0
  111. raxe/infrastructure/cloud/__init__.py +9 -0
  112. raxe/infrastructure/config/__init__.py +56 -0
  113. raxe/infrastructure/config/endpoints.py +641 -0
  114. raxe/infrastructure/config/scan_config.py +352 -0
  115. raxe/infrastructure/config/yaml_config.py +459 -0
  116. raxe/infrastructure/database/__init__.py +10 -0
  117. raxe/infrastructure/database/connection.py +200 -0
  118. raxe/infrastructure/database/models.py +325 -0
  119. raxe/infrastructure/database/scan_history.py +764 -0
  120. raxe/infrastructure/ml/__init__.py +0 -0
  121. raxe/infrastructure/ml/download_progress.py +438 -0
  122. raxe/infrastructure/ml/model_downloader.py +457 -0
  123. raxe/infrastructure/models/__init__.py +16 -0
  124. raxe/infrastructure/models/discovery.py +461 -0
  125. raxe/infrastructure/packs/__init__.py +13 -0
  126. raxe/infrastructure/packs/loader.py +407 -0
  127. raxe/infrastructure/packs/registry.py +381 -0
  128. raxe/infrastructure/policies/__init__.py +16 -0
  129. raxe/infrastructure/policies/api_client.py +256 -0
  130. raxe/infrastructure/policies/validator.py +227 -0
  131. raxe/infrastructure/policies/yaml_loader.py +250 -0
  132. raxe/infrastructure/rules/__init__.py +18 -0
  133. raxe/infrastructure/rules/custom_loader.py +224 -0
  134. raxe/infrastructure/rules/versioning.py +222 -0
  135. raxe/infrastructure/rules/yaml_loader.py +286 -0
  136. raxe/infrastructure/security/__init__.py +31 -0
  137. raxe/infrastructure/security/auth.py +145 -0
  138. raxe/infrastructure/security/policy_validator.py +124 -0
  139. raxe/infrastructure/security/signatures.py +171 -0
  140. raxe/infrastructure/suppression/__init__.py +36 -0
  141. raxe/infrastructure/suppression/composite_repository.py +154 -0
  142. raxe/infrastructure/suppression/sqlite_repository.py +231 -0
  143. raxe/infrastructure/suppression/yaml_composite_repository.py +156 -0
  144. raxe/infrastructure/suppression/yaml_repository.py +510 -0
  145. raxe/infrastructure/telemetry/__init__.py +79 -0
  146. raxe/infrastructure/telemetry/acquisition.py +179 -0
  147. raxe/infrastructure/telemetry/config.py +254 -0
  148. raxe/infrastructure/telemetry/credential_store.py +947 -0
  149. raxe/infrastructure/telemetry/dual_queue.py +1123 -0
  150. raxe/infrastructure/telemetry/flush_helper.py +343 -0
  151. raxe/infrastructure/telemetry/flush_scheduler.py +776 -0
  152. raxe/infrastructure/telemetry/health_client.py +394 -0
  153. raxe/infrastructure/telemetry/hook.py +347 -0
  154. raxe/infrastructure/telemetry/queue.py +520 -0
  155. raxe/infrastructure/telemetry/sender.py +476 -0
  156. raxe/infrastructure/tracking/__init__.py +13 -0
  157. raxe/infrastructure/tracking/usage.py +389 -0
  158. raxe/integrations/__init__.py +55 -0
  159. raxe/integrations/availability.py +143 -0
  160. raxe/integrations/registry.py +122 -0
  161. raxe/integrations/utils.py +135 -0
  162. raxe/mcp/__init__.py +62 -0
  163. raxe/mcp/cli.py +97 -0
  164. raxe/mcp/server.py +409 -0
  165. raxe/monitoring/__init__.py +51 -0
  166. raxe/monitoring/metrics.py +372 -0
  167. raxe/monitoring/profiler.py +388 -0
  168. raxe/monitoring/server.py +136 -0
  169. raxe/packs/core/v1.0.0/pack.yaml +1394 -0
  170. raxe/packs/core/v1.0.0/rules/PI/pi-001@1.0.0.yaml +49 -0
  171. raxe/packs/core/v1.0.0/rules/PI/pi-006@1.0.0.yaml +48 -0
  172. raxe/packs/core/v1.0.0/rules/PI/pi-014@1.0.0.yaml +54 -0
  173. raxe/packs/core/v1.0.0/rules/PI/pi-017@1.0.0.yaml +52 -0
  174. raxe/packs/core/v1.0.0/rules/PI/pi-022@1.0.0.yaml +67 -0
  175. raxe/packs/core/v1.0.0/rules/PI/pi-023@1.0.0.yaml +91 -0
  176. raxe/packs/core/v1.0.0/rules/PI/pi-024@1.0.0.yaml +80 -0
  177. raxe/packs/core/v1.0.0/rules/PI/pi-025@1.0.0.yaml +81 -0
  178. raxe/packs/core/v1.0.0/rules/PI/pi-026@1.0.0.yaml +50 -0
  179. raxe/packs/core/v1.0.0/rules/PI/pi-027@1.0.0.yaml +77 -0
  180. raxe/packs/core/v1.0.0/rules/PI/pi-028@1.0.0.yaml +52 -0
  181. raxe/packs/core/v1.0.0/rules/PI/pi-029@1.0.0.yaml +51 -0
  182. raxe/packs/core/v1.0.0/rules/PI/pi-030@1.0.0.yaml +55 -0
  183. raxe/packs/core/v1.0.0/rules/PI/pi-033@1.0.0.yaml +50 -0
  184. raxe/packs/core/v1.0.0/rules/PI/pi-034@1.0.0.yaml +50 -0
  185. raxe/packs/core/v1.0.0/rules/PI/pi-035@1.0.0.yaml +50 -0
  186. raxe/packs/core/v1.0.0/rules/PI/pi-046@1.0.0.yaml +50 -0
  187. raxe/packs/core/v1.0.0/rules/PI/pi-047@1.0.0.yaml +50 -0
  188. raxe/packs/core/v1.0.0/rules/PI/pi-048@1.0.0.yaml +50 -0
  189. raxe/packs/core/v1.0.0/rules/PI/pi-049@1.0.0.yaml +50 -0
  190. raxe/packs/core/v1.0.0/rules/PI/pi-050@1.0.0.yaml +50 -0
  191. raxe/packs/core/v1.0.0/rules/PI/pi-068@1.0.0.yaml +50 -0
  192. raxe/packs/core/v1.0.0/rules/PI/pi-078@1.0.0.yaml +50 -0
  193. raxe/packs/core/v1.0.0/rules/PI/pi-2001@1.0.0.yaml +35 -0
  194. raxe/packs/core/v1.0.0/rules/PI/pi-2004@1.0.0.yaml +39 -0
  195. raxe/packs/core/v1.0.0/rules/PI/pi-201@1.0.0.yaml +43 -0
  196. raxe/packs/core/v1.0.0/rules/PI/pi-202@1.0.0.yaml +47 -0
  197. raxe/packs/core/v1.0.0/rules/PI/pi-203@1.0.0.yaml +46 -0
  198. raxe/packs/core/v1.0.0/rules/PI/pi-3007@1.0.0.yaml +44 -0
  199. raxe/packs/core/v1.0.0/rules/PI/pi-3016@1.0.0.yaml +44 -0
  200. raxe/packs/core/v1.0.0/rules/PI/pi-3026@1.0.0.yaml +39 -0
  201. raxe/packs/core/v1.0.0/rules/PI/pi-3027@1.0.0.yaml +64 -0
  202. raxe/packs/core/v1.0.0/rules/PI/pi-3028@1.0.0.yaml +51 -0
  203. raxe/packs/core/v1.0.0/rules/PI/pi-3029@1.0.0.yaml +53 -0
  204. raxe/packs/core/v1.0.0/rules/PI/pi-3030@1.0.0.yaml +50 -0
  205. raxe/packs/core/v1.0.0/rules/PI/pi-3031@1.0.0.yaml +50 -0
  206. raxe/packs/core/v1.0.0/rules/PI/pi-3032@1.0.0.yaml +50 -0
  207. raxe/packs/core/v1.0.0/rules/PI/pi-3033@1.0.0.yaml +56 -0
  208. raxe/packs/core/v1.0.0/rules/PI/pi-3034@1.0.0.yaml +50 -0
  209. raxe/packs/core/v1.0.0/rules/PI/pi-79@1.0.0.yaml +38 -0
  210. raxe/packs/core/v1.0.0/rules/PI/pi-80@1.0.0.yaml +38 -0
  211. raxe/packs/core/v1.0.0/rules/PI/pi-81@1.0.0.yaml +38 -0
  212. raxe/packs/core/v1.0.0/rules/PI/pi-82@1.0.0.yaml +38 -0
  213. raxe/packs/core/v1.0.0/rules/PI/pi-83@1.0.0.yaml +38 -0
  214. raxe/packs/core/v1.0.0/rules/PI/pi-84@1.0.0.yaml +38 -0
  215. raxe/packs/core/v1.0.0/rules/PI/pi-85@1.0.0.yaml +38 -0
  216. raxe/packs/core/v1.0.0/rules/PI/pi-86@1.0.0.yaml +38 -0
  217. raxe/packs/core/v1.0.0/rules/PI/pi-87@1.0.0.yaml +38 -0
  218. raxe/packs/core/v1.0.0/rules/PI/pi-88@1.0.0.yaml +38 -0
  219. raxe/packs/core/v1.0.0/rules/PI/pi-89@1.0.0.yaml +38 -0
  220. raxe/packs/core/v1.0.0/rules/PI/pi-90@1.0.0.yaml +38 -0
  221. raxe/packs/core/v1.0.0/rules/PI/pi-91@1.0.0.yaml +38 -0
  222. raxe/packs/core/v1.0.0/rules/PI/pi-92@1.0.0.yaml +38 -0
  223. raxe/packs/core/v1.0.0/rules/PI/pi-93@1.0.0.yaml +38 -0
  224. raxe/packs/core/v1.0.0/rules/PI/pi-94@1.0.0.yaml +38 -0
  225. raxe/packs/core/v1.0.0/rules/PI/pi-95@1.0.0.yaml +38 -0
  226. raxe/packs/core/v1.0.0/rules/PI/pi-96@1.0.0.yaml +38 -0
  227. raxe/packs/core/v1.0.0/rules/PI/pi-97@1.0.0.yaml +38 -0
  228. raxe/packs/core/v1.0.0/rules/PI/pi-98@1.0.0.yaml +38 -0
  229. raxe/packs/core/v1.0.0/rules/cmd/cmd-001@1.0.0.yaml +48 -0
  230. raxe/packs/core/v1.0.0/rules/cmd/cmd-007@1.0.0.yaml +48 -0
  231. raxe/packs/core/v1.0.0/rules/cmd/cmd-015@1.0.0.yaml +56 -0
  232. raxe/packs/core/v1.0.0/rules/cmd/cmd-016@1.0.0.yaml +46 -0
  233. raxe/packs/core/v1.0.0/rules/cmd/cmd-017@1.0.0.yaml +57 -0
  234. raxe/packs/core/v1.0.0/rules/cmd/cmd-021@1.0.0.yaml +46 -0
  235. raxe/packs/core/v1.0.0/rules/cmd/cmd-022@1.0.0.yaml +46 -0
  236. raxe/packs/core/v1.0.0/rules/cmd/cmd-023@1.0.0.yaml +78 -0
  237. raxe/packs/core/v1.0.0/rules/cmd/cmd-024@1.0.0.yaml +46 -0
  238. raxe/packs/core/v1.0.0/rules/cmd/cmd-025@1.0.0.yaml +93 -0
  239. raxe/packs/core/v1.0.0/rules/cmd/cmd-026@1.0.0.yaml +81 -0
  240. raxe/packs/core/v1.0.0/rules/cmd/cmd-027@1.0.0.yaml +82 -0
  241. raxe/packs/core/v1.0.0/rules/cmd/cmd-028@1.0.0.yaml +46 -0
  242. raxe/packs/core/v1.0.0/rules/cmd/cmd-033@1.0.0.yaml +48 -0
  243. raxe/packs/core/v1.0.0/rules/cmd/cmd-036@1.0.0.yaml +47 -0
  244. raxe/packs/core/v1.0.0/rules/cmd/cmd-037@1.0.0.yaml +44 -0
  245. raxe/packs/core/v1.0.0/rules/cmd/cmd-052@1.0.0.yaml +43 -0
  246. raxe/packs/core/v1.0.0/rules/cmd/cmd-054@1.0.0.yaml +44 -0
  247. raxe/packs/core/v1.0.0/rules/cmd/cmd-056@1.0.0.yaml +43 -0
  248. raxe/packs/core/v1.0.0/rules/cmd/cmd-065@1.0.0.yaml +46 -0
  249. raxe/packs/core/v1.0.0/rules/cmd/cmd-075@1.0.0.yaml +45 -0
  250. raxe/packs/core/v1.0.0/rules/cmd/cmd-079@1.0.0.yaml +44 -0
  251. raxe/packs/core/v1.0.0/rules/cmd/cmd-1080@1.0.0.yaml +41 -0
  252. raxe/packs/core/v1.0.0/rules/cmd/cmd-1090@1.0.0.yaml +41 -0
  253. raxe/packs/core/v1.0.0/rules/cmd/cmd-1104@1.0.0.yaml +44 -0
  254. raxe/packs/core/v1.0.0/rules/cmd/cmd-1105@1.0.0.yaml +41 -0
  255. raxe/packs/core/v1.0.0/rules/cmd/cmd-1112@1.0.0.yaml +44 -0
  256. raxe/packs/core/v1.0.0/rules/cmd/cmd-201@1.0.0.yaml +47 -0
  257. raxe/packs/core/v1.0.0/rules/cmd/cmd-202@1.0.0.yaml +42 -0
  258. raxe/packs/core/v1.0.0/rules/cmd/cmd-203@1.0.0.yaml +43 -0
  259. raxe/packs/core/v1.0.0/rules/cmd/cmd-204@1.0.0.yaml +47 -0
  260. raxe/packs/core/v1.0.0/rules/cmd/cmd-205@1.0.0.yaml +44 -0
  261. raxe/packs/core/v1.0.0/rules/cmd/cmd-206@1.0.0.yaml +47 -0
  262. raxe/packs/core/v1.0.0/rules/cmd/cmd-207@1.0.0.yaml +46 -0
  263. raxe/packs/core/v1.0.0/rules/cmd/cmd-208@1.0.0.yaml +42 -0
  264. raxe/packs/core/v1.0.0/rules/cmd/cmd-209@1.0.0.yaml +38 -0
  265. raxe/packs/core/v1.0.0/rules/cmd/cmd-210@1.0.0.yaml +38 -0
  266. raxe/packs/core/v1.0.0/rules/cmd/cmd-211@1.0.0.yaml +38 -0
  267. raxe/packs/core/v1.0.0/rules/cmd/cmd-212@1.0.0.yaml +38 -0
  268. raxe/packs/core/v1.0.0/rules/cmd/cmd-213@1.0.0.yaml +38 -0
  269. raxe/packs/core/v1.0.0/rules/cmd/cmd-214@1.0.0.yaml +38 -0
  270. raxe/packs/core/v1.0.0/rules/cmd/cmd-215@1.0.0.yaml +38 -0
  271. raxe/packs/core/v1.0.0/rules/cmd/cmd-216@1.0.0.yaml +38 -0
  272. raxe/packs/core/v1.0.0/rules/cmd/cmd-217@1.0.0.yaml +38 -0
  273. raxe/packs/core/v1.0.0/rules/cmd/cmd-218@1.0.0.yaml +38 -0
  274. raxe/packs/core/v1.0.0/rules/cmd/cmd-219@1.0.0.yaml +38 -0
  275. raxe/packs/core/v1.0.0/rules/cmd/cmd-220@1.0.0.yaml +38 -0
  276. raxe/packs/core/v1.0.0/rules/cmd/cmd-221@1.0.0.yaml +38 -0
  277. raxe/packs/core/v1.0.0/rules/cmd/cmd-222@1.0.0.yaml +38 -0
  278. raxe/packs/core/v1.0.0/rules/cmd/cmd-223@1.0.0.yaml +38 -0
  279. raxe/packs/core/v1.0.0/rules/cmd/cmd-224@1.0.0.yaml +38 -0
  280. raxe/packs/core/v1.0.0/rules/cmd/cmd-225@1.0.0.yaml +38 -0
  281. raxe/packs/core/v1.0.0/rules/cmd/cmd-226@1.0.0.yaml +38 -0
  282. raxe/packs/core/v1.0.0/rules/cmd/cmd-227@1.0.0.yaml +38 -0
  283. raxe/packs/core/v1.0.0/rules/cmd/cmd-228@1.0.0.yaml +38 -0
  284. raxe/packs/core/v1.0.0/rules/cmd/cmd-229@1.0.0.yaml +38 -0
  285. raxe/packs/core/v1.0.0/rules/cmd/cmd-230@1.0.0.yaml +38 -0
  286. raxe/packs/core/v1.0.0/rules/cmd/cmd-231@1.0.0.yaml +38 -0
  287. raxe/packs/core/v1.0.0/rules/cmd/cmd-232@1.0.0.yaml +38 -0
  288. raxe/packs/core/v1.0.0/rules/cmd/cmd-233@1.0.0.yaml +38 -0
  289. raxe/packs/core/v1.0.0/rules/cmd/cmd-234@1.0.0.yaml +38 -0
  290. raxe/packs/core/v1.0.0/rules/cmd/cmd-235@1.0.0.yaml +38 -0
  291. raxe/packs/core/v1.0.0/rules/cmd/cmd-236@1.0.0.yaml +38 -0
  292. raxe/packs/core/v1.0.0/rules/cmd/cmd-237@1.0.0.yaml +38 -0
  293. raxe/packs/core/v1.0.0/rules/cmd/cmd-238@1.0.0.yaml +38 -0
  294. raxe/packs/core/v1.0.0/rules/enc/enc-001@1.0.0.yaml +48 -0
  295. raxe/packs/core/v1.0.0/rules/enc/enc-013@1.0.0.yaml +46 -0
  296. raxe/packs/core/v1.0.0/rules/enc/enc-019@1.0.0.yaml +43 -0
  297. raxe/packs/core/v1.0.0/rules/enc/enc-020@1.0.0.yaml +47 -0
  298. raxe/packs/core/v1.0.0/rules/enc/enc-024@1.0.0.yaml +46 -0
  299. raxe/packs/core/v1.0.0/rules/enc/enc-029@1.0.0.yaml +44 -0
  300. raxe/packs/core/v1.0.0/rules/enc/enc-038@1.0.0.yaml +44 -0
  301. raxe/packs/core/v1.0.0/rules/enc/enc-044@1.0.0.yaml +46 -0
  302. raxe/packs/core/v1.0.0/rules/enc/enc-067@1.0.0.yaml +42 -0
  303. raxe/packs/core/v1.0.0/rules/enc/enc-069@1.0.0.yaml +42 -0
  304. raxe/packs/core/v1.0.0/rules/enc/enc-100@1.0.0.yaml +38 -0
  305. raxe/packs/core/v1.0.0/rules/enc/enc-101@1.0.0.yaml +38 -0
  306. raxe/packs/core/v1.0.0/rules/enc/enc-102@1.0.0.yaml +38 -0
  307. raxe/packs/core/v1.0.0/rules/enc/enc-103@1.0.0.yaml +38 -0
  308. raxe/packs/core/v1.0.0/rules/enc/enc-104@1.0.0.yaml +38 -0
  309. raxe/packs/core/v1.0.0/rules/enc/enc-105@1.0.0.yaml +38 -0
  310. raxe/packs/core/v1.0.0/rules/enc/enc-106@1.0.0.yaml +38 -0
  311. raxe/packs/core/v1.0.0/rules/enc/enc-107@1.0.0.yaml +38 -0
  312. raxe/packs/core/v1.0.0/rules/enc/enc-108@1.0.0.yaml +38 -0
  313. raxe/packs/core/v1.0.0/rules/enc/enc-109@1.0.0.yaml +38 -0
  314. raxe/packs/core/v1.0.0/rules/enc/enc-110@1.0.0.yaml +38 -0
  315. raxe/packs/core/v1.0.0/rules/enc/enc-111@1.0.0.yaml +38 -0
  316. raxe/packs/core/v1.0.0/rules/enc/enc-112@1.0.0.yaml +38 -0
  317. raxe/packs/core/v1.0.0/rules/enc/enc-113@1.0.0.yaml +38 -0
  318. raxe/packs/core/v1.0.0/rules/enc/enc-114@1.0.0.yaml +38 -0
  319. raxe/packs/core/v1.0.0/rules/enc/enc-115@1.0.0.yaml +38 -0
  320. raxe/packs/core/v1.0.0/rules/enc/enc-116@1.0.0.yaml +38 -0
  321. raxe/packs/core/v1.0.0/rules/enc/enc-117@1.0.0.yaml +38 -0
  322. raxe/packs/core/v1.0.0/rules/enc/enc-118@1.0.0.yaml +38 -0
  323. raxe/packs/core/v1.0.0/rules/enc/enc-119@1.0.0.yaml +38 -0
  324. raxe/packs/core/v1.0.0/rules/enc/enc-120@1.0.0.yaml +38 -0
  325. raxe/packs/core/v1.0.0/rules/enc/enc-201@1.0.0.yaml +37 -0
  326. raxe/packs/core/v1.0.0/rules/enc/enc-202@1.0.0.yaml +41 -0
  327. raxe/packs/core/v1.0.0/rules/enc/enc-203@1.0.0.yaml +41 -0
  328. raxe/packs/core/v1.0.0/rules/enc/enc-3004@1.0.0.yaml +40 -0
  329. raxe/packs/core/v1.0.0/rules/enc/enc-3006@1.0.0.yaml +40 -0
  330. raxe/packs/core/v1.0.0/rules/enc/enc-3011@1.0.0.yaml +40 -0
  331. raxe/packs/core/v1.0.0/rules/enc/enc-5016@1.0.0.yaml +46 -0
  332. raxe/packs/core/v1.0.0/rules/enc/enc-6001@1.0.0.yaml +53 -0
  333. raxe/packs/core/v1.0.0/rules/enc/enc-6002@1.0.0.yaml +41 -0
  334. raxe/packs/core/v1.0.0/rules/enc/enc-70@1.0.0.yaml +38 -0
  335. raxe/packs/core/v1.0.0/rules/enc/enc-71@1.0.0.yaml +38 -0
  336. raxe/packs/core/v1.0.0/rules/enc/enc-72@1.0.0.yaml +38 -0
  337. raxe/packs/core/v1.0.0/rules/enc/enc-73@1.0.0.yaml +38 -0
  338. raxe/packs/core/v1.0.0/rules/enc/enc-74@1.0.0.yaml +38 -0
  339. raxe/packs/core/v1.0.0/rules/enc/enc-75@1.0.0.yaml +38 -0
  340. raxe/packs/core/v1.0.0/rules/enc/enc-76@1.0.0.yaml +38 -0
  341. raxe/packs/core/v1.0.0/rules/enc/enc-77@1.0.0.yaml +38 -0
  342. raxe/packs/core/v1.0.0/rules/enc/enc-78@1.0.0.yaml +38 -0
  343. raxe/packs/core/v1.0.0/rules/enc/enc-79@1.0.0.yaml +38 -0
  344. raxe/packs/core/v1.0.0/rules/enc/enc-80@1.0.0.yaml +38 -0
  345. raxe/packs/core/v1.0.0/rules/enc/enc-81@1.0.0.yaml +38 -0
  346. raxe/packs/core/v1.0.0/rules/enc/enc-82@1.0.0.yaml +38 -0
  347. raxe/packs/core/v1.0.0/rules/enc/enc-83@1.0.0.yaml +38 -0
  348. raxe/packs/core/v1.0.0/rules/enc/enc-84@1.0.0.yaml +38 -0
  349. raxe/packs/core/v1.0.0/rules/enc/enc-85@1.0.0.yaml +38 -0
  350. raxe/packs/core/v1.0.0/rules/enc/enc-86@1.0.0.yaml +38 -0
  351. raxe/packs/core/v1.0.0/rules/enc/enc-87@1.0.0.yaml +38 -0
  352. raxe/packs/core/v1.0.0/rules/enc/enc-88@1.0.0.yaml +38 -0
  353. raxe/packs/core/v1.0.0/rules/enc/enc-89@1.0.0.yaml +38 -0
  354. raxe/packs/core/v1.0.0/rules/enc/enc-90@1.0.0.yaml +38 -0
  355. raxe/packs/core/v1.0.0/rules/enc/enc-91@1.0.0.yaml +38 -0
  356. raxe/packs/core/v1.0.0/rules/enc/enc-92@1.0.0.yaml +38 -0
  357. raxe/packs/core/v1.0.0/rules/enc/enc-93@1.0.0.yaml +38 -0
  358. raxe/packs/core/v1.0.0/rules/enc/enc-94@1.0.0.yaml +38 -0
  359. raxe/packs/core/v1.0.0/rules/enc/enc-95@1.0.0.yaml +38 -0
  360. raxe/packs/core/v1.0.0/rules/enc/enc-96@1.0.0.yaml +38 -0
  361. raxe/packs/core/v1.0.0/rules/enc/enc-97@1.0.0.yaml +38 -0
  362. raxe/packs/core/v1.0.0/rules/enc/enc-98@1.0.0.yaml +38 -0
  363. raxe/packs/core/v1.0.0/rules/enc/enc-99@1.0.0.yaml +38 -0
  364. raxe/packs/core/v1.0.0/rules/hc/hc-001@1.0.0.yaml +73 -0
  365. raxe/packs/core/v1.0.0/rules/hc/hc-002@1.0.0.yaml +71 -0
  366. raxe/packs/core/v1.0.0/rules/hc/hc-003@1.0.0.yaml +65 -0
  367. raxe/packs/core/v1.0.0/rules/hc/hc-004@1.0.0.yaml +73 -0
  368. raxe/packs/core/v1.0.0/rules/hc/hc-101@1.0.0.yaml +47 -0
  369. raxe/packs/core/v1.0.0/rules/hc/hc-102@1.0.0.yaml +47 -0
  370. raxe/packs/core/v1.0.0/rules/hc/hc-103@1.0.0.yaml +47 -0
  371. raxe/packs/core/v1.0.0/rules/hc/hc-104@1.0.0.yaml +47 -0
  372. raxe/packs/core/v1.0.0/rules/hc/hc-105@1.0.0.yaml +48 -0
  373. raxe/packs/core/v1.0.0/rules/hc/hc-106@1.0.0.yaml +40 -0
  374. raxe/packs/core/v1.0.0/rules/hc/hc-107@1.0.0.yaml +47 -0
  375. raxe/packs/core/v1.0.0/rules/hc/hc-108@1.0.0.yaml +47 -0
  376. raxe/packs/core/v1.0.0/rules/hc/hc-109@1.0.0.yaml +50 -0
  377. raxe/packs/core/v1.0.0/rules/hc/hc-110@1.0.0.yaml +56 -0
  378. raxe/packs/core/v1.0.0/rules/hc/hc-111@1.0.0.yaml +49 -0
  379. raxe/packs/core/v1.0.0/rules/hc/hc-112@1.0.0.yaml +53 -0
  380. raxe/packs/core/v1.0.0/rules/hc/hc-113@1.0.0.yaml +52 -0
  381. raxe/packs/core/v1.0.0/rules/hc/hc-114@1.0.0.yaml +52 -0
  382. raxe/packs/core/v1.0.0/rules/hc/hc-115@1.0.0.yaml +52 -0
  383. raxe/packs/core/v1.0.0/rules/hc/hc-116@1.0.0.yaml +53 -0
  384. raxe/packs/core/v1.0.0/rules/hc/hc-117@1.0.0.yaml +54 -0
  385. raxe/packs/core/v1.0.0/rules/hc/hc-118@1.0.0.yaml +52 -0
  386. raxe/packs/core/v1.0.0/rules/hc/hc-119@1.0.0.yaml +51 -0
  387. raxe/packs/core/v1.0.0/rules/hc/hc-120@1.0.0.yaml +52 -0
  388. raxe/packs/core/v1.0.0/rules/hc/hc-121@1.0.0.yaml +51 -0
  389. raxe/packs/core/v1.0.0/rules/hc/hc-122@1.0.0.yaml +51 -0
  390. raxe/packs/core/v1.0.0/rules/hc/hc-123@1.0.0.yaml +52 -0
  391. raxe/packs/core/v1.0.0/rules/hc/hc-124@1.0.0.yaml +53 -0
  392. raxe/packs/core/v1.0.0/rules/hc/hc-125@1.0.0.yaml +53 -0
  393. raxe/packs/core/v1.0.0/rules/hc/hc-126@1.0.0.yaml +53 -0
  394. raxe/packs/core/v1.0.0/rules/hc/hc-127@1.0.0.yaml +53 -0
  395. raxe/packs/core/v1.0.0/rules/hc/hc-128@1.0.0.yaml +53 -0
  396. raxe/packs/core/v1.0.0/rules/hc/hc-129@1.0.0.yaml +51 -0
  397. raxe/packs/core/v1.0.0/rules/hc/hc-130@1.0.0.yaml +51 -0
  398. raxe/packs/core/v1.0.0/rules/hc/hc-131@1.0.0.yaml +51 -0
  399. raxe/packs/core/v1.0.0/rules/hc/hc-132@1.0.0.yaml +51 -0
  400. raxe/packs/core/v1.0.0/rules/hc/hc-133@1.0.0.yaml +53 -0
  401. raxe/packs/core/v1.0.0/rules/hc/hc-134@1.0.0.yaml +51 -0
  402. raxe/packs/core/v1.0.0/rules/hc/hc-135@1.0.0.yaml +51 -0
  403. raxe/packs/core/v1.0.0/rules/hc/hc-136@1.0.0.yaml +51 -0
  404. raxe/packs/core/v1.0.0/rules/hc/hc-137@1.0.0.yaml +51 -0
  405. raxe/packs/core/v1.0.0/rules/hc/hc-138@1.0.0.yaml +51 -0
  406. raxe/packs/core/v1.0.0/rules/hc/hc-139@1.0.0.yaml +51 -0
  407. raxe/packs/core/v1.0.0/rules/hc/hc-140@1.0.0.yaml +51 -0
  408. raxe/packs/core/v1.0.0/rules/hc/hc-141@1.0.0.yaml +41 -0
  409. raxe/packs/core/v1.0.0/rules/hc/hc-142@1.0.0.yaml +37 -0
  410. raxe/packs/core/v1.0.0/rules/hc/hc-143@1.0.0.yaml +37 -0
  411. raxe/packs/core/v1.0.0/rules/hc/hc-144@1.0.0.yaml +37 -0
  412. raxe/packs/core/v1.0.0/rules/hc/hc-145@1.0.0.yaml +37 -0
  413. raxe/packs/core/v1.0.0/rules/hc/hc-146@1.0.0.yaml +37 -0
  414. raxe/packs/core/v1.0.0/rules/hc/hc-147@1.0.0.yaml +37 -0
  415. raxe/packs/core/v1.0.0/rules/hc/hc-148@1.0.0.yaml +37 -0
  416. raxe/packs/core/v1.0.0/rules/hc/hc-149@1.0.0.yaml +37 -0
  417. raxe/packs/core/v1.0.0/rules/hc/hc-150@1.0.0.yaml +37 -0
  418. raxe/packs/core/v1.0.0/rules/hc/hc-151@1.0.0.yaml +37 -0
  419. raxe/packs/core/v1.0.0/rules/hc/hc-152@1.0.0.yaml +37 -0
  420. raxe/packs/core/v1.0.0/rules/hc/hc-153@1.0.0.yaml +37 -0
  421. raxe/packs/core/v1.0.0/rules/hc/hc-154@1.0.0.yaml +37 -0
  422. raxe/packs/core/v1.0.0/rules/hc/hc-155@1.0.0.yaml +37 -0
  423. raxe/packs/core/v1.0.0/rules/hc/hc-156@1.0.0.yaml +37 -0
  424. raxe/packs/core/v1.0.0/rules/hc/hc-157@1.0.0.yaml +37 -0
  425. raxe/packs/core/v1.0.0/rules/hc/hc-158@1.0.0.yaml +37 -0
  426. raxe/packs/core/v1.0.0/rules/hc/hc-159@1.0.0.yaml +37 -0
  427. raxe/packs/core/v1.0.0/rules/hc/hc-160@1.0.0.yaml +37 -0
  428. raxe/packs/core/v1.0.0/rules/hc/hc-161@1.0.0.yaml +37 -0
  429. raxe/packs/core/v1.0.0/rules/jb/jb-001@1.0.0.yaml +47 -0
  430. raxe/packs/core/v1.0.0/rules/jb/jb-009@1.0.0.yaml +47 -0
  431. raxe/packs/core/v1.0.0/rules/jb/jb-020@1.0.0.yaml +47 -0
  432. raxe/packs/core/v1.0.0/rules/jb/jb-021@1.0.0.yaml +46 -0
  433. raxe/packs/core/v1.0.0/rules/jb/jb-022@1.0.0.yaml +47 -0
  434. raxe/packs/core/v1.0.0/rules/jb/jb-028@1.0.0.yaml +43 -0
  435. raxe/packs/core/v1.0.0/rules/jb/jb-033@1.0.0.yaml +46 -0
  436. raxe/packs/core/v1.0.0/rules/jb/jb-034@1.0.0.yaml +46 -0
  437. raxe/packs/core/v1.0.0/rules/jb/jb-036@1.0.0.yaml +41 -0
  438. raxe/packs/core/v1.0.0/rules/jb/jb-039@1.0.0.yaml +41 -0
  439. raxe/packs/core/v1.0.0/rules/jb/jb-056@1.0.0.yaml +38 -0
  440. raxe/packs/core/v1.0.0/rules/jb/jb-066@1.0.0.yaml +37 -0
  441. raxe/packs/core/v1.0.0/rules/jb/jb-076@1.0.0.yaml +37 -0
  442. raxe/packs/core/v1.0.0/rules/jb/jb-098@1.0.0.yaml +46 -0
  443. raxe/packs/core/v1.0.0/rules/jb/jb-103@1.0.0.yaml +47 -0
  444. raxe/packs/core/v1.0.0/rules/jb/jb-104@1.0.0.yaml +52 -0
  445. raxe/packs/core/v1.0.0/rules/jb/jb-105@1.0.0.yaml +56 -0
  446. raxe/packs/core/v1.0.0/rules/jb/jb-110@1.0.0.yaml +56 -0
  447. raxe/packs/core/v1.0.0/rules/jb/jb-111@1.0.0.yaml +57 -0
  448. raxe/packs/core/v1.0.0/rules/jb/jb-112@1.0.0.yaml +38 -0
  449. raxe/packs/core/v1.0.0/rules/jb/jb-113@1.0.0.yaml +38 -0
  450. raxe/packs/core/v1.0.0/rules/jb/jb-114@1.0.0.yaml +38 -0
  451. raxe/packs/core/v1.0.0/rules/jb/jb-115@1.0.0.yaml +38 -0
  452. raxe/packs/core/v1.0.0/rules/jb/jb-116@1.0.0.yaml +38 -0
  453. raxe/packs/core/v1.0.0/rules/jb/jb-117@1.0.0.yaml +38 -0
  454. raxe/packs/core/v1.0.0/rules/jb/jb-118@1.0.0.yaml +38 -0
  455. raxe/packs/core/v1.0.0/rules/jb/jb-119@1.0.0.yaml +38 -0
  456. raxe/packs/core/v1.0.0/rules/jb/jb-120@1.0.0.yaml +38 -0
  457. raxe/packs/core/v1.0.0/rules/jb/jb-121@1.0.0.yaml +38 -0
  458. raxe/packs/core/v1.0.0/rules/jb/jb-122@1.0.0.yaml +38 -0
  459. raxe/packs/core/v1.0.0/rules/jb/jb-123@1.0.0.yaml +38 -0
  460. raxe/packs/core/v1.0.0/rules/jb/jb-124@1.0.0.yaml +38 -0
  461. raxe/packs/core/v1.0.0/rules/jb/jb-125@1.0.0.yaml +38 -0
  462. raxe/packs/core/v1.0.0/rules/jb/jb-126@1.0.0.yaml +38 -0
  463. raxe/packs/core/v1.0.0/rules/jb/jb-127@1.0.0.yaml +38 -0
  464. raxe/packs/core/v1.0.0/rules/jb/jb-128@1.0.0.yaml +38 -0
  465. raxe/packs/core/v1.0.0/rules/jb/jb-129@1.0.0.yaml +38 -0
  466. raxe/packs/core/v1.0.0/rules/jb/jb-130@1.0.0.yaml +38 -0
  467. raxe/packs/core/v1.0.0/rules/jb/jb-131@1.0.0.yaml +38 -0
  468. raxe/packs/core/v1.0.0/rules/jb/jb-132@1.0.0.yaml +38 -0
  469. raxe/packs/core/v1.0.0/rules/jb/jb-133@1.0.0.yaml +38 -0
  470. raxe/packs/core/v1.0.0/rules/jb/jb-134@1.0.0.yaml +38 -0
  471. raxe/packs/core/v1.0.0/rules/jb/jb-135@1.0.0.yaml +38 -0
  472. raxe/packs/core/v1.0.0/rules/jb/jb-136@1.0.0.yaml +38 -0
  473. raxe/packs/core/v1.0.0/rules/jb/jb-137@1.0.0.yaml +38 -0
  474. raxe/packs/core/v1.0.0/rules/jb/jb-138@1.0.0.yaml +38 -0
  475. raxe/packs/core/v1.0.0/rules/jb/jb-139@1.0.0.yaml +38 -0
  476. raxe/packs/core/v1.0.0/rules/jb/jb-140@1.0.0.yaml +38 -0
  477. raxe/packs/core/v1.0.0/rules/jb/jb-141@1.0.0.yaml +38 -0
  478. raxe/packs/core/v1.0.0/rules/jb/jb-142@1.0.0.yaml +38 -0
  479. raxe/packs/core/v1.0.0/rules/jb/jb-143@1.0.0.yaml +38 -0
  480. raxe/packs/core/v1.0.0/rules/jb/jb-144@1.0.0.yaml +38 -0
  481. raxe/packs/core/v1.0.0/rules/jb/jb-145@1.0.0.yaml +38 -0
  482. raxe/packs/core/v1.0.0/rules/jb/jb-146@1.0.0.yaml +38 -0
  483. raxe/packs/core/v1.0.0/rules/jb/jb-147@1.0.0.yaml +38 -0
  484. raxe/packs/core/v1.0.0/rules/jb/jb-148@1.0.0.yaml +38 -0
  485. raxe/packs/core/v1.0.0/rules/jb/jb-149@1.0.0.yaml +38 -0
  486. raxe/packs/core/v1.0.0/rules/jb/jb-150@1.0.0.yaml +38 -0
  487. raxe/packs/core/v1.0.0/rules/jb/jb-151@1.0.0.yaml +38 -0
  488. raxe/packs/core/v1.0.0/rules/jb/jb-152@1.0.0.yaml +38 -0
  489. raxe/packs/core/v1.0.0/rules/jb/jb-153@1.0.0.yaml +38 -0
  490. raxe/packs/core/v1.0.0/rules/jb/jb-154@1.0.0.yaml +38 -0
  491. raxe/packs/core/v1.0.0/rules/jb/jb-155@1.0.0.yaml +38 -0
  492. raxe/packs/core/v1.0.0/rules/jb/jb-156@1.0.0.yaml +38 -0
  493. raxe/packs/core/v1.0.0/rules/jb/jb-157@1.0.0.yaml +38 -0
  494. raxe/packs/core/v1.0.0/rules/jb/jb-158@1.0.0.yaml +38 -0
  495. raxe/packs/core/v1.0.0/rules/jb/jb-159@1.0.0.yaml +38 -0
  496. raxe/packs/core/v1.0.0/rules/jb/jb-160@1.0.0.yaml +38 -0
  497. raxe/packs/core/v1.0.0/rules/jb/jb-161@1.0.0.yaml +38 -0
  498. raxe/packs/core/v1.0.0/rules/jb/jb-162@1.0.0.yaml +38 -0
  499. raxe/packs/core/v1.0.0/rules/jb/jb-201@1.0.0.yaml +40 -0
  500. raxe/packs/core/v1.0.0/rules/jb/jb-202@1.0.0.yaml +41 -0
  501. raxe/packs/core/v1.0.0/rules/jb/jb-203@1.0.0.yaml +51 -0
  502. raxe/packs/core/v1.0.0/rules/jb/jb-204@1.0.0.yaml +50 -0
  503. raxe/packs/core/v1.0.0/rules/jb/jb-205@1.0.0.yaml +50 -0
  504. raxe/packs/core/v1.0.0/rules/jb/jb-206@1.0.0.yaml +50 -0
  505. raxe/packs/core/v1.0.0/rules/jb/jb-207@1.0.0.yaml +49 -0
  506. raxe/packs/core/v1.0.0/rules/pii/pii-001@1.0.0.yaml +48 -0
  507. raxe/packs/core/v1.0.0/rules/pii/pii-009@1.0.0.yaml +48 -0
  508. raxe/packs/core/v1.0.0/rules/pii/pii-012@1.0.0.yaml +48 -0
  509. raxe/packs/core/v1.0.0/rules/pii/pii-017@1.0.0.yaml +48 -0
  510. raxe/packs/core/v1.0.0/rules/pii/pii-022@1.0.0.yaml +47 -0
  511. raxe/packs/core/v1.0.0/rules/pii/pii-025@1.0.0.yaml +47 -0
  512. raxe/packs/core/v1.0.0/rules/pii/pii-027@1.0.0.yaml +47 -0
  513. raxe/packs/core/v1.0.0/rules/pii/pii-028@1.0.0.yaml +47 -0
  514. raxe/packs/core/v1.0.0/rules/pii/pii-034@1.0.0.yaml +47 -0
  515. raxe/packs/core/v1.0.0/rules/pii/pii-037@1.0.0.yaml +47 -0
  516. raxe/packs/core/v1.0.0/rules/pii/pii-040@1.0.0.yaml +47 -0
  517. raxe/packs/core/v1.0.0/rules/pii/pii-041@1.0.0.yaml +47 -0
  518. raxe/packs/core/v1.0.0/rules/pii/pii-044@1.0.0.yaml +47 -0
  519. raxe/packs/core/v1.0.0/rules/pii/pii-050@1.0.0.yaml +57 -0
  520. raxe/packs/core/v1.0.0/rules/pii/pii-051@1.0.0.yaml +53 -0
  521. raxe/packs/core/v1.0.0/rules/pii/pii-052@1.0.0.yaml +52 -0
  522. raxe/packs/core/v1.0.0/rules/pii/pii-053@1.0.0.yaml +56 -0
  523. raxe/packs/core/v1.0.0/rules/pii/pii-054@1.0.0.yaml +53 -0
  524. raxe/packs/core/v1.0.0/rules/pii/pii-055@1.0.0.yaml +51 -0
  525. raxe/packs/core/v1.0.0/rules/pii/pii-056@1.0.0.yaml +51 -0
  526. raxe/packs/core/v1.0.0/rules/pii/pii-058@1.0.0.yaml +47 -0
  527. raxe/packs/core/v1.0.0/rules/pii/pii-2015@1.0.0.yaml +41 -0
  528. raxe/packs/core/v1.0.0/rules/pii/pii-2025@1.0.0.yaml +35 -0
  529. raxe/packs/core/v1.0.0/rules/pii/pii-2026@1.0.0.yaml +39 -0
  530. raxe/packs/core/v1.0.0/rules/pii/pii-2035@1.0.0.yaml +39 -0
  531. raxe/packs/core/v1.0.0/rules/pii/pii-2037@1.0.0.yaml +39 -0
  532. raxe/packs/core/v1.0.0/rules/pii/pii-2042@1.0.0.yaml +39 -0
  533. raxe/packs/core/v1.0.0/rules/pii/pii-3001@1.0.0.yaml +39 -0
  534. raxe/packs/core/v1.0.0/rules/pii/pii-3002@1.0.0.yaml +41 -0
  535. raxe/packs/core/v1.0.0/rules/pii/pii-3003@1.0.0.yaml +36 -0
  536. raxe/packs/core/v1.0.0/rules/pii/pii-3004@1.0.0.yaml +41 -0
  537. raxe/packs/core/v1.0.0/rules/pii/pii-3005@1.0.0.yaml +39 -0
  538. raxe/packs/core/v1.0.0/rules/pii/pii-3006@1.0.0.yaml +35 -0
  539. raxe/packs/core/v1.0.0/rules/pii/pii-3007@1.0.0.yaml +37 -0
  540. raxe/packs/core/v1.0.0/rules/pii/pii-3008@1.0.0.yaml +35 -0
  541. raxe/packs/core/v1.0.0/rules/pii/pii-3009@1.0.0.yaml +42 -0
  542. raxe/packs/core/v1.0.0/rules/pii/pii-3010@1.0.0.yaml +39 -0
  543. raxe/packs/core/v1.0.0/rules/pii/pii-3011@1.0.0.yaml +35 -0
  544. raxe/packs/core/v1.0.0/rules/pii/pii-3012@1.0.0.yaml +35 -0
  545. raxe/packs/core/v1.0.0/rules/pii/pii-3013@1.0.0.yaml +36 -0
  546. raxe/packs/core/v1.0.0/rules/pii/pii-3014@1.0.0.yaml +36 -0
  547. raxe/packs/core/v1.0.0/rules/pii/pii-3015@1.0.0.yaml +42 -0
  548. raxe/packs/core/v1.0.0/rules/pii/pii-3016@1.0.0.yaml +42 -0
  549. raxe/packs/core/v1.0.0/rules/pii/pii-3017@1.0.0.yaml +40 -0
  550. raxe/packs/core/v1.0.0/rules/pii/pii-3018@1.0.0.yaml +38 -0
  551. raxe/packs/core/v1.0.0/rules/pii/pii-3019@1.0.0.yaml +40 -0
  552. raxe/packs/core/v1.0.0/rules/pii/pii-3020@1.0.0.yaml +40 -0
  553. raxe/packs/core/v1.0.0/rules/pii/pii-3021@1.0.0.yaml +39 -0
  554. raxe/packs/core/v1.0.0/rules/pii/pii-3022@1.0.0.yaml +36 -0
  555. raxe/packs/core/v1.0.0/rules/pii/pii-3023@1.0.0.yaml +41 -0
  556. raxe/packs/core/v1.0.0/rules/pii/pii-3024@1.0.0.yaml +37 -0
  557. raxe/packs/core/v1.0.0/rules/pii/pii-3025@1.0.0.yaml +38 -0
  558. raxe/packs/core/v1.0.0/rules/pii/pii-3026@1.0.0.yaml +42 -0
  559. raxe/packs/core/v1.0.0/rules/pii/pii-3027@1.0.0.yaml +38 -0
  560. raxe/packs/core/v1.0.0/rules/pii/pii-3028@1.0.0.yaml +42 -0
  561. raxe/packs/core/v1.0.0/rules/pii/pii-3029@1.0.0.yaml +36 -0
  562. raxe/packs/core/v1.0.0/rules/pii/pii-3030@1.0.0.yaml +42 -0
  563. raxe/packs/core/v1.0.0/rules/pii/pii-3031@1.0.0.yaml +37 -0
  564. raxe/packs/core/v1.0.0/rules/pii/pii-3032@1.0.0.yaml +42 -0
  565. raxe/packs/core/v1.0.0/rules/pii/pii-3033@1.0.0.yaml +39 -0
  566. raxe/packs/core/v1.0.0/rules/pii/pii-3034@1.0.0.yaml +40 -0
  567. raxe/packs/core/v1.0.0/rules/pii/pii-3035@1.0.0.yaml +43 -0
  568. raxe/packs/core/v1.0.0/rules/pii/pii-3036@1.0.0.yaml +41 -0
  569. raxe/packs/core/v1.0.0/rules/pii/pii-3037@1.0.0.yaml +35 -0
  570. raxe/packs/core/v1.0.0/rules/pii/pii-3038@1.0.0.yaml +35 -0
  571. raxe/packs/core/v1.0.0/rules/pii/pii-3039@1.0.0.yaml +35 -0
  572. raxe/packs/core/v1.0.0/rules/pii/pii-3040@1.0.0.yaml +41 -0
  573. raxe/packs/core/v1.0.0/rules/pii/pii-3041@1.0.0.yaml +39 -0
  574. raxe/packs/core/v1.0.0/rules/pii/pii-3042@1.0.0.yaml +36 -0
  575. raxe/packs/core/v1.0.0/rules/pii/pii-3043@1.0.0.yaml +35 -0
  576. raxe/packs/core/v1.0.0/rules/pii/pii-3044@1.0.0.yaml +43 -0
  577. raxe/packs/core/v1.0.0/rules/pii/pii-3045@1.0.0.yaml +36 -0
  578. raxe/packs/core/v1.0.0/rules/pii/pii-3046@1.0.0.yaml +37 -0
  579. raxe/packs/core/v1.0.0/rules/pii/pii-3047@1.0.0.yaml +36 -0
  580. raxe/packs/core/v1.0.0/rules/pii/pii-3048@1.0.0.yaml +36 -0
  581. raxe/packs/core/v1.0.0/rules/pii/pii-3049@1.0.0.yaml +38 -0
  582. raxe/packs/core/v1.0.0/rules/pii/pii-3050@1.0.0.yaml +44 -0
  583. raxe/packs/core/v1.0.0/rules/pii/pii-3051@1.0.0.yaml +35 -0
  584. raxe/packs/core/v1.0.0/rules/pii/pii-3052@1.0.0.yaml +36 -0
  585. raxe/packs/core/v1.0.0/rules/pii/pii-3053@1.0.0.yaml +35 -0
  586. raxe/packs/core/v1.0.0/rules/pii/pii-3054@1.0.0.yaml +35 -0
  587. raxe/packs/core/v1.0.0/rules/pii/pii-3055@1.0.0.yaml +40 -0
  588. raxe/packs/core/v1.0.0/rules/pii/pii-3056@1.0.0.yaml +38 -0
  589. raxe/packs/core/v1.0.0/rules/pii/pii-3057@1.0.0.yaml +40 -0
  590. raxe/packs/core/v1.0.0/rules/pii/pii-3058@1.0.0.yaml +43 -0
  591. raxe/packs/core/v1.0.0/rules/pii/pii-3059@1.0.0.yaml +42 -0
  592. raxe/packs/core/v1.0.0/rules/pii/pii-3060@1.0.0.yaml +42 -0
  593. raxe/packs/core/v1.0.0/rules/pii/pii-3061@1.0.0.yaml +50 -0
  594. raxe/packs/core/v1.0.0/rules/pii/pii-3062@1.0.0.yaml +50 -0
  595. raxe/packs/core/v1.0.0/rules/pii/pii-3063@1.0.0.yaml +54 -0
  596. raxe/packs/core/v1.0.0/rules/pii/pii-3064@1.0.0.yaml +78 -0
  597. raxe/packs/core/v1.0.0/rules/pii/pii-3065@1.0.0.yaml +84 -0
  598. raxe/packs/core/v1.0.0/rules/pii/pii-3066@1.0.0.yaml +84 -0
  599. raxe/packs/core/v1.0.0/rules/pii/pii-3067@1.0.0.yaml +88 -0
  600. raxe/packs/core/v1.0.0/rules/pii/pii-3068@1.0.0.yaml +94 -0
  601. raxe/packs/core/v1.0.0/rules/pii/pii-3069@1.0.0.yaml +90 -0
  602. raxe/packs/core/v1.0.0/rules/pii/pii-3070@1.0.0.yaml +99 -0
  603. raxe/packs/core/v1.0.0/rules/pii/pii-3071@1.0.0.yaml +91 -0
  604. raxe/packs/core/v1.0.0/rules/pii/pii-3072@1.0.0.yaml +38 -0
  605. raxe/packs/core/v1.0.0/rules/pii/pii-3073@1.0.0.yaml +38 -0
  606. raxe/packs/core/v1.0.0/rules/pii/pii-3074@1.0.0.yaml +38 -0
  607. raxe/packs/core/v1.0.0/rules/pii/pii-3075@1.0.0.yaml +38 -0
  608. raxe/packs/core/v1.0.0/rules/pii/pii-3076@1.0.0.yaml +38 -0
  609. raxe/packs/core/v1.0.0/rules/pii/pii-3077@1.0.0.yaml +38 -0
  610. raxe/packs/core/v1.0.0/rules/pii/pii-3078@1.0.0.yaml +38 -0
  611. raxe/packs/core/v1.0.0/rules/pii/pii-3079@1.0.0.yaml +38 -0
  612. raxe/packs/core/v1.0.0/rules/pii/pii-3080@1.0.0.yaml +38 -0
  613. raxe/packs/core/v1.0.0/rules/pii/pii-3081@1.0.0.yaml +38 -0
  614. raxe/packs/core/v1.0.0/rules/pii/pii-3082@1.0.0.yaml +38 -0
  615. raxe/packs/core/v1.0.0/rules/pii/pii-3083@1.0.0.yaml +38 -0
  616. raxe/packs/core/v1.0.0/rules/pii/pii-3084@1.0.0.yaml +38 -0
  617. raxe/packs/core/v1.0.0/rules/pii/pii-3085@1.0.0.yaml +38 -0
  618. raxe/packs/core/v1.0.0/rules/rag/rag-016@1.0.0.yaml +47 -0
  619. raxe/packs/core/v1.0.0/rules/rag/rag-028@1.0.0.yaml +47 -0
  620. raxe/packs/core/v1.0.0/rules/rag/rag-042@1.0.0.yaml +47 -0
  621. raxe/packs/core/v1.0.0/rules/rag/rag-044@1.0.0.yaml +47 -0
  622. raxe/packs/core/v1.0.0/rules/rag/rag-045@1.0.0.yaml +47 -0
  623. raxe/packs/core/v1.0.0/rules/rag/rag-050@1.0.0.yaml +47 -0
  624. raxe/packs/core/v1.0.0/rules/rag/rag-201@1.0.0.yaml +41 -0
  625. raxe/packs/core/v1.0.0/rules/rag/rag-202@1.0.0.yaml +41 -0
  626. raxe/packs/core/v1.0.0/rules/rag/rag-3001@1.0.0.yaml +41 -0
  627. raxe/packs/core/v1.0.0/rules/rag/rag-3006@1.0.0.yaml +41 -0
  628. raxe/packs/core/v1.0.0/rules/rag/rag-3009@1.0.0.yaml +41 -0
  629. raxe/packs/core/v1.0.0/rules/rag/rag-3012@1.0.0.yaml +41 -0
  630. raxe/plugins/__init__.py +98 -0
  631. raxe/plugins/custom_rules.py +380 -0
  632. raxe/plugins/loader.py +389 -0
  633. raxe/plugins/manager.py +538 -0
  634. raxe/plugins/protocol.py +428 -0
  635. raxe/py.typed +0 -0
  636. raxe/sdk/__init__.py +77 -0
  637. raxe/sdk/agent_scanner.py +1918 -0
  638. raxe/sdk/client.py +1603 -0
  639. raxe/sdk/decorator.py +175 -0
  640. raxe/sdk/exceptions.py +859 -0
  641. raxe/sdk/integrations/__init__.py +277 -0
  642. raxe/sdk/integrations/agent_scanner.py +71 -0
  643. raxe/sdk/integrations/autogen.py +872 -0
  644. raxe/sdk/integrations/crewai.py +1368 -0
  645. raxe/sdk/integrations/dspy.py +845 -0
  646. raxe/sdk/integrations/extractors.py +363 -0
  647. raxe/sdk/integrations/huggingface.py +395 -0
  648. raxe/sdk/integrations/langchain.py +948 -0
  649. raxe/sdk/integrations/litellm.py +484 -0
  650. raxe/sdk/integrations/llamaindex.py +1049 -0
  651. raxe/sdk/integrations/portkey.py +831 -0
  652. raxe/sdk/suppression_context.py +215 -0
  653. raxe/sdk/wrappers/__init__.py +163 -0
  654. raxe/sdk/wrappers/anthropic.py +310 -0
  655. raxe/sdk/wrappers/openai.py +221 -0
  656. raxe/sdk/wrappers/vertexai.py +484 -0
  657. raxe/utils/__init__.py +12 -0
  658. raxe/utils/error_sanitizer.py +135 -0
  659. raxe/utils/logging.py +241 -0
  660. raxe/utils/performance.py +414 -0
  661. raxe/utils/profiler.py +339 -0
  662. raxe/utils/validators.py +170 -0
  663. raxe-0.4.6.dist-info/METADATA +471 -0
  664. raxe-0.4.6.dist-info/RECORD +668 -0
  665. raxe-0.4.6.dist-info/WHEEL +5 -0
  666. raxe-0.4.6.dist-info/entry_points.txt +2 -0
  667. raxe-0.4.6.dist-info/licenses/LICENSE +56 -0
  668. raxe-0.4.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,921 @@
1
+ """Gemma-based 5-head multilabel L2 detector.
2
+
3
+ This detector uses the EmbeddingGemma-300M model with 5 classifier heads:
4
+ 1. is_threat (binary): benign vs threat
5
+ 2. threat_family (multiclass, 9): Attack category
6
+ 3. severity (multiclass, 5): Threat severity level
7
+ 4. primary_technique (multiclass, 22): Specific attack technique
8
+ 5. harm_types (multilabel, 10): Types of potential harm
9
+
10
+ Model files expected in model directory:
11
+ - model_int8.onnx: EmbeddingGemma-300M (INT8 quantized)
12
+ - classifier_is_threat_int8.onnx
13
+ - classifier_threat_family_int8.onnx
14
+ - classifier_severity_int8.onnx
15
+ - classifier_primary_technique_int8.onnx
16
+ - classifier_harm_types_int8.onnx
17
+ - tokenizer.json, config.json, label_config.json, model_metadata.json
18
+
19
+ NEW: Voting Engine Integration
20
+ The ensemble decision logic now uses VotingEngine for transparent weighted voting
21
+ instead of boost-based heuristics. Set L2Config.voting.enabled=False to use legacy logic.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import time
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ import numpy as np
31
+
32
+ from raxe.domain.engine.executor import ScanResult as L1ScanResult
33
+ from raxe.domain.ml.gemma_models import (
34
+ DEFAULT_HARM_THRESHOLDS,
35
+ GemmaClassificationResult,
36
+ HarmType,
37
+ MultilabelResult,
38
+ PrimaryTechnique,
39
+ Severity,
40
+ ThreatFamily,
41
+ )
42
+ from raxe.domain.ml.l2_config import L2Config, get_l2_config
43
+ from raxe.domain.ml.protocol import L2Prediction, L2Result, L2ThreatType
44
+ from raxe.domain.ml.voting import (
45
+ Decision,
46
+ HeadOutputs,
47
+ VotingEngine,
48
+ VotingResult,
49
+ )
50
+ from raxe.utils.logging import get_logger
51
+
52
+ logger = get_logger(__name__)
53
+
54
+
55
+ class GemmaL2Detector:
56
+ """Gemma-based 5-head L2 detector.
57
+
58
+ Implements L2Detector protocol with the new Gemma multilabel architecture.
59
+
60
+ Performance targets:
61
+ - P95 latency: <50ms (with cache miss)
62
+ - P50 latency: <10ms (with cache hit)
63
+ - Memory: <400MB
64
+
65
+ Example:
66
+ detector = GemmaL2Detector(model_dir="/path/to/models")
67
+ result = detector.analyze(text, l1_results)
68
+ """
69
+
70
+ VERSION = "gemma-compact-v1"
71
+ EMBEDDING_DIM = 256 # Matryoshka truncation from 768
72
+
73
+ def __init__(
74
+ self,
75
+ model_dir: str | Path,
76
+ *,
77
+ confidence_threshold: float | None = None,
78
+ harm_thresholds: dict[str, float] | None = None,
79
+ cache_size: int = 1000,
80
+ scorer: Any | None = None,
81
+ l2_config: L2Config | None = None,
82
+ voting_preset: str | None = None,
83
+ ):
84
+ """Initialize Gemma L2 detector.
85
+
86
+ Args:
87
+ model_dir: Path to directory containing ONNX models
88
+ confidence_threshold: Override threshold for binary threat classification
89
+ (uses L2Config if not provided)
90
+ harm_thresholds: Per-class thresholds for harm_types multilabel
91
+ cache_size: Size of embedding cache (0 to disable)
92
+ scorer: Optional HierarchicalThreatScorer instance
93
+ l2_config: L2 configuration (uses global config if not provided)
94
+ voting_preset: Voting preset override (balanced, high_security, low_fp)
95
+ """
96
+ self.model_dir = Path(model_dir)
97
+ self._l2_config = l2_config or get_l2_config()
98
+ # Use provided threshold, or config threshold
99
+ self.confidence_threshold = (
100
+ confidence_threshold
101
+ if confidence_threshold is not None
102
+ else self._l2_config.thresholds.threat_threshold
103
+ )
104
+ # Use provided harm thresholds, or config thresholds
105
+ self.harm_thresholds = (
106
+ harm_thresholds
107
+ if harm_thresholds is not None
108
+ else self._l2_config.thresholds.harm_type_thresholds.copy()
109
+ )
110
+ self.scorer = scorer
111
+
112
+ # Initialize voting engine if enabled
113
+ self._voting_enabled = self._l2_config.voting.enabled
114
+ if self._voting_enabled:
115
+ # Use override preset if provided, otherwise use config preset
116
+ preset = voting_preset or self._l2_config.voting.preset
117
+ self._voting_engine = VotingEngine(preset=preset)
118
+ logger.info(
119
+ "VotingEngine initialized",
120
+ preset=preset,
121
+ enabled=True,
122
+ )
123
+ else:
124
+ self._voting_engine = None
125
+ logger.info("VotingEngine disabled, using legacy ensemble logic")
126
+
127
+ # Lazy imports for ONNX runtime
128
+ import onnxruntime as ort
129
+ from transformers import PreTrainedTokenizerFast
130
+
131
+ self._ort = ort
132
+
133
+ # Load tokenizer from tokenizer.json (portable format)
134
+ logger.info("Loading Gemma tokenizer", model_dir=str(self.model_dir))
135
+ tokenizer_path = self.model_dir / "tokenizer.json"
136
+ if not tokenizer_path.exists():
137
+ raise FileNotFoundError(f"tokenizer.json not found in {self.model_dir}")
138
+ self._tokenizer = PreTrainedTokenizerFast(tokenizer_file=str(tokenizer_path))
139
+
140
+ # Configure special tokens from config.json
141
+ config_path = self.model_dir / "config.json"
142
+ if config_path.exists():
143
+ import json
144
+ with open(config_path) as f:
145
+ config = json.load(f)
146
+ # Set pad_token_id (Gemma uses id 0 for padding)
147
+ if "pad_token_id" in config:
148
+ self._tokenizer.pad_token_id = config["pad_token_id"]
149
+ # Set pad_token if not already set
150
+ if self._tokenizer.pad_token is None:
151
+ self._tokenizer.pad_token = self._tokenizer.decode([config["pad_token_id"]])
152
+ if "eos_token_id" in config:
153
+ self._tokenizer.eos_token_id = config["eos_token_id"]
154
+ if "bos_token_id" in config:
155
+ self._tokenizer.bos_token_id = config["bos_token_id"]
156
+
157
+ # Create session options
158
+ sess_options = ort.SessionOptions()
159
+ sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
160
+ sess_options.log_severity_level = 3 # ERROR only
161
+ sess_options.intra_op_num_threads = 4
162
+ sess_options.inter_op_num_threads = 1
163
+ sess_options.enable_mem_pattern = True
164
+ sess_options.enable_cpu_mem_arena = True
165
+
166
+ providers = ["CPUExecutionProvider"]
167
+
168
+ # Load embedding model
169
+ embedding_path = self._find_model_file("model", ".onnx")
170
+ logger.info("Loading embedding model", path=str(embedding_path))
171
+ self._embedding_session = ort.InferenceSession(
172
+ str(embedding_path), sess_options, providers=providers
173
+ )
174
+
175
+ # Load classifier heads
176
+ self._classifiers: dict[str, ort.InferenceSession] = {}
177
+ for head in ["is_threat", "threat_family", "severity", "primary_technique", "harm_types"]:
178
+ classifier_path = self._find_model_file(f"classifier_{head}", ".onnx")
179
+ logger.info(f"Loading classifier: {head}", path=str(classifier_path))
180
+ self._classifiers[head] = ort.InferenceSession(
181
+ str(classifier_path), sess_options, providers=providers
182
+ )
183
+
184
+ # Load label config
185
+ label_config_path = self.model_dir / "label_config.json"
186
+ if label_config_path.exists():
187
+ with open(label_config_path) as f:
188
+ self._label_config = json.load(f)
189
+ else:
190
+ self._label_config = {}
191
+
192
+ # Load model metadata
193
+ metadata_path = self.model_dir / "model_metadata.json"
194
+ if metadata_path.exists():
195
+ with open(metadata_path) as f:
196
+ self._model_metadata = json.load(f)
197
+ else:
198
+ self._model_metadata = {}
199
+
200
+ # Setup embedding cache
201
+ self._cache_enabled = cache_size > 0
202
+ if self._cache_enabled:
203
+ from raxe.domain.ml.embedding_cache import EmbeddingCache
204
+ self._embedding_cache = EmbeddingCache(max_size=cache_size)
205
+ else:
206
+ self._embedding_cache = None
207
+
208
+ logger.info(
209
+ "GemmaL2Detector initialized",
210
+ model_dir=str(self.model_dir),
211
+ embedding_dim=self.EMBEDDING_DIM,
212
+ cache_size=cache_size,
213
+ confidence_threshold=confidence_threshold,
214
+ )
215
+
216
+ def _find_model_file(self, prefix: str, suffix: str) -> Path:
217
+ """Find model file with INT8 preference."""
218
+ # Prefer INT8 quantized version
219
+ int8_path = self.model_dir / f"{prefix}_int8{suffix}"
220
+ if int8_path.exists():
221
+ return int8_path
222
+
223
+ # Fall back to non-quantized
224
+ regular_path = self.model_dir / f"{prefix}{suffix}"
225
+ if regular_path.exists():
226
+ return regular_path
227
+
228
+ # Try glob pattern
229
+ matches = list(self.model_dir.glob(f"{prefix}*{suffix}"))
230
+ if matches:
231
+ # Prefer INT8 if available
232
+ for match in matches:
233
+ if "int8" in match.name:
234
+ return match
235
+ return matches[0]
236
+
237
+ raise FileNotFoundError(
238
+ f"No model file found for {prefix}*{suffix} in {self.model_dir}"
239
+ )
240
+
241
+ def analyze(
242
+ self,
243
+ text: str,
244
+ l1_results: L1ScanResult,
245
+ context: dict[str, Any] | None = None,
246
+ ) -> L2Result:
247
+ """Analyze text for threats using Gemma 5-head classifier.
248
+
249
+ Args:
250
+ text: Text to analyze
251
+ l1_results: Results from L1 rule-based detection
252
+ context: Optional context metadata
253
+
254
+ Returns:
255
+ L2Result with predictions from all 5 heads and voting metadata
256
+ """
257
+ start_time = time.perf_counter()
258
+
259
+ try:
260
+ # Generate embeddings (with caching)
261
+ embeddings = self._generate_embeddings(text)
262
+
263
+ # Run classification (returns both classification and voting result)
264
+ classification, voting_result = self._classify(embeddings)
265
+
266
+ # Build predictions
267
+ predictions = self._build_predictions(classification, text, voting_result)
268
+
269
+ # Apply scorer if available
270
+ hierarchical_score = None
271
+ classification_label = None
272
+ recommended_action = None
273
+ decision_rationale = None
274
+ signal_quality = None
275
+
276
+ if self.scorer and classification.is_threat:
277
+ scoring_result = self._apply_scorer(classification, text)
278
+ if scoring_result:
279
+ hierarchical_score = scoring_result.hierarchical_score
280
+ classification_label = scoring_result.classification.value
281
+ recommended_action = scoring_result.action.value
282
+ decision_rationale = scoring_result.reason
283
+ signal_quality = {
284
+ "is_consistent": scoring_result.is_consistent,
285
+ "variance": scoring_result.variance,
286
+ "weak_margins_count": scoring_result.weak_margins_count,
287
+ }
288
+
289
+ # Build classification and action from voting result if available
290
+ if voting_result:
291
+ classification_label = self._voting_decision_to_classification(
292
+ voting_result.decision, voting_result.confidence
293
+ )
294
+ recommended_action = self._voting_decision_to_action(voting_result.decision)
295
+ decision_rationale = f"Voting rule: {voting_result.decision_rule_triggered}"
296
+
297
+ duration_ms = (time.perf_counter() - start_time) * 1000
298
+
299
+ # Build voting metadata for telemetry
300
+ voting_metadata = voting_result.to_dict() if voting_result else None
301
+
302
+ return L2Result(
303
+ predictions=predictions,
304
+ confidence=classification.threat_probability,
305
+ processing_time_ms=duration_ms,
306
+ model_version=self.VERSION,
307
+ features_extracted={
308
+ "text_length": len(text),
309
+ "l1_detection_count": l1_results.detection_count,
310
+ "embedding_dim": self.EMBEDDING_DIM,
311
+ },
312
+ metadata={
313
+ "detector_type": "gemma",
314
+ "classification_result": classification.to_dict(),
315
+ "voting_enabled": self._voting_enabled,
316
+ },
317
+ hierarchical_score=hierarchical_score,
318
+ classification=classification_label,
319
+ recommended_action=recommended_action,
320
+ decision_rationale=decision_rationale,
321
+ signal_quality=signal_quality,
322
+ voting=voting_metadata,
323
+ )
324
+
325
+ except Exception as e:
326
+ logger.error("Gemma detection failed", error=str(e), exc_info=True)
327
+ duration_ms = (time.perf_counter() - start_time) * 1000
328
+ return L2Result(
329
+ predictions=[],
330
+ confidence=0.0,
331
+ processing_time_ms=duration_ms,
332
+ model_version=self.VERSION,
333
+ metadata={"error": str(e), "detector_type": "gemma"},
334
+ )
335
+
336
+ def _voting_decision_to_classification(
337
+ self, decision: Decision, confidence: float
338
+ ) -> str:
339
+ """Map voting decision to classification label."""
340
+ if decision == Decision.THREAT:
341
+ if confidence >= 0.9:
342
+ return "HIGH_THREAT"
343
+ elif confidence >= 0.75:
344
+ return "THREAT"
345
+ else:
346
+ return "LIKELY_THREAT"
347
+ elif decision == Decision.REVIEW:
348
+ return "REVIEW"
349
+ else:
350
+ return "SAFE"
351
+
352
+ def _voting_decision_to_action(self, decision: Decision) -> str:
353
+ """Map voting decision to recommended action."""
354
+ if decision == Decision.THREAT:
355
+ return "BLOCK"
356
+ elif decision == Decision.REVIEW:
357
+ return "MANUAL_REVIEW"
358
+ else:
359
+ return "ALLOW"
360
+
361
+ def _generate_embeddings(self, text: str) -> np.ndarray:
362
+ """Generate embeddings with optional caching.
363
+
364
+ The EmbeddingGemma model outputs two tensors:
365
+ - outputs[0]: token embeddings (batch, seq_len, hidden_dim)
366
+ - outputs[1]: pooled embedding (batch, hidden_dim) - used directly
367
+ """
368
+ # Check cache
369
+ if self._cache_enabled and self._embedding_cache:
370
+ cached = self._embedding_cache.get(text)
371
+ if cached is not None:
372
+ return cached
373
+
374
+ # Tokenize
375
+ inputs = self._tokenizer(
376
+ text,
377
+ padding=True,
378
+ truncation=True,
379
+ max_length=512,
380
+ return_tensors="np",
381
+ )
382
+
383
+ # Get embeddings from model
384
+ outputs = self._embedding_session.run(
385
+ None,
386
+ {
387
+ "input_ids": inputs["input_ids"].astype(np.int64),
388
+ "attention_mask": inputs["attention_mask"].astype(np.int64),
389
+ },
390
+ )
391
+
392
+ # outputs[1] is the model's pooled embedding (batch_size, hidden_dim)
393
+ # The model already does internal pooling, so use it directly
394
+ embeddings = outputs[1]
395
+
396
+ # Truncate to 256 dims (Matryoshka)
397
+ embeddings = embeddings[:, :self.EMBEDDING_DIM]
398
+
399
+ # L2 normalize
400
+ norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
401
+ embeddings = embeddings / (norms + 1e-9)
402
+
403
+ # Cache result
404
+ if self._cache_enabled and self._embedding_cache:
405
+ self._embedding_cache.put(text, embeddings)
406
+
407
+ return embeddings
408
+
409
+ def _classify(
410
+ self, embeddings: np.ndarray
411
+ ) -> tuple[GemmaClassificationResult, VotingResult | None]:
412
+ """Run all 5 classifier heads with ensemble logic.
413
+
414
+ Each classifier returns 2 outputs:
415
+ - [0]: predicted class (int64)
416
+ - [1]: probabilities (float32)
417
+
418
+ Returns:
419
+ Tuple of (GemmaClassificationResult, VotingResult or None)
420
+ VotingResult is None if voting engine is disabled.
421
+ """
422
+ embeddings_f32 = embeddings.astype(np.float32)
423
+
424
+ # ════════════════════════════════════════════════════════════════════
425
+ # Run all 5 classifier heads (always run all for voting engine)
426
+ # ════════════════════════════════════════════════════════════════════
427
+
428
+ # 1. Binary threat classification
429
+ is_threat_outputs = self._classifiers["is_threat"].run(
430
+ None, {"embeddings": embeddings_f32}
431
+ )
432
+ is_threat_proba = is_threat_outputs[1][0] # [benign_prob, threat_prob]
433
+ safe_prob = float(is_threat_proba[0])
434
+ threat_prob = float(is_threat_proba[1])
435
+
436
+ # 2. Threat family
437
+ family_outputs = self._classifiers["threat_family"].run(
438
+ None, {"embeddings": embeddings_f32}
439
+ )
440
+ family_proba = family_outputs[1][0]
441
+ family_idx = int(np.argmax(family_proba))
442
+ family_confidence = float(family_proba[family_idx])
443
+ threat_family = ThreatFamily.from_index(family_idx)
444
+
445
+ # 3. Severity
446
+ severity_outputs = self._classifiers["severity"].run(
447
+ None, {"embeddings": embeddings_f32}
448
+ )
449
+ severity_proba = severity_outputs[1][0]
450
+ severity_idx = int(np.argmax(severity_proba))
451
+ severity_confidence = float(severity_proba[severity_idx])
452
+ severity = Severity.from_index(severity_idx)
453
+
454
+ # 4. Primary technique (always run for voting engine)
455
+ technique_outputs = self._classifiers["primary_technique"].run(
456
+ None, {"embeddings": embeddings_f32}
457
+ )
458
+ technique_proba_arr = technique_outputs[1][0]
459
+ technique_idx = int(np.argmax(technique_proba_arr))
460
+ technique_confidence = float(technique_proba_arr[technique_idx])
461
+ primary_technique = PrimaryTechnique.from_index(technique_idx)
462
+ technique_proba = tuple(float(p) for p in technique_proba_arr)
463
+
464
+ # 5. Harm types (always run for voting engine)
465
+ harm_outputs = self._classifiers["harm_types"].run(
466
+ None, {"embeddings": embeddings_f32}
467
+ )
468
+ harm_proba = harm_outputs[1][0]
469
+ harm_types_result = self._process_multilabel_harm_types(harm_proba)
470
+ harm_max_prob = max(float(p) for p in harm_proba)
471
+ harm_active_labels = [h.value for h in harm_types_result.active_labels]
472
+
473
+ # ════════════════════════════════════════════════════════════════════
474
+ # DECISION LOGIC: Voting Engine or Legacy Ensemble
475
+ # ════════════════════════════════════════════════════════════════════
476
+
477
+ voting_result: VotingResult | None = None
478
+
479
+ if self._voting_enabled and self._voting_engine:
480
+ # Use new VotingEngine for transparent weighted voting
481
+ head_outputs = HeadOutputs(
482
+ binary_threat_prob=threat_prob,
483
+ binary_safe_prob=safe_prob,
484
+ family_prediction=threat_family.value,
485
+ family_confidence=family_confidence,
486
+ severity_prediction=severity.value,
487
+ severity_confidence=severity_confidence,
488
+ technique_prediction=primary_technique.value if primary_technique else None,
489
+ technique_confidence=technique_confidence,
490
+ harm_max_probability=harm_max_prob,
491
+ harm_active_labels=harm_active_labels,
492
+ )
493
+
494
+ voting_result = self._voting_engine.vote(head_outputs)
495
+
496
+ # Map voting decision to is_threat and effective probability
497
+ is_threat = voting_result.decision in (Decision.THREAT, Decision.REVIEW)
498
+ final_threat_prob = voting_result.confidence
499
+ family_override_triggered = False # Not used with voting engine
500
+ else:
501
+ # Legacy boost-based ensemble logic
502
+ is_threat, final_threat_prob, family_override_triggered = (
503
+ self._legacy_ensemble_logic(
504
+ threat_prob=threat_prob,
505
+ threat_family=threat_family,
506
+ family_confidence=family_confidence,
507
+ severity=severity,
508
+ primary_technique=primary_technique,
509
+ technique_confidence=technique_confidence,
510
+ )
511
+ )
512
+
513
+ # Determine harm_types for result (only include if threat)
514
+ harm_types = harm_types_result if is_threat else None
515
+
516
+ result = GemmaClassificationResult(
517
+ is_threat=is_threat,
518
+ threat_probability=final_threat_prob,
519
+ safe_probability=1.0 - final_threat_prob,
520
+ threat_family=threat_family,
521
+ family_confidence=family_confidence,
522
+ family_probabilities=tuple(float(p) for p in family_proba),
523
+ severity=severity,
524
+ severity_confidence=severity_confidence,
525
+ severity_probabilities=tuple(float(p) for p in severity_proba),
526
+ primary_technique=primary_technique,
527
+ technique_confidence=technique_confidence,
528
+ technique_probabilities=technique_proba,
529
+ harm_types=harm_types,
530
+ raw_threat_probability=threat_prob,
531
+ family_override_triggered=family_override_triggered,
532
+ )
533
+
534
+ return result, voting_result
535
+
536
+ def _legacy_ensemble_logic(
537
+ self,
538
+ threat_prob: float,
539
+ threat_family: ThreatFamily,
540
+ family_confidence: float,
541
+ severity: Severity,
542
+ primary_technique: PrimaryTechnique | None,
543
+ technique_confidence: float,
544
+ ) -> tuple[bool, float, bool]:
545
+ """Legacy boost-based ensemble logic (used when voting is disabled).
546
+
547
+ Returns:
548
+ Tuple of (is_threat, effective_threat_prob, family_override_triggered)
549
+ """
550
+ cfg = self._l2_config
551
+ ensemble = cfg.ensemble
552
+
553
+ # Start with binary head decision
554
+ effective_threat_prob = threat_prob
555
+ is_threat = threat_prob >= self.confidence_threshold
556
+
557
+ # Check for family override
558
+ family_override_triggered = False
559
+ if ensemble.use_family_override:
560
+ family_is_benign = threat_family == ThreatFamily.BENIGN
561
+ family_override_threshold = cfg.thresholds.family_override_threshold
562
+
563
+ if not family_is_benign and family_confidence >= family_override_threshold:
564
+ family_override_triggered = True
565
+ is_threat = True
566
+ if effective_threat_prob < self.confidence_threshold:
567
+ effective_threat_prob = max(
568
+ effective_threat_prob,
569
+ self.confidence_threshold + 0.05
570
+ )
571
+
572
+ # Check for always-threat families
573
+ if threat_family.value in ensemble.always_threat_families:
574
+ is_threat = True
575
+ effective_threat_prob = max(effective_threat_prob, 0.75)
576
+
577
+ # Apply severity boost
578
+ if ensemble.use_severity_boost:
579
+ if severity in (Severity.HIGH, Severity.CRITICAL):
580
+ effective_threat_prob = min(
581
+ 1.0, effective_threat_prob + ensemble.severity_boost_amount
582
+ )
583
+ if severity == Severity.CRITICAL and not is_threat:
584
+ is_threat = True
585
+
586
+ # Apply technique boost
587
+ if ensemble.use_technique_boost and primary_technique:
588
+ if (
589
+ technique_confidence >= ensemble.technique_boost_threshold
590
+ and primary_technique.value in ensemble.high_confidence_techniques
591
+ ):
592
+ effective_threat_prob = min(
593
+ 1.0, effective_threat_prob + ensemble.technique_boost_amount
594
+ )
595
+ if not is_threat:
596
+ is_threat = True
597
+
598
+ return is_threat, effective_threat_prob, family_override_triggered
599
+
600
+ def _process_multilabel_harm_types(
601
+ self, probabilities: np.ndarray
602
+ ) -> MultilabelResult:
603
+ """Process multilabel harm types with per-class thresholds."""
604
+ harm_classes = HarmType.all_classes()
605
+ active_labels = []
606
+ proba_dict: dict[str, float] = {}
607
+ thresholds_dict: dict[str, float] = {}
608
+
609
+ for i, harm_type in enumerate(harm_classes):
610
+ prob = float(probabilities[i])
611
+ threshold = self.harm_thresholds.get(harm_type.value, 0.5)
612
+ proba_dict[harm_type.value] = prob
613
+ thresholds_dict[harm_type.value] = threshold
614
+
615
+ if prob >= threshold:
616
+ active_labels.append(harm_type)
617
+
618
+ return MultilabelResult(
619
+ active_labels=tuple(active_labels),
620
+ probabilities=proba_dict,
621
+ thresholds_used=thresholds_dict,
622
+ )
623
+
624
+ def _build_predictions(
625
+ self,
626
+ classification: GemmaClassificationResult,
627
+ text: str,
628
+ voting_result: VotingResult | None = None,
629
+ ) -> list[L2Prediction]:
630
+ """Build L2Predictions from classification result."""
631
+ if not classification.is_threat:
632
+ return []
633
+
634
+ # Map family to L2ThreatType
635
+ threat_type = L2ThreatType.from_family(classification.threat_family.value)
636
+
637
+ # Build explanation
638
+ explanation_parts = [
639
+ f"Detected {classification.threat_family.value} threat",
640
+ f"with {classification.severity.value} severity",
641
+ ]
642
+ if classification.primary_technique:
643
+ explanation_parts.append(
644
+ f"using {classification.primary_technique.value} technique"
645
+ )
646
+ if classification.harm_types and classification.harm_types.has_active_labels:
647
+ harm_labels = [h.value for h in classification.harm_types.active_labels]
648
+ explanation_parts.append(f"causing potential harm: {', '.join(harm_labels)}")
649
+
650
+ explanation = " ".join(explanation_parts)
651
+
652
+ # Build metadata
653
+ # Calculate classification label based on confidence
654
+ if classification.threat_probability >= 0.9:
655
+ classification_label = "HIGH_THREAT"
656
+ action = "BLOCK_ALERT"
657
+ elif classification.threat_probability >= 0.75:
658
+ classification_label = "THREAT"
659
+ action = "BLOCK"
660
+ elif classification.threat_probability >= 0.6:
661
+ classification_label = "LIKELY_THREAT"
662
+ action = "BLOCK_WITH_REVIEW"
663
+ elif classification.threat_probability >= 0.4:
664
+ classification_label = "REVIEW"
665
+ action = "MANUAL_REVIEW"
666
+ else:
667
+ classification_label = "FP_LIKELY"
668
+ action = "ALLOW_WITH_LOG"
669
+
670
+ metadata: dict[str, Any] = {
671
+ "is_attack": True,
672
+ "family": classification.threat_family.value,
673
+ "severity": classification.severity.value,
674
+ "classification": classification_label,
675
+ "action": action,
676
+ "risk_score": classification.threat_probability * 100,
677
+ "hierarchical_score": classification.threat_probability,
678
+ "scores": {
679
+ "attack_probability": classification.threat_probability,
680
+ "family_confidence": classification.family_confidence,
681
+ "severity_confidence": classification.severity_confidence,
682
+ "subfamily_confidence": classification.technique_confidence,
683
+ },
684
+ }
685
+
686
+ if classification.primary_technique:
687
+ metadata["primary_technique"] = classification.primary_technique.value
688
+ metadata["technique_confidence"] = classification.technique_confidence
689
+ # Alias for CLI formatter compatibility
690
+ metadata["sub_family"] = classification.primary_technique.value
691
+
692
+ if classification.harm_types:
693
+ metadata["harm_types"] = classification.harm_types.to_dict()
694
+
695
+ # Generate "why_it_hit" explanations
696
+ why_it_hit = self._generate_why_it_hit(classification)
697
+ metadata["why_it_hit"] = why_it_hit
698
+
699
+ # Generate recommended actions
700
+ recommended_actions = self._generate_recommended_actions(classification)
701
+ metadata["recommended_action"] = recommended_actions
702
+
703
+ # Uncertainty flag
704
+ metadata["uncertain"] = (
705
+ classification.family_confidence < 0.5 or
706
+ classification.threat_probability < 0.6
707
+ )
708
+
709
+ # Add voting information if available
710
+ if voting_result:
711
+ metadata["voting"] = {
712
+ "decision": voting_result.decision.value,
713
+ "confidence": voting_result.confidence,
714
+ "preset_used": voting_result.preset_used,
715
+ "decision_rule_triggered": voting_result.decision_rule_triggered,
716
+ "threat_vote_count": voting_result.threat_vote_count,
717
+ "safe_vote_count": voting_result.safe_vote_count,
718
+ "abstain_vote_count": voting_result.abstain_vote_count,
719
+ "weighted_ratio": voting_result.weighted_ratio,
720
+ }
721
+ # Update classification and action from voting
722
+ metadata["classification"] = self._voting_decision_to_classification(
723
+ voting_result.decision, voting_result.confidence
724
+ )
725
+ metadata["action"] = self._voting_decision_to_action(voting_result.decision)
726
+
727
+ return [
728
+ L2Prediction(
729
+ threat_type=threat_type,
730
+ confidence=classification.threat_probability,
731
+ explanation=explanation,
732
+ features_used=[
733
+ f"family={classification.threat_family.value}",
734
+ f"severity={classification.severity.value}",
735
+ f"technique={classification.primary_technique.value if classification.primary_technique else 'none'}", # noqa: E501
736
+ "model=gemma-compact",
737
+ ],
738
+ metadata=metadata,
739
+ )
740
+ ]
741
+
742
+ def _generate_why_it_hit(
743
+ self, classification: GemmaClassificationResult
744
+ ) -> list[str]:
745
+ """Generate human-readable explanations for detection."""
746
+ reasons = []
747
+
748
+ # Threat family reason
749
+ family_descriptions = {
750
+ "prompt_injection": "Prompt injection attempting to override instructions",
751
+ "jailbreak": "Jailbreak attempt to bypass safety guidelines",
752
+ "data_exfiltration": "Data exfiltration pattern detected",
753
+ "encoding_or_obfuscation_attack": "Encoded or obfuscated malicious content",
754
+ "rag_or_context_attack": "RAG or context manipulation attack",
755
+ "tool_or_command_abuse": "Tool or command abuse attempt",
756
+ "toxic_or_policy_violating_content": "Toxic or policy-violating content",
757
+ "other_security": "Security-related threat pattern detected",
758
+ }
759
+ family_desc = family_descriptions.get(
760
+ classification.threat_family.value,
761
+ f"{classification.threat_family.value} threat detected"
762
+ )
763
+ reasons.append(family_desc)
764
+
765
+ # Severity reason
766
+ if classification.severity in (Severity.HIGH, Severity.CRITICAL):
767
+ reasons.append(
768
+ f"{classification.severity.value.upper()} severity - immediate action recommended"
769
+ )
770
+
771
+ # Technique reason
772
+ has_technique = (
773
+ classification.primary_technique
774
+ and classification.primary_technique != PrimaryTechnique.NONE
775
+ )
776
+ if has_technique:
777
+ technique_desc = classification.primary_technique.value.replace("_", " ")
778
+ reasons.append(f"Specific attack technique: {technique_desc}")
779
+
780
+ # Harm types reason
781
+ if classification.harm_types and classification.harm_types.has_active_labels:
782
+ harm_count = classification.harm_types.active_count
783
+ if harm_count == 1:
784
+ harm_label = classification.harm_types.active_labels[0].value.replace("_", " ")
785
+ reasons.append(f"Potential harm type: {harm_label}")
786
+ else:
787
+ reasons.append(f"Multiple harm types detected ({harm_count} categories)")
788
+
789
+ return reasons
790
+
791
+ def _generate_recommended_actions(
792
+ self, classification: GemmaClassificationResult
793
+ ) -> list[str]:
794
+ """Generate recommended actions based on classification."""
795
+ actions = []
796
+
797
+ if classification.severity == Severity.CRITICAL:
798
+ actions.append("CRITICAL threat - BLOCK immediately and alert security team")
799
+ elif classification.severity == Severity.HIGH:
800
+ actions.append("HIGH severity - BLOCK and log for review")
801
+ elif classification.severity == Severity.MEDIUM:
802
+ actions.append("MEDIUM severity - Consider blocking or require additional validation")
803
+ elif classification.severity == Severity.LOW:
804
+ actions.append("LOW severity - Log and monitor, allow with caution")
805
+ else:
806
+ actions.append("Minimal severity - Allow with logging")
807
+
808
+ # Family-specific recommendations
809
+ if classification.threat_family == ThreatFamily.DATA_EXFILTRATION:
810
+ actions.append("Review data access controls and implement DLP policies")
811
+ elif classification.threat_family == ThreatFamily.JAILBREAK:
812
+ actions.append("Block and update guardrails to prevent similar attempts")
813
+ elif classification.threat_family == ThreatFamily.PROMPT_INJECTION:
814
+ actions.append("Sanitize input and validate instruction boundaries")
815
+
816
+ return actions
817
+
818
+ def _apply_scorer(
819
+ self, classification: GemmaClassificationResult, text: str
820
+ ) -> Any:
821
+ """Apply hierarchical threat scorer if available."""
822
+ if not self.scorer:
823
+ return None
824
+
825
+ try:
826
+ from raxe.domain.ml.scoring_models import ThreatScore
827
+
828
+ # Map to ThreatScore format
829
+ threat_score = ThreatScore(
830
+ binary_threat_score=classification.threat_probability,
831
+ binary_safe_score=classification.safe_probability,
832
+ family_confidence=classification.family_confidence,
833
+ subfamily_confidence=classification.technique_confidence,
834
+ binary_proba=[
835
+ classification.safe_probability,
836
+ classification.threat_probability,
837
+ ],
838
+ family_proba=list(classification.family_probabilities),
839
+ subfamily_proba=list(classification.technique_probabilities or [0.0]),
840
+ family_name=classification.threat_family.value,
841
+ subfamily_name=(
842
+ classification.primary_technique.value
843
+ if classification.primary_technique
844
+ else None
845
+ ),
846
+ )
847
+
848
+ return self.scorer.score(threat_score, prompt=text)
849
+ except Exception as e:
850
+ logger.warning("Scorer failed", error=str(e))
851
+ return None
852
+
853
+ @staticmethod
854
+ def _softmax(x: np.ndarray) -> np.ndarray:
855
+ """Apply softmax to logits."""
856
+ exp_x = np.exp(x - np.max(x))
857
+ return exp_x / exp_x.sum()
858
+
859
+ @staticmethod
860
+ def _sigmoid(x: np.ndarray) -> np.ndarray:
861
+ """Apply sigmoid to logits."""
862
+ return 1 / (1 + np.exp(-x))
863
+
864
+ @property
865
+ def model_info(self) -> dict[str, Any]:
866
+ """Return model information."""
867
+ return {
868
+ "name": "Gemma 5-Head Multilabel Classifier",
869
+ "version": self.VERSION,
870
+ "type": "onnx",
871
+ "is_stub": False,
872
+ "size_mb": 300, # Approximate
873
+ "latency_p95_ms": 50,
874
+ "embedding_model": "google/embeddinggemma-300m",
875
+ "embedding_dim": self.EMBEDDING_DIM,
876
+ "heads": [
877
+ "is_threat",
878
+ "threat_family",
879
+ "severity",
880
+ "primary_technique",
881
+ "harm_types",
882
+ ],
883
+ "families": [f.value for f in ThreatFamily],
884
+ "description": (
885
+ "Gemma-based 5-head multilabel classifier with EmbeddingGemma-300M "
886
+ "for threat detection, family classification, severity assessment, "
887
+ "technique identification, and harm type prediction."
888
+ ),
889
+ }
890
+
891
+
892
+ def create_gemma_detector(
893
+ model_dir: str | Path,
894
+ *,
895
+ confidence_threshold: float = 0.5,
896
+ harm_thresholds: dict[str, float] | None = None,
897
+ cache_size: int = 1000,
898
+ scorer: Any | None = None,
899
+ voting_preset: str | None = None,
900
+ ) -> GemmaL2Detector:
901
+ """Factory function to create Gemma L2 detector.
902
+
903
+ Args:
904
+ model_dir: Path to directory containing ONNX models
905
+ confidence_threshold: Threshold for binary threat classification
906
+ harm_thresholds: Per-class thresholds for harm_types multilabel
907
+ cache_size: Size of embedding cache (0 to disable)
908
+ scorer: Optional HierarchicalThreatScorer instance
909
+ voting_preset: Voting preset override (balanced, high_security, low_fp)
910
+
911
+ Returns:
912
+ GemmaL2Detector instance
913
+ """
914
+ return GemmaL2Detector(
915
+ model_dir=model_dir,
916
+ confidence_threshold=confidence_threshold,
917
+ harm_thresholds=harm_thresholds,
918
+ cache_size=cache_size,
919
+ scorer=scorer,
920
+ voting_preset=voting_preset,
921
+ )