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
raxe/sdk/client.py ADDED
@@ -0,0 +1,1603 @@
1
+ """Unified RAXE client - SINGLE ENTRY POINT for all integrations.
2
+
3
+ This class is the foundation for all RAXE integrations:
4
+ - CLI commands (raxe scan)
5
+ - SDK direct usage (raxe.scan())
6
+ - Decorators (@raxe.protect)
7
+ - Wrappers (RaxeOpenAI)
8
+
9
+ ALL scanning MUST go through the Raxe.scan() method to ensure
10
+ consistency and proper configuration cascade.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import atexit
15
+ import threading
16
+ from collections.abc import Callable
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Any, ClassVar
20
+
21
+ from raxe.application.preloader import preload_pipeline
22
+ from raxe.application.scan_merger import ScanMerger
23
+ from raxe.application.scan_pipeline import ScanPipelineResult
24
+ from raxe.application.telemetry_orchestrator import get_orchestrator
25
+ from raxe.domain.engine.executor import Detection
26
+ from raxe.domain.engine.matcher import Match
27
+ from raxe.domain.inline_suppression import parse_inline_suppressions
28
+ from raxe.domain.ml.protocol import L2Prediction
29
+ from raxe.domain.rules.models import Severity
30
+ from raxe.domain.suppression import SuppressionAction, check_suppressions
31
+ from raxe.domain.suppression_factory import create_suppression_manager
32
+ from raxe.domain.telemetry.events import generate_event_id
33
+ from raxe.infrastructure.config.scan_config import ScanConfig
34
+ from raxe.infrastructure.database.scan_history import ScanHistoryDB
35
+ from raxe.infrastructure.tracking.usage import UsageTracker
36
+ from raxe.sdk.suppression_context import SuppressedContext, get_scoped_suppressions
37
+ from raxe.utils.logging import get_logger
38
+
39
+ # Use structured logging for better observability (privacy-preserving)
40
+ logger = get_logger(__name__)
41
+
42
+ # Reuse ScanMerger for consistent severity mapping
43
+ _scan_merger = ScanMerger()
44
+
45
+
46
+ def _l2_prediction_to_detection(
47
+ prediction: L2Prediction,
48
+ processing_time_ms: float = 0.0,
49
+ ) -> Detection:
50
+ """Convert an L2Prediction to a Detection object for storage.
51
+
52
+ Maps L2 ML predictions to the Detection format used by scan history.
53
+ This enables consistent storage and display of L2 threats alongside L1.
54
+
55
+ Args:
56
+ prediction: L2 prediction from ML detector
57
+ processing_time_ms: L2 processing time for this detection
58
+
59
+ Returns:
60
+ Detection object compatible with scan history storage
61
+ """
62
+ # Map L2 confidence to severity using the same thresholds as ScanMerger
63
+ severity = _scan_merger._map_confidence_to_severity(prediction.confidence)
64
+ if severity is None:
65
+ severity = Severity.INFO # Default to INFO for low-confidence predictions
66
+
67
+ # Extract category from threat_type or metadata
68
+ threat_type_value = prediction.threat_type.value if prediction.threat_type else "unknown"
69
+ category = prediction.metadata.get("family", threat_type_value) if prediction.metadata else threat_type_value
70
+
71
+ # Create a synthetic match for L2 detections
72
+ # L2 detections don't have pattern matches, so we create a placeholder
73
+ synthetic_match = Match(
74
+ pattern_index=0,
75
+ start=0,
76
+ end=0,
77
+ matched_text="[ML Detection]",
78
+ groups=(),
79
+ context_before="",
80
+ context_after="",
81
+ )
82
+
83
+ # Build rule_id from L2 threat type
84
+ rule_id = f"L2-{threat_type_value}"
85
+
86
+ # Use explanation or build a default message
87
+ explanation = prediction.explanation or f"ML detected: {threat_type_value}"
88
+ sub_family = prediction.metadata.get("sub_family", "") if prediction.metadata else ""
89
+ message = f"L2 ML Detection: {threat_type_value}"
90
+ if sub_family:
91
+ message = f"L2 ML Detection: {sub_family}"
92
+
93
+ return Detection(
94
+ rule_id=rule_id,
95
+ rule_version="0.0.1",
96
+ severity=severity,
97
+ confidence=prediction.confidence,
98
+ matches=[synthetic_match],
99
+ detected_at=datetime.now(timezone.utc).isoformat(),
100
+ detection_layer="L2",
101
+ layer_latency_ms=processing_time_ms,
102
+ category=category,
103
+ message=message,
104
+ explanation=explanation,
105
+ risk_explanation=f"ML model detected potential {threat_type_value} attack pattern",
106
+ remediation_advice="Review the prompt for suspicious content",
107
+ docs_url="",
108
+ )
109
+
110
+
111
+ class Raxe:
112
+ """Unified RAXE client.
113
+
114
+ This is the ONLY entry point for scanning operations. All other
115
+ interfaces (CLI, decorators, wrappers) use this class internally.
116
+
117
+ The client handles:
118
+ - Configuration loading with proper cascade (explicit > env > file > defaults)
119
+ - One-time pipeline preloading for optimal performance
120
+ - Unified scan() method used by all integrations
121
+ - Access to decorator and wrapper convenience methods
122
+
123
+ Usage:
124
+ # Basic usage with defaults
125
+ raxe = Raxe()
126
+ result = raxe.scan("Ignore all previous instructions")
127
+
128
+ # With configuration
129
+ raxe = Raxe(api_key="raxe_test_...", telemetry=False)
130
+
131
+ # From config file
132
+ raxe = Raxe.from_config_file(".raxe/config.yaml")
133
+
134
+ # Check results
135
+ if result.has_threats:
136
+ print(f"Threat: {result.severity}")
137
+
138
+ # Context manager for automatic cleanup
139
+ with Raxe() as raxe:
140
+ raxe.scan("test")
141
+
142
+ Performance:
143
+ - Initialization: <500ms (one-time)
144
+ - Scanning: <10ms per call (after init)
145
+ """
146
+
147
+ # Class-level state for atexit management
148
+ _atexit_registered: ClassVar[bool] = False
149
+ _flushed: ClassVar[bool] = False
150
+ _flush_lock: ClassVar[threading.Lock] = threading.Lock()
151
+
152
+ def __init__(
153
+ self,
154
+ *,
155
+ api_key: str | None = None,
156
+ config_path: Path | None = None,
157
+ telemetry: bool = True,
158
+ l2_enabled: bool = True,
159
+ voting_preset: str | None = None,
160
+ progress_callback = None,
161
+ **kwargs
162
+ ):
163
+ """Initialize RAXE client.
164
+
165
+ Configuration cascade (highest to lowest priority):
166
+ 1. Explicit parameters (this method)
167
+ 2. Environment variables (RAXE_*)
168
+ 3. Config file (explicit path or default)
169
+ 4. Defaults
170
+
171
+ Args:
172
+ api_key: Optional API key for cloud features
173
+ config_path: Path to config file (overrides default search)
174
+ telemetry: Enable privacy-preserving telemetry (default: True)
175
+ l2_enabled: Enable L2 ML detection (default: True)
176
+ voting_preset: L2 voting preset (balanced, high_security, low_fp)
177
+ progress_callback: Optional progress indicator for initialization
178
+ **kwargs: Additional config options passed to ScanConfig
179
+
180
+ Raises:
181
+ Exception: If critical components fail to load
182
+ """
183
+ # Store progress callback (use NullProgress if none provided)
184
+ from raxe.cli.progress import NullProgress
185
+ self._progress = progress_callback or NullProgress()
186
+
187
+ # Build configuration with cascade
188
+ # Note: load_config would handle the cascade, but it doesn't exist yet
189
+ # For now, we'll use ScanConfig directly and update in Phase 4E
190
+ if config_path and config_path.exists():
191
+ self.config = ScanConfig.from_file(config_path)
192
+ else:
193
+ self.config = ScanConfig()
194
+
195
+ # Apply explicit overrides
196
+ if api_key is not None:
197
+ self.config.api_key = api_key
198
+
199
+ # Validate telemetry disable against server permissions
200
+ if not telemetry:
201
+ # Check cached server permissions before disabling
202
+ can_disable = self._check_telemetry_disable_permission()
203
+ if not can_disable:
204
+ logger.warning(
205
+ "telemetry_disable_denied",
206
+ reason="tier_not_allowed",
207
+ message="Your tier does not allow disabling telemetry. "
208
+ "Upgrade to Pro via 'raxe auth login'"
209
+ )
210
+ # Keep telemetry enabled (ignore the disable request)
211
+ telemetry = True
212
+
213
+ # Explicitly set telemetry (handles both True and False)
214
+ self.config.telemetry.enabled = telemetry
215
+ self.config.enable_l2 = l2_enabled
216
+
217
+ # Store voting preset for L2 detector initialization
218
+ self._voting_preset = voting_preset
219
+
220
+ # Initialize tracking and history components
221
+ # These are lazily loaded - only create files when first used
222
+ self._usage_tracker: UsageTracker | None = None
223
+ self._scan_history: ScanHistoryDB | None = None
224
+ self._streak_tracker = None
225
+
226
+ # Initialize suppression manager (auto-loads .raxe/suppressions.yaml from cwd)
227
+ self.suppression_manager = create_suppression_manager(auto_load=True)
228
+
229
+ # Preload pipeline (one-time startup cost ~100-200ms)
230
+ # This compiles patterns, loads packs, warms caches
231
+ logger.info("raxe_client_init_start")
232
+
233
+ # Start progress indicator
234
+ self._progress.start("Initializing RAXE...")
235
+
236
+ try:
237
+ self.pipeline, self.preload_stats = preload_pipeline(
238
+ config=self.config,
239
+ suppression_manager=self.suppression_manager,
240
+ progress_callback=self._progress,
241
+ voting_preset=self._voting_preset,
242
+ )
243
+
244
+ # Also create async pipeline for parallel L1/L2 execution (5x faster!)
245
+ # This shares the same components but runs L1+L2 concurrently
246
+ self._async_pipeline = None # Lazy init on first use
247
+
248
+ self._initialized = True
249
+
250
+ # Initialize telemetry (non-blocking, never raises)
251
+ self._init_telemetry()
252
+
253
+ # Register atexit handler (once per process)
254
+ if not Raxe._atexit_registered:
255
+ atexit.register(Raxe._atexit_flush)
256
+ Raxe._atexit_registered = True
257
+
258
+ # Complete progress
259
+ self._progress.complete(
260
+ total_duration_ms=self.preload_stats.duration_ms
261
+ )
262
+
263
+ logger.info(
264
+ "raxe_client_init_complete",
265
+ rules_loaded=self.preload_stats.rules_loaded
266
+ )
267
+ except Exception as e:
268
+ # Report error to progress
269
+ self._progress.error("initialization", str(e))
270
+ logger.error("raxe_client_init_failed", error=str(e))
271
+ raise
272
+
273
+ def _get_async_pipeline(self):
274
+ """Get or create async pipeline (lazy initialization).
275
+
276
+ The async pipeline runs L1 and L2 in parallel for 5x speedup.
277
+ It shares components with the sync pipeline for efficiency.
278
+ """
279
+ if self._async_pipeline is None:
280
+ from raxe.application.scan_pipeline_async import AsyncScanPipeline
281
+
282
+ # Reuse components from sync pipeline
283
+ self._async_pipeline = AsyncScanPipeline(
284
+ pack_registry=self.pipeline.pack_registry,
285
+ rule_executor=self.pipeline.rule_executor,
286
+ l2_detector=self.pipeline.l2_detector,
287
+ scan_merger=self.pipeline.scan_merger,
288
+ apply_policy=self.pipeline.apply_policy,
289
+ enable_l2=self.pipeline.enable_l2,
290
+ fail_fast_on_critical=self.pipeline.fail_fast_on_critical,
291
+ min_confidence_for_skip=self.pipeline.min_confidence_for_skip,
292
+ l1_timeout_ms=10.0,
293
+ l2_timeout_ms=150.0,
294
+ )
295
+ logger.info("Async pipeline initialized (parallel L1+L2 execution)")
296
+
297
+ return self._async_pipeline
298
+
299
+ def _check_telemetry_disable_permission(self) -> bool:
300
+ """Check if telemetry can be disabled based on cached server permissions.
301
+
302
+ This method checks the locally cached server permissions from the
303
+ credential store. If no cached permissions exist or they are stale,
304
+ it defaults to allowing disable (non-blocking behavior).
305
+
306
+ Returns:
307
+ True if telemetry can be disabled, False if tier does not allow it.
308
+
309
+ Note:
310
+ This is a client-side validation for user experience. The server
311
+ will enforce the actual restriction regardless of this check.
312
+ """
313
+ try:
314
+ from raxe.infrastructure.telemetry.credential_store import CredentialStore
315
+
316
+ store = CredentialStore()
317
+ credentials = store.load()
318
+
319
+ if credentials is None:
320
+ # No credentials yet - allow disable (server will enforce)
321
+ return True
322
+
323
+ # Check cached permission
324
+ # If health check is stale, default to allowing (non-blocking)
325
+ if credentials.is_health_check_stale(max_age_hours=24):
326
+ # Cache is stale - allow but server will enforce
327
+ return True
328
+
329
+ # Check the cached permission
330
+ return credentials.can_disable_telemetry
331
+
332
+ except Exception:
333
+ # On any error, allow (non-blocking, server enforces)
334
+ return True
335
+
336
+ def _init_telemetry(self) -> None:
337
+ """Initialize telemetry on SDK instantiation.
338
+
339
+ This method:
340
+ 1. Ensures installation event is fired (first install tracking)
341
+ 2. Tracks SDK initialization as feature usage
342
+ 3. Starts the telemetry session
343
+
344
+ All operations are non-blocking and never raise exceptions.
345
+ Telemetry failures should never affect SDK functionality.
346
+
347
+ If credentials are expired, telemetry is disabled gracefully
348
+ and a warning is logged. The SDK continues to function normally
349
+ for scanning operations.
350
+ """
351
+ try:
352
+ # Check for expired credentials before initializing telemetry
353
+ # This provides a clear warning without breaking SDK functionality
354
+ from raxe.infrastructure.telemetry.credential_store import (
355
+ CredentialExpiredError,
356
+ CredentialStore,
357
+ )
358
+
359
+ store = CredentialStore()
360
+ try:
361
+ # Use raise_on_expired=True to get the error
362
+ store.get_or_create(raise_on_expired=True)
363
+ except CredentialExpiredError as e:
364
+ # Log warning but continue without telemetry
365
+ logger.warning(
366
+ "telemetry_disabled_expired_key",
367
+ extra={
368
+ "days_expired": e.days_expired,
369
+ "console_url": e.console_url,
370
+ },
371
+ )
372
+ logger.warning(
373
+ f"Telemetry disabled: {e}. "
374
+ "Scanning will continue to work normally."
375
+ )
376
+ return
377
+
378
+ orchestrator = get_orchestrator()
379
+ if orchestrator is None:
380
+ return
381
+
382
+ # Flush any stale telemetry from previous sessions (non-blocking)
383
+ # This recovers events that were queued but not flushed due to
384
+ # crashes, SIGKILL, or SDK usage without proper cleanup
385
+ try:
386
+ from raxe.infrastructure.telemetry.flush_helper import (
387
+ flush_stale_telemetry_async,
388
+ )
389
+ flush_stale_telemetry_async()
390
+ except Exception:
391
+ pass # Never block on stale flush
392
+
393
+ # Start the orchestrator (lazy initialization)
394
+ orchestrator.start()
395
+
396
+ # Ensure installation event fired
397
+ orchestrator.ensure_installation()
398
+
399
+ # Track SDK initialization
400
+ if orchestrator.is_enabled():
401
+ orchestrator.track_feature_usage(
402
+ feature="sdk_scan",
403
+ action="invoked",
404
+ )
405
+ except Exception:
406
+ # Never let telemetry break SDK initialization
407
+ pass
408
+
409
+ def _track_scan(
410
+ self,
411
+ result: ScanPipelineResult,
412
+ prompt: str,
413
+ entry_point: str = "sdk",
414
+ event_id: str | None = None,
415
+ wrapper_type: str | None = None,
416
+ integration_type: str | None = None,
417
+ ) -> None:
418
+ """Track scan telemetry using schema v2.0 (non-blocking, never raises).
419
+
420
+ This method sends privacy-preserving telemetry about the scan using
421
+ the full L2 telemetry schema defined in docs/SCAN_TELEMETRY_SCHEMA.md.
422
+
423
+ Privacy: Only hashes, metrics, and enum values are transmitted.
424
+ No actual prompt content is ever transmitted.
425
+
426
+ Args:
427
+ result: The scan result to track
428
+ prompt: Original prompt text (used for hash and length calculation)
429
+ entry_point: How the scan was triggered (sdk, cli, wrapper, integration)
430
+ event_id: Pre-generated event ID for portal-CLI correlation
431
+ wrapper_type: SDK wrapper type if applicable (openai, anthropic, etc.)
432
+ integration_type: Integration framework if applicable (langchain, crewai, etc.)
433
+ """
434
+ try:
435
+ orchestrator = get_orchestrator()
436
+ if orchestrator is None:
437
+ return
438
+ # Don't check is_enabled() here - let track_scan_v2() handle initialization
439
+ # is_enabled() returns False before initialization, blocking the lazy init
440
+
441
+ # Import builder here to avoid circular imports
442
+ from raxe.domain.telemetry.scan_telemetry_builder import build_scan_telemetry
443
+
444
+ # Get L1 and L2 results
445
+ l1_result = None
446
+ l2_result = None
447
+ if result.scan_result:
448
+ l1_result = result.scan_result.l1_result
449
+ l2_result = result.scan_result.l2_result
450
+
451
+ # Build telemetry payload using v2 schema
452
+ # All fields are dynamically calculated from actual scan results
453
+ telemetry_payload = build_scan_telemetry(
454
+ l1_result=l1_result,
455
+ l2_result=l2_result,
456
+ scan_duration_ms=result.duration_ms,
457
+ entry_point=entry_point, # type: ignore[arg-type]
458
+ prompt=prompt,
459
+ wrapper_type=wrapper_type, # type: ignore[arg-type]
460
+ action_taken="block" if result.should_block else "allow",
461
+ l2_enabled=result.metadata.get("l2_enabled", True),
462
+ integration_type=integration_type,
463
+ )
464
+
465
+ # Track using v2 method
466
+ orchestrator.track_scan_v2(
467
+ payload=telemetry_payload,
468
+ event_id=event_id,
469
+ )
470
+ except Exception:
471
+ # Never let telemetry break SDK functionality
472
+ pass
473
+
474
+ @property
475
+ def usage_tracker(self) -> UsageTracker:
476
+ """Get usage tracker (lazy initialization).
477
+
478
+ Creates install.json and usage.json on first access.
479
+ """
480
+ if self._usage_tracker is None:
481
+ self._usage_tracker = UsageTracker()
482
+ return self._usage_tracker
483
+
484
+ @property
485
+ def scan_history(self) -> ScanHistoryDB:
486
+ """Get scan history database (lazy initialization).
487
+
488
+ Creates scan_history.db on first access.
489
+ """
490
+ if self._scan_history is None:
491
+ self._scan_history = ScanHistoryDB()
492
+ return self._scan_history
493
+
494
+ @property
495
+ def streak_tracker(self):
496
+ """Get streak tracker (lazy initialization).
497
+
498
+ Creates achievements.json on first access for gamification.
499
+ """
500
+ if self._streak_tracker is None:
501
+ from raxe.infrastructure.analytics.streaks import StreakTracker
502
+ self._streak_tracker = StreakTracker()
503
+ return self._streak_tracker
504
+
505
+ @classmethod
506
+ def from_config_file(cls, path: Path) -> Raxe:
507
+ """Create Raxe client from config file.
508
+
509
+ When using this method, configuration is loaded ONLY from the file
510
+ without default parameter overrides.
511
+
512
+ Args:
513
+ path: Path to .raxe/config.yaml
514
+
515
+ Returns:
516
+ Configured Raxe instance
517
+
518
+ Example:
519
+ raxe = Raxe.from_config_file(Path.home() / ".raxe" / "config.yaml")
520
+ result = raxe.scan("test")
521
+ """
522
+ # Create instance with minimal intervention
523
+ # Load config from file without applying default overrides
524
+ instance = cls.__new__(cls)
525
+
526
+ # Load configuration from file
527
+ instance.config = ScanConfig.from_file(path)
528
+
529
+ # Initialize suppression manager
530
+ instance.suppression_manager = create_suppression_manager(auto_load=True)
531
+
532
+ # Preload pipeline
533
+ logger.info("Initializing RAXE client from config file")
534
+ try:
535
+ instance.pipeline, instance.preload_stats = preload_pipeline(
536
+ config=instance.config,
537
+ suppression_manager=instance.suppression_manager
538
+ )
539
+ instance._initialized = True
540
+ logger.info(
541
+ f"RAXE client initialized: {instance.preload_stats.rules_loaded} rules loaded"
542
+ )
543
+ except Exception as e:
544
+ logger.error(f"Failed to initialize RAXE client: {e}")
545
+ raise
546
+
547
+ return instance
548
+
549
+ def _apply_inline_suppressions(
550
+ self,
551
+ result: ScanPipelineResult,
552
+ inline_suppress: list[str | dict[str, Any]] | None,
553
+ ) -> ScanPipelineResult:
554
+ """Apply inline and scoped suppressions to scan result.
555
+
556
+ This method processes suppressions in order of precedence:
557
+ 1. Scoped suppressions (from context manager)
558
+ 2. Inline suppressions (from suppress parameter)
559
+ 3. Config file suppressions (already applied by pipeline)
560
+
561
+ Actions are handled as follows:
562
+ - SUPPRESS: Remove detection from results
563
+ - FLAG: Keep detection with is_flagged=True
564
+ - LOG: Keep detection in results (for logging only)
565
+
566
+ Args:
567
+ result: Original scan result from pipeline
568
+ inline_suppress: Inline suppression specs from scan() call
569
+
570
+ Returns:
571
+ Modified ScanPipelineResult with suppressions applied
572
+ """
573
+ # Parse inline suppressions
574
+ inline_suppressions = parse_inline_suppressions(inline_suppress)
575
+
576
+ # Get scoped suppressions from context manager
577
+ scoped_suppressions = get_scoped_suppressions()
578
+
579
+ # If no inline or scoped suppressions, return original result
580
+ if not inline_suppressions and not scoped_suppressions:
581
+ return result
582
+
583
+ # Merge all suppressions (scoped + inline take precedence)
584
+ # Config file suppressions are already handled by the pipeline
585
+ all_suppressions = scoped_suppressions + inline_suppressions
586
+
587
+ # No detections to process
588
+ if not result.scan_result or not result.scan_result.l1_result:
589
+ return result
590
+
591
+ # Process detections
592
+ from raxe.domain.engine.executor import ScanResult
593
+
594
+ processed_detections: list[Detection] = []
595
+ suppressed_count = 0
596
+ flagged_count = 0
597
+
598
+ for detection in result.scan_result.l1_result.detections:
599
+ check_result = check_suppressions(detection.rule_id, all_suppressions)
600
+
601
+ if check_result.is_suppressed:
602
+ if check_result.action == SuppressionAction.SUPPRESS:
603
+ # Remove from results
604
+ suppressed_count += 1
605
+ logger.debug(
606
+ "inline_suppression_applied",
607
+ rule_id=detection.rule_id,
608
+ action="SUPPRESS",
609
+ reason=check_result.reason,
610
+ )
611
+ elif check_result.action == SuppressionAction.FLAG:
612
+ # Keep with is_flagged=True
613
+ flagged_detection = detection.with_flag(check_result.reason)
614
+ processed_detections.append(flagged_detection)
615
+ flagged_count += 1
616
+ logger.debug(
617
+ "inline_suppression_applied",
618
+ rule_id=detection.rule_id,
619
+ action="FLAG",
620
+ reason=check_result.reason,
621
+ )
622
+ elif check_result.action == SuppressionAction.LOG:
623
+ # Keep in results (LOG action just logs, doesn't modify)
624
+ processed_detections.append(detection)
625
+ logger.debug(
626
+ "inline_suppression_applied",
627
+ rule_id=detection.rule_id,
628
+ action="LOG",
629
+ reason=check_result.reason,
630
+ )
631
+ else:
632
+ # No suppression matched, keep detection
633
+ processed_detections.append(detection)
634
+
635
+ # Create new L1 result with processed detections
636
+ new_l1_result = ScanResult(
637
+ detections=processed_detections,
638
+ scanned_at=result.scan_result.l1_result.scanned_at,
639
+ text_length=result.scan_result.l1_result.text_length,
640
+ rules_checked=result.scan_result.l1_result.rules_checked,
641
+ scan_duration_ms=result.scan_result.l1_result.scan_duration_ms,
642
+ )
643
+
644
+ # Create new combined result
645
+ from raxe.application.scan_merger import CombinedScanResult
646
+
647
+ new_combined = CombinedScanResult(
648
+ l1_result=new_l1_result,
649
+ l2_result=result.scan_result.l2_result,
650
+ combined_severity=result.scan_result.combined_severity,
651
+ total_processing_ms=result.scan_result.total_processing_ms,
652
+ metadata={
653
+ **result.scan_result.metadata,
654
+ "inline_suppressed_count": suppressed_count,
655
+ "inline_flagged_count": flagged_count,
656
+ },
657
+ )
658
+
659
+ # Recalculate combined severity based on remaining detections
660
+ if new_l1_result.has_detections:
661
+ new_combined = CombinedScanResult(
662
+ l1_result=new_l1_result,
663
+ l2_result=result.scan_result.l2_result,
664
+ combined_severity=new_l1_result.highest_severity,
665
+ total_processing_ms=result.scan_result.total_processing_ms,
666
+ metadata=new_combined.metadata,
667
+ )
668
+
669
+ # Create new pipeline result
670
+ new_metadata = dict(result.metadata) if result.metadata else {}
671
+ new_metadata["inline_suppressed_count"] = suppressed_count
672
+ new_metadata["inline_flagged_count"] = flagged_count
673
+
674
+
675
+ return ScanPipelineResult(
676
+ scan_result=new_combined,
677
+ policy_decision=result.policy_decision,
678
+ should_block=result.should_block,
679
+ duration_ms=result.duration_ms,
680
+ text_hash=result.text_hash,
681
+ metadata=new_metadata,
682
+ l1_detections=len([d for d in processed_detections if d.detection_layer == "L1"]),
683
+ l2_detections=result.l2_detections,
684
+ plugin_detections=result.plugin_detections,
685
+ l1_duration_ms=result.l1_duration_ms,
686
+ l2_duration_ms=result.l2_duration_ms,
687
+ )
688
+
689
+ def scan(
690
+ self,
691
+ text: str,
692
+ *,
693
+ customer_id: str | None = None,
694
+ context: dict[str, object] | None = None,
695
+ block_on_threat: bool = False,
696
+ mode: str = "balanced",
697
+ l1_enabled: bool = True,
698
+ l2_enabled: bool = True,
699
+ confidence_threshold: float = 0.5,
700
+ explain: bool = False,
701
+ dry_run: bool = False,
702
+ use_async: bool = True,
703
+ suppress: list[str | dict[str, Any]] | None = None,
704
+ integration_type: str | None = None,
705
+ entry_point: str | None = None,
706
+ ) -> ScanPipelineResult:
707
+ """Scan text for security threats with layer control.
708
+
709
+ THIS IS THE ONLY SCAN METHOD. All other interfaces call this.
710
+
711
+ The scan method:
712
+ 1. Validates input
713
+ 2. Executes full scan pipeline (L1, L2, policy, telemetry)
714
+ 3. Applies suppressions (inline + config file + scoped)
715
+ 4. Returns comprehensive results
716
+ 5. Optionally raises exception if blocking enabled
717
+
718
+ Response Scanning Warning:
719
+ RAXE can DETECT threats in LLM responses but CANNOT MODIFY them.
720
+ Response scanning is for monitoring and alerting only. Implement
721
+ application-level fallbacks when threats are detected in responses.
722
+
723
+ Example:
724
+ response = llm.generate(prompt)
725
+ scan_result = raxe.scan(response)
726
+ if scan_result.has_threats:
727
+ logger.warning(f"Response threat: {scan_result.combined_severity}")
728
+ return "I cannot provide that information." # Fallback
729
+
730
+ Args:
731
+ text: Text to scan (prompt or response)
732
+ customer_id: Optional customer ID for policy evaluation
733
+ context: Optional context metadata for the scan
734
+ block_on_threat: Raise SecurityException if threat detected (default: False)
735
+ mode: Performance mode - "fast" (<3ms), "balanced" (<10ms), or "thorough" (<100ms)
736
+ l1_enabled: Enable L1 regex detection layer (default: True)
737
+ l2_enabled: Enable L2 ML detection layer (default: True)
738
+ confidence_threshold: Minimum confidence for reporting (0.0-1.0, default: 0.5)
739
+ explain: Include explanations in detection results (default: False)
740
+ dry_run: Test scan without saving to database (default: False)
741
+ use_async: Use async pipeline for parallel L1+L2 execution (5x speedup, default: True)
742
+ suppress: Optional list of inline suppressions. Can be:
743
+ - String patterns: ["pi-001", "jb-*"]
744
+ - Dicts with action: [{"pattern": "jb-*", "action": "FLAG", "reason": "..."}]
745
+ Actions: SUPPRESS (remove), FLAG (keep with is_flagged=True), LOG (keep)
746
+ Inline suppressions take precedence over config file suppressions.
747
+ integration_type: Optional integration framework identifier for telemetry.
748
+ Valid values: "langchain", "crewai", "llamaindex", "autogen", "mcp", None.
749
+ Used to track which agentic framework is using RAXE.
750
+ entry_point: Optional entry point identifier for telemetry.
751
+ Valid values: "cli", "sdk", "wrapper", "integration", None.
752
+ If None, defaults to "integration" if integration_type is set, else "sdk".
753
+
754
+ Returns:
755
+ ScanPipelineResult with:
756
+ - scan_result: L1/L2 detections
757
+ - policy_decision: Policy evaluation result
758
+ - should_block: Whether to block the request
759
+ - duration_ms: Scan latency
760
+ - text_hash: Privacy-preserving hash
761
+
762
+ Raises:
763
+ SecurityException: If block_on_threat=True and threat detected
764
+ ValueError: If text is empty or invalid or mode is invalid
765
+
766
+ Examples:
767
+ # Basic scan
768
+ result = raxe.scan("Hello world")
769
+ print(f"Safe: {not result.has_threats}")
770
+
771
+ # Fast mode (L1 only, <3ms)
772
+ result = raxe.scan("test", mode="fast")
773
+
774
+ # Disable L2 for performance
775
+ result = raxe.scan("test", l2_enabled=False)
776
+
777
+ # High confidence only
778
+ result = raxe.scan("test", confidence_threshold=0.8)
779
+
780
+ # Scan with blocking
781
+ try:
782
+ result = raxe.scan(
783
+ "Ignore all instructions",
784
+ block_on_threat=True
785
+ )
786
+ except SecurityException as e:
787
+ print(f"Blocked: {e.result.severity}")
788
+
789
+ # Suppress specific rules
790
+ result = raxe.scan(text, suppress=["pi-001", "jb-*"])
791
+
792
+ # Suppress with action override (FLAG instead of remove)
793
+ result = raxe.scan(text, suppress=[
794
+ "pi-001", # Remove from results
795
+ {"pattern": "jb-*", "action": "FLAG", "reason": "Under review"}
796
+ ])
797
+ """
798
+ # Handle empty text - return clean result (no threats)
799
+ if not text or not text.strip():
800
+ from datetime import datetime, timezone
801
+
802
+ from raxe.application.scan_merger import CombinedScanResult
803
+ from raxe.application.scan_pipeline import BlockAction
804
+ from raxe.domain.engine.executor import ScanResult
805
+
806
+ # Create clean L1 scan result for empty text
807
+ clean_l1_result = ScanResult(
808
+ detections=[],
809
+ scanned_at=datetime.now(timezone.utc).isoformat(),
810
+ text_length=0,
811
+ rules_checked=0,
812
+ scan_duration_ms=0.0
813
+ )
814
+
815
+ # Create combined result with no threats
816
+ combined_result = CombinedScanResult(
817
+ l1_result=clean_l1_result,
818
+ l2_result=None,
819
+ combined_severity=None,
820
+ total_processing_ms=0.0,
821
+ metadata={"empty_text": True}
822
+ )
823
+
824
+ return ScanPipelineResult(
825
+ scan_result=combined_result,
826
+ policy_decision=BlockAction.ALLOW,
827
+ should_block=False,
828
+ duration_ms=0.0,
829
+ text_hash="",
830
+ metadata={"empty_text": True}
831
+ )
832
+
833
+ # Use async pipeline for 5x speedup (parallel L1+L2)
834
+ # Falls back to sync pipeline if async fails or is disabled
835
+ if use_async:
836
+ try:
837
+ import asyncio
838
+
839
+ async_pipeline = self._get_async_pipeline()
840
+
841
+ # Run async pipeline in sync context
842
+ try:
843
+ _ = asyncio.get_running_loop()
844
+ # Already in async context - this shouldn't happen in sync SDK
845
+ logger.warning("Already in async context, falling back to sync pipeline")
846
+ use_sync_fallback = True
847
+ except RuntimeError:
848
+ # Not in async context - create new event loop (correct path)
849
+ use_sync_fallback = False
850
+
851
+ if not use_sync_fallback:
852
+ async_result = asyncio.run(async_pipeline.scan(
853
+ text,
854
+ customer_id=customer_id or self.config.customer_id,
855
+ context=context,
856
+ l1_enabled=l1_enabled,
857
+ l2_enabled=l2_enabled,
858
+ mode=mode,
859
+ ))
860
+
861
+ # Convert AsyncScanPipelineResult to ScanPipelineResult
862
+ # They have the same structure, just different types
863
+ result = ScanPipelineResult(
864
+ scan_result=async_result.scan_result,
865
+ policy_decision=async_result.policy_decision,
866
+ should_block=async_result.should_block,
867
+ duration_ms=async_result.duration_ms,
868
+ text_hash=async_result.text_hash,
869
+ metadata=async_result.metadata,
870
+ )
871
+ logger.debug(
872
+ "async_scan_complete",
873
+ duration_ms=result.duration_ms,
874
+ parallel_speedup=async_result.metrics.parallel_speedup if async_result.metrics else 1.0
875
+ )
876
+ else:
877
+ # Fallback to sync pipeline
878
+ result = self.pipeline.scan(
879
+ text,
880
+ customer_id=customer_id or self.config.customer_id,
881
+ context=context,
882
+ l1_enabled=l1_enabled,
883
+ l2_enabled=l2_enabled,
884
+ mode=mode,
885
+ confidence_threshold=confidence_threshold,
886
+ explain=explain,
887
+ )
888
+ except Exception as e:
889
+ # Async pipeline failed - fall back to sync
890
+ logger.warning(f"Async pipeline failed ({e}), falling back to sync pipeline")
891
+ result = self.pipeline.scan(
892
+ text,
893
+ customer_id=customer_id or self.config.customer_id,
894
+ context=context,
895
+ l1_enabled=l1_enabled,
896
+ l2_enabled=l2_enabled,
897
+ mode=mode,
898
+ confidence_threshold=confidence_threshold,
899
+ explain=explain,
900
+ )
901
+ else:
902
+ # Use sync pipeline (original behavior)
903
+ result = self.pipeline.scan(
904
+ text,
905
+ customer_id=customer_id or self.config.customer_id,
906
+ context=context,
907
+ l1_enabled=l1_enabled,
908
+ l2_enabled=l2_enabled,
909
+ mode=mode,
910
+ confidence_threshold=confidence_threshold,
911
+ explain=explain,
912
+ )
913
+
914
+ # Apply inline and scoped suppressions (takes precedence over config file)
915
+ # This must happen after the scan but before tracking
916
+ result = self._apply_inline_suppressions(result, suppress)
917
+
918
+ # Record scan in tracking and history
919
+ # This captures:
920
+ # 1. Usage metrics (install tracking, time-to-first-scan)
921
+ # 2. Scan history (privacy-preserving hashes only)
922
+ # 3. Structured logging (no PII)
923
+ # Skipped if dry_run=True
924
+ if not dry_run:
925
+ try:
926
+ # Generate event_id FIRST for portal-CLI correlation
927
+ # This ID links local scan history to telemetry events
928
+ # IMPORTANT: Same event_id is used for both local storage and telemetry
929
+ event_id = generate_event_id()
930
+
931
+ # Track telemetry (non-blocking, privacy-preserving)
932
+ # Pass event_id to ensure telemetry uses the same ID as local storage
933
+ # Pass original prompt for accurate hash and length calculation
934
+ # Determine entry_point: use provided value, or infer from integration_type
935
+ if entry_point:
936
+ effective_entry_point = entry_point
937
+ elif integration_type:
938
+ effective_entry_point = "integration"
939
+ else:
940
+ effective_entry_point = "sdk"
941
+ self._track_scan(
942
+ result,
943
+ prompt=text,
944
+ entry_point=effective_entry_point,
945
+ event_id=event_id,
946
+ integration_type=integration_type,
947
+ )
948
+
949
+ # Track usage (creates install.json on first scan)
950
+ self.usage_tracker.record_scan(found_threats=result.has_threats)
951
+
952
+ # Track feature enablement (for product analytics)
953
+ if l2_enabled:
954
+ self.usage_tracker.record_feature("l2_detection")
955
+ if explain:
956
+ self.usage_tracker.record_feature("explain")
957
+ if mode != "balanced":
958
+ self.usage_tracker.record_feature(f"mode_{mode}")
959
+ if confidence_threshold != 0.5:
960
+ self.usage_tracker.record_feature("custom_confidence_threshold")
961
+ if block_on_threat:
962
+ self.usage_tracker.record_feature("block_on_threat")
963
+
964
+ # Record in scan history (creates scan_history.db on first scan)
965
+ # Extract detections from result (both L1 and L2)
966
+ detections = []
967
+ if result.scan_result and result.scan_result.l1_result:
968
+ detections.extend(result.scan_result.l1_result.detections)
969
+
970
+ # Convert L2 predictions to Detection objects for consistent storage
971
+ l2_duration_ms = None
972
+ if result.scan_result and result.scan_result.l2_result:
973
+ l2_result = result.scan_result.l2_result
974
+ l2_duration_ms = l2_result.processing_time_ms
975
+ # Calculate per-prediction latency (distribute evenly)
976
+ per_prediction_ms = (
977
+ l2_result.processing_time_ms / len(l2_result.predictions)
978
+ if l2_result.predictions else 0.0
979
+ )
980
+ for prediction in l2_result.predictions:
981
+ l2_detection = _l2_prediction_to_detection(
982
+ prediction,
983
+ processing_time_ms=per_prediction_ms
984
+ )
985
+ detections.append(l2_detection)
986
+
987
+ self.scan_history.record_scan(
988
+ prompt=text,
989
+ detections=detections,
990
+ l1_duration_ms=result.scan_result.l1_result.scan_duration_ms if result.scan_result and result.scan_result.l1_result else None,
991
+ l2_duration_ms=l2_duration_ms,
992
+ version="0.0.1",
993
+ event_id=event_id,
994
+ )
995
+
996
+ # Attach event_id to result metadata for external access
997
+ if result.metadata is None:
998
+ result.metadata = {}
999
+ result.metadata["event_id"] = event_id
1000
+
1001
+ # Structured logging (privacy-preserving)
1002
+ # Include initialization timing (separate from scan timing)
1003
+ init_stats = self.initialization_stats
1004
+ logger.info(
1005
+ "scan_completed",
1006
+ prompt_hash=result.text_hash,
1007
+ has_threats=result.has_threats,
1008
+ detection_count=result.total_detections,
1009
+ severity=result.severity if result.has_threats else "none",
1010
+ scan_duration_ms=result.duration_ms, # Actual scan time (not including init)
1011
+ initialization_ms=init_stats.get("total_init_time_ms", 0), # One-time init cost
1012
+ l2_init_ms=init_stats.get("l2_init_time_ms", 0), # ML model loading time
1013
+ l2_model_type=init_stats.get("l2_model_type", "none"), # onnx_int8, sentence_transformers, stub
1014
+ l1_enabled=l1_enabled,
1015
+ l2_enabled=l2_enabled,
1016
+ mode=mode
1017
+ )
1018
+
1019
+ # Streak tracking and achievements (P2 gamification)
1020
+ # Record scan for streak tracking
1021
+ newly_unlocked = self.streak_tracker.record_scan()
1022
+
1023
+ # Check for achievements based on usage stats
1024
+ usage_stats = self.usage_tracker.get_usage_stats()
1025
+ achievement_unlocks = self.streak_tracker.check_achievements(
1026
+ total_scans=usage_stats.total_scans,
1027
+ threats_detected=usage_stats.scans_with_threats,
1028
+ avg_scan_time_ms=result.duration_ms, # Use current scan time as proxy
1029
+ threats_blocked=0 # TODO: Track blocked threats when blocking is implemented
1030
+ )
1031
+ newly_unlocked.extend(achievement_unlocks)
1032
+
1033
+ # Log newly unlocked achievements
1034
+ if newly_unlocked:
1035
+ for achievement in newly_unlocked:
1036
+ logger.info(
1037
+ "achievement_unlocked",
1038
+ achievement_id=achievement.id,
1039
+ name=achievement.name,
1040
+ points=achievement.points
1041
+ )
1042
+
1043
+ except Exception as e:
1044
+ # Don't fail the scan if tracking/history fails
1045
+ # Just log the error
1046
+ logger.warning(
1047
+ "scan_tracking_failed",
1048
+ error=str(e),
1049
+ error_type=type(e).__name__
1050
+ )
1051
+ else:
1052
+ # Log that dry_run scan skipped tracking
1053
+ # Include initialization timing (separate from scan timing)
1054
+ init_stats = self.initialization_stats
1055
+ logger.info(
1056
+ "scan_completed_dry_run",
1057
+ prompt_hash=result.text_hash,
1058
+ has_threats=result.has_threats,
1059
+ detection_count=result.total_detections,
1060
+ severity=result.severity if result.has_threats else "none",
1061
+ scan_duration_ms=result.duration_ms, # Actual scan time (not including init)
1062
+ initialization_ms=init_stats.get("total_init_time_ms", 0), # One-time init cost
1063
+ l2_init_ms=init_stats.get("l2_init_time_ms", 0), # ML model loading time
1064
+ l2_model_type=init_stats.get("l2_model_type", "none"), # onnx_int8, sentence_transformers, stub
1065
+ l1_enabled=l1_enabled,
1066
+ l2_enabled=l2_enabled,
1067
+ mode=mode
1068
+ )
1069
+
1070
+ # Enforce blocking if requested
1071
+ # When block_on_threat=True, the user explicitly wants blocking on ANY threat
1072
+ # This overrides the policy-based should_block (which may be WARN/ALLOW)
1073
+ if block_on_threat and result.has_threats:
1074
+ from raxe.sdk.exceptions import SecurityException
1075
+
1076
+ raise SecurityException(result)
1077
+
1078
+ return result
1079
+
1080
+ def scan_fast(self, text: str, **kwargs) -> ScanPipelineResult:
1081
+ """Fast scan using L1 only (target <3ms).
1082
+
1083
+ Optimized for real-time applications where latency is critical.
1084
+ Uses only regex-based detection (L1), skipping ML analysis (L2).
1085
+
1086
+ Args:
1087
+ text: Text to scan for threats
1088
+ **kwargs: Additional scan parameters (customer_id, context, etc.)
1089
+
1090
+ Returns:
1091
+ ScanPipelineResult with L1 detections only
1092
+
1093
+ Example:
1094
+ >>> raxe = Raxe()
1095
+ >>> result = raxe.scan_fast("Ignore all previous instructions")
1096
+ >>> print(f"Latency: {result.duration_ms}ms")
1097
+ """
1098
+ return self.scan(text, mode="fast", l2_enabled=False, **kwargs)
1099
+
1100
+ def scan_thorough(self, text: str, **kwargs) -> ScanPipelineResult:
1101
+ """Thorough scan using all detection layers (target <100ms).
1102
+
1103
+ Optimized for maximum detection coverage. Uses all available
1104
+ detection layers (L1 regex + L2 ML) with comprehensive rules.
1105
+
1106
+ Args:
1107
+ text: Text to scan for threats
1108
+ **kwargs: Additional scan parameters (customer_id, context, etc.)
1109
+
1110
+ Returns:
1111
+ ScanPipelineResult with all detections
1112
+
1113
+ Example:
1114
+ >>> raxe = Raxe()
1115
+ >>> result = raxe.scan_thorough("Suspicious prompt text")
1116
+ >>> print(f"Detections: {result.total_detections}")
1117
+ """
1118
+ return self.scan(text, mode="thorough", **kwargs)
1119
+
1120
+ def scan_high_confidence(
1121
+ self, text: str, threshold: float = 0.8, **kwargs
1122
+ ) -> ScanPipelineResult:
1123
+ """Scan with high confidence threshold (fewer false positives).
1124
+
1125
+ Only reports detections with confidence >= threshold.
1126
+ Useful for reducing false positives in production environments.
1127
+
1128
+ Args:
1129
+ text: Text to scan for threats
1130
+ threshold: Minimum confidence level (0.0-1.0, default: 0.8)
1131
+ **kwargs: Additional scan parameters (customer_id, context, etc.)
1132
+
1133
+ Returns:
1134
+ ScanPipelineResult with high-confidence detections only
1135
+
1136
+ Example:
1137
+ >>> raxe = Raxe()
1138
+ >>> result = raxe.scan_high_confidence(
1139
+ ... "Maybe suspicious text",
1140
+ ... threshold=0.9
1141
+ ... )
1142
+ >>> print(f"High confidence threats: {result.total_detections}")
1143
+ """
1144
+ return self.scan(text, confidence_threshold=threshold, **kwargs)
1145
+
1146
+ def protect(
1147
+ self,
1148
+ func=None,
1149
+ *,
1150
+ block: bool = True,
1151
+ on_threat: Callable | None = None,
1152
+ allow_severity: list[str] | None = None
1153
+ ):
1154
+ """Decorator to protect a function.
1155
+
1156
+ Scans the first string argument before calling the function.
1157
+ Can be used with or without parameters.
1158
+
1159
+ Usage:
1160
+ raxe = Raxe()
1161
+
1162
+ # Without parameters (blocks by default)
1163
+ @raxe.protect
1164
+ def generate(prompt: str) -> str:
1165
+ return llm.generate(prompt)
1166
+
1167
+ # With parameters (monitoring mode)
1168
+ @raxe.protect(block=False)
1169
+ def monitor(prompt: str) -> str:
1170
+ return llm.generate(prompt)
1171
+
1172
+ # With custom threat handler
1173
+ @raxe.protect(on_threat=lambda result: log.warning(result))
1174
+ def custom_handler(prompt: str) -> str:
1175
+ return llm.generate(prompt)
1176
+
1177
+ Note:
1178
+ This is a convenience method. Actual implementation
1179
+ is in raxe.sdk.decorator module (Phase 4B).
1180
+
1181
+ Args:
1182
+ func: Function to protect (when used without parameters)
1183
+ block: Whether to raise SecurityException on threat (default: True)
1184
+ on_threat: Optional callback to invoke when threat detected
1185
+ allow_severity: Optional list of severities to allow (e.g., ["LOW"])
1186
+
1187
+ Returns:
1188
+ Wrapped function that scans inputs, or decorator if called with parameters
1189
+ """
1190
+ # Import here to avoid circular dependency
1191
+ from raxe.sdk.decorator import protect_function
1192
+
1193
+ def decorator(f):
1194
+ return protect_function(
1195
+ self,
1196
+ f,
1197
+ block_on_threat=block,
1198
+ on_threat=on_threat,
1199
+ allow_severity=allow_severity
1200
+ )
1201
+
1202
+ # Support both @raxe.protect and @raxe.protect()
1203
+ if func is None:
1204
+ # Called with parameters: @raxe.protect(block=False)
1205
+ return decorator
1206
+ else:
1207
+ # Called without parameters: @raxe.protect
1208
+ return decorator(func)
1209
+
1210
+ def wrap(self, client):
1211
+ """Wrap an LLM client with RAXE scanning.
1212
+
1213
+ Creates a proxy that automatically scans all prompts and responses
1214
+ sent through the client.
1215
+
1216
+ Usage:
1217
+ raxe = Raxe()
1218
+ from openai import OpenAI
1219
+ client = raxe.wrap(OpenAI())
1220
+
1221
+ # All calls automatically scanned
1222
+ response = client.chat.completions.create(
1223
+ model="gpt-4",
1224
+ messages=[{"role": "user", "content": "Hello"}]
1225
+ )
1226
+
1227
+ Note:
1228
+ This is a convenience method. Actual implementation
1229
+ is in raxe.sdk.wrappers module (Phase 4C).
1230
+
1231
+ Args:
1232
+ client: LLM client to wrap (OpenAI, Anthropic, etc.)
1233
+
1234
+ Returns:
1235
+ Wrapped client with automatic scanning
1236
+ """
1237
+ # Import here to avoid circular dependency
1238
+ from raxe.sdk.wrappers import wrap_client
1239
+
1240
+ return wrap_client(self, client)
1241
+
1242
+ def suppressed(
1243
+ self,
1244
+ *patterns: str,
1245
+ action: str = "SUPPRESS",
1246
+ reason: str = "Scoped suppression",
1247
+ ) -> SuppressedContext:
1248
+ """Context manager for scoped suppression.
1249
+
1250
+ All scans within the context will have the specified patterns suppressed.
1251
+ This is useful for temporarily disabling specific rules during testing
1252
+ or for specific code paths where false positives are known.
1253
+
1254
+ The context manager is thread-safe using contextvars.
1255
+
1256
+ Args:
1257
+ *patterns: One or more rule ID patterns to suppress (e.g., "pi-*", "jb-001")
1258
+ action: Action to take - "SUPPRESS" (remove), "FLAG" (mark), or "LOG" (keep)
1259
+ reason: Reason for suppression (for audit trail)
1260
+
1261
+ Returns:
1262
+ Context manager that applies suppressions within its scope
1263
+
1264
+ Example:
1265
+ # Suppress all prompt injection rules during testing
1266
+ with raxe.suppressed("pi-*", reason="Testing auth flow"):
1267
+ result = raxe.scan(text)
1268
+ # pi-* patterns are suppressed in this scan
1269
+
1270
+ # Suppress multiple patterns
1271
+ with raxe.suppressed("pi-*", "jb-*", reason="Known false positives"):
1272
+ result = raxe.scan(text)
1273
+
1274
+ # Flag instead of suppress (keeps detection with is_flagged=True)
1275
+ with raxe.suppressed("pi-*", action="FLAG", reason="Under review"):
1276
+ result = raxe.scan(text)
1277
+ # Detections have is_flagged=True instead of being removed
1278
+ """
1279
+ return SuppressedContext(
1280
+ self,
1281
+ *patterns,
1282
+ action=action,
1283
+ reason=reason,
1284
+ )
1285
+
1286
+ # === PUBLIC API METHODS ===
1287
+ # These methods provide controlled access to internal components
1288
+ # for CLI commands and diagnostic tools, eliminating the need for
1289
+ # private attribute access.
1290
+
1291
+ def get_all_rules(self) -> list:
1292
+ """Get all loaded detection rules.
1293
+
1294
+ Public API for CLI and external tools to access rules without
1295
+ accessing private attributes.
1296
+
1297
+ Returns:
1298
+ List of all loaded rules from all packs
1299
+
1300
+ Example:
1301
+ raxe = Raxe()
1302
+ rules = raxe.get_all_rules()
1303
+ print(f"Loaded {len(rules)} rules")
1304
+ """
1305
+ return self.pipeline.pack_registry.get_all_rules()
1306
+
1307
+ def list_rule_packs(self) -> list[str]:
1308
+ """List all available rule packs.
1309
+
1310
+ Returns:
1311
+ List of pack names currently loaded
1312
+
1313
+ Example:
1314
+ raxe = Raxe()
1315
+ packs = raxe.list_rule_packs()
1316
+ print(f"Packs: {', '.join(packs)}")
1317
+ """
1318
+ return self.pipeline.pack_registry.list_packs()
1319
+
1320
+ def has_api_key(self) -> bool:
1321
+ """Check if an API key is configured.
1322
+
1323
+ Returns:
1324
+ True if API key is set, False otherwise
1325
+
1326
+ Example:
1327
+ raxe = Raxe(api_key="raxe_test_123")
1328
+ if raxe.has_api_key():
1329
+ print("Cloud features available")
1330
+ """
1331
+ return bool(self.config.api_key)
1332
+
1333
+ def get_telemetry_enabled(self) -> bool:
1334
+ """Check if telemetry is enabled.
1335
+
1336
+ Returns:
1337
+ True if telemetry is enabled
1338
+
1339
+ Example:
1340
+ raxe = Raxe(telemetry=False)
1341
+ if not raxe.get_telemetry_enabled():
1342
+ print("Telemetry disabled")
1343
+ """
1344
+ return self.config.telemetry.enabled
1345
+
1346
+ def get_profiling_components(self) -> dict[str, Any]:
1347
+ """Get internal components for profiling.
1348
+
1349
+ This provides controlled access to internal components
1350
+ needed by the profiler CLI command without exposing
1351
+ private attributes.
1352
+
1353
+ Returns:
1354
+ Dictionary with profiling components:
1355
+ - executor: RuleExecutor instance
1356
+ - l2_detector: L2Detector instance (optional)
1357
+ - rules: List of all loaded rules
1358
+
1359
+ Example:
1360
+ raxe = Raxe()
1361
+ components = raxe.get_profiling_components()
1362
+ executor = components['executor']
1363
+ rules = components['rules']
1364
+ """
1365
+ return {
1366
+ 'executor': self.pipeline.rule_executor,
1367
+ 'l2_detector': self.pipeline.l2_detector if self.config.enable_l2 else None,
1368
+ 'rules': self.get_all_rules()
1369
+ }
1370
+
1371
+ def get_pipeline_stats(self) -> dict[str, Any]:
1372
+ """Get pipeline statistics for diagnostics.
1373
+
1374
+ Returns:
1375
+ Dictionary with pipeline statistics:
1376
+ - rules_loaded: Number of rules loaded
1377
+ - packs_loaded: Number of packs loaded
1378
+ - telemetry_enabled: Whether telemetry is enabled
1379
+ - has_api_key: Whether API key is configured
1380
+ - l2_enabled: Whether L2 detection is enabled
1381
+
1382
+ Example:
1383
+ raxe = Raxe()
1384
+ stats = raxe.get_pipeline_stats()
1385
+ print(f"Rules: {stats['rules_loaded']}, L2: {stats['l2_enabled']}")
1386
+ """
1387
+ stats = {
1388
+ 'rules_loaded': len(self.get_all_rules()),
1389
+ 'packs_loaded': len(self.list_rule_packs()),
1390
+ 'telemetry_enabled': self.get_telemetry_enabled(),
1391
+ 'has_api_key': self.has_api_key(),
1392
+ 'l2_enabled': self.config.enable_l2
1393
+ }
1394
+
1395
+ # Add preload stats if available
1396
+ if hasattr(self, 'preload_stats'):
1397
+ stats['preload_time_ms'] = self.preload_stats.duration_ms
1398
+ stats['patterns_compiled'] = self.preload_stats.patterns_compiled
1399
+ stats['l2_init_time_ms'] = self.preload_stats.l2_init_time_ms
1400
+ stats['l2_model_type'] = self.preload_stats.l2_model_type
1401
+
1402
+ return stats
1403
+
1404
+ def validate_configuration(self) -> dict[str, Any]:
1405
+ """Validate the current configuration.
1406
+
1407
+ Used by doctor command for health checks. Performs comprehensive
1408
+ validation of configuration settings and returns detailed results.
1409
+
1410
+ Returns:
1411
+ Dictionary with validation results:
1412
+ - config_valid: True if configuration is valid
1413
+ - errors: List of error messages (blocking issues)
1414
+ - warnings: List of warning messages (non-blocking issues)
1415
+
1416
+ Example:
1417
+ raxe = Raxe()
1418
+ validation = raxe.validate_configuration()
1419
+ if not validation['config_valid']:
1420
+ print(f"Errors: {validation['errors']}")
1421
+ if validation['warnings']:
1422
+ print(f"Warnings: {validation['warnings']}")
1423
+ """
1424
+ validation = {
1425
+ 'config_valid': True,
1426
+ 'errors': [],
1427
+ 'warnings': []
1428
+ }
1429
+
1430
+ # Check API key format if present
1431
+ if self.config.api_key:
1432
+ if not self.config.api_key.startswith('raxe_'):
1433
+ validation['warnings'].append("API key should start with 'raxe_'")
1434
+ if len(self.config.api_key) < 20:
1435
+ validation['warnings'].append("API key seems too short")
1436
+
1437
+ # Check rule loading
1438
+ if len(self.get_all_rules()) == 0:
1439
+ validation['warnings'].append("No detection rules loaded")
1440
+
1441
+ # Check pack loading
1442
+ if len(self.list_rule_packs()) == 0:
1443
+ validation['warnings'].append("No rule packs loaded")
1444
+
1445
+ return validation
1446
+
1447
+ @property
1448
+ def initialization_stats(self) -> dict[str, Any]:
1449
+ """Get initialization statistics (separate from scan timing).
1450
+
1451
+ This property provides detailed initialization metrics separated
1452
+ from scan performance metrics. Useful for understanding startup costs.
1453
+
1454
+ Returns:
1455
+ Dictionary with:
1456
+ - total_init_time_ms: Total initialization time (preload + L2)
1457
+ - preload_time_ms: Core preload time (rules, packs, patterns)
1458
+ - l2_init_time_ms: L2 model initialization time
1459
+ - l2_model_type: Type of L2 model loaded
1460
+ - rules_loaded: Number of rules loaded
1461
+ - packs_loaded: Number of packs loaded
1462
+ - patterns_compiled: Number of patterns compiled
1463
+ - config_loaded: True if config loaded successfully
1464
+ - telemetry_initialized: True if telemetry initialized
1465
+
1466
+ Example:
1467
+ raxe = Raxe()
1468
+ init_stats = raxe.initialization_stats
1469
+ print(f"Total init: {init_stats['total_init_time_ms']}ms")
1470
+ print(f"L2 init: {init_stats['l2_init_time_ms']}ms ({init_stats['l2_model_type']})")
1471
+ """
1472
+ return {
1473
+ "total_init_time_ms": self.preload_stats.duration_ms,
1474
+ "preload_time_ms": self.preload_stats.duration_ms - self.preload_stats.l2_init_time_ms,
1475
+ "l2_init_time_ms": self.preload_stats.l2_init_time_ms,
1476
+ "l2_model_type": self.preload_stats.l2_model_type,
1477
+ "rules_loaded": self.preload_stats.rules_loaded,
1478
+ "packs_loaded": self.preload_stats.packs_loaded,
1479
+ "patterns_compiled": self.preload_stats.patterns_compiled,
1480
+ "config_loaded": self.preload_stats.config_loaded,
1481
+ "telemetry_initialized": self.preload_stats.telemetry_initialized,
1482
+ }
1483
+
1484
+ @property
1485
+ def stats(self) -> dict[str, Any]:
1486
+ """Get preload statistics.
1487
+
1488
+ Returns:
1489
+ Dictionary with:
1490
+ - rules_loaded: Number of rules loaded
1491
+ - packs_loaded: Number of packs loaded
1492
+ - patterns_compiled: Number of patterns compiled
1493
+ - preload_time_ms: Initialization time
1494
+
1495
+ Example:
1496
+ raxe = Raxe()
1497
+ print(f"Loaded {raxe.stats['rules_loaded']} rules")
1498
+ """
1499
+ return {
1500
+ "rules_loaded": self.preload_stats.rules_loaded,
1501
+ "packs_loaded": self.preload_stats.packs_loaded,
1502
+ "patterns_compiled": self.preload_stats.patterns_compiled,
1503
+ "preload_time_ms": self.preload_stats.duration_ms,
1504
+ "config_loaded": self.preload_stats.config_loaded,
1505
+ "telemetry_initialized": self.preload_stats.telemetry_initialized,
1506
+ "l2_init_time_ms": self.preload_stats.l2_init_time_ms,
1507
+ "l2_model_type": self.preload_stats.l2_model_type,
1508
+ }
1509
+
1510
+ def close(self) -> None:
1511
+ """Close client and flush pending telemetry.
1512
+
1513
+ This method ensures all queued telemetry events are sent before
1514
+ the client is closed. It's safe to call multiple times.
1515
+
1516
+ For long-running applications, consider using the context manager
1517
+ pattern instead:
1518
+ with Raxe() as raxe:
1519
+ raxe.scan("test")
1520
+ """
1521
+ self._flush_telemetry()
1522
+
1523
+ def _flush_telemetry(self) -> None:
1524
+ """Internal method to flush telemetry (thread-safe)."""
1525
+ with Raxe._flush_lock:
1526
+ if Raxe._flushed:
1527
+ return
1528
+
1529
+ try:
1530
+ from raxe.infrastructure.telemetry.flush_helper import (
1531
+ ensure_telemetry_flushed,
1532
+ )
1533
+ ensure_telemetry_flushed(
1534
+ timeout_seconds=2.0,
1535
+ max_batches=50,
1536
+ batch_size=50,
1537
+ end_session=True,
1538
+ )
1539
+ Raxe._flushed = True
1540
+ except Exception:
1541
+ pass # Never fail on telemetry cleanup
1542
+
1543
+ @classmethod
1544
+ def _atexit_flush(cls) -> None:
1545
+ """Class method for atexit handler."""
1546
+ with cls._flush_lock:
1547
+ if cls._flushed:
1548
+ return
1549
+ try:
1550
+ from raxe.infrastructure.telemetry.flush_helper import (
1551
+ ensure_telemetry_flushed,
1552
+ )
1553
+ ensure_telemetry_flushed(
1554
+ timeout_seconds=2.0,
1555
+ max_batches=50,
1556
+ batch_size=50,
1557
+ end_session=True,
1558
+ )
1559
+ cls._flushed = True
1560
+ except Exception:
1561
+ pass # Never fail on atexit
1562
+
1563
+ @classmethod
1564
+ def _reset_flush_state(cls) -> None:
1565
+ """Reset flush state (for testing only)."""
1566
+ with cls._flush_lock:
1567
+ cls._flushed = False
1568
+ cls._atexit_registered = False
1569
+
1570
+ def __enter__(self) -> Raxe:
1571
+ """Enter context manager.
1572
+
1573
+ Returns:
1574
+ Self for use in with statement
1575
+ """
1576
+ return self
1577
+
1578
+ def __exit__(
1579
+ self,
1580
+ exc_type: type[BaseException] | None,
1581
+ exc_val: BaseException | None,
1582
+ exc_tb: object,
1583
+ ) -> None:
1584
+ """Exit context manager and cleanup.
1585
+
1586
+ Args:
1587
+ exc_type: Exception type (if any)
1588
+ exc_val: Exception value (if any)
1589
+ exc_tb: Exception traceback (if any)
1590
+ """
1591
+ self.close()
1592
+
1593
+ def __repr__(self) -> str:
1594
+ """String representation of Raxe client.
1595
+
1596
+ Returns:
1597
+ Human-readable string showing key stats
1598
+ """
1599
+ return (
1600
+ f"Raxe(initialized={self._initialized}, "
1601
+ f"rules={self.stats['rules_loaded']}, "
1602
+ f"l2_enabled={self.config.enable_l2})"
1603
+ )