raxe 0.4.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (668) hide show
  1. raxe/__init__.py +101 -0
  2. raxe/application/__init__.py +48 -0
  3. raxe/application/ab_testing.py +170 -0
  4. raxe/application/analytics/__init__.py +30 -0
  5. raxe/application/analytics/achievement_service.py +444 -0
  6. raxe/application/analytics/repositories.py +172 -0
  7. raxe/application/analytics/retention_service.py +267 -0
  8. raxe/application/analytics/statistics_service.py +419 -0
  9. raxe/application/analytics/streak_service.py +283 -0
  10. raxe/application/apply_policy.py +291 -0
  11. raxe/application/eager_l2.py +503 -0
  12. raxe/application/preloader.py +353 -0
  13. raxe/application/scan_merger.py +321 -0
  14. raxe/application/scan_pipeline.py +1059 -0
  15. raxe/application/scan_pipeline_async.py +403 -0
  16. raxe/application/session_tracker.py +458 -0
  17. raxe/application/telemetry_manager.py +357 -0
  18. raxe/application/telemetry_orchestrator.py +1210 -0
  19. raxe/async_sdk/__init__.py +34 -0
  20. raxe/async_sdk/cache.py +286 -0
  21. raxe/async_sdk/client.py +556 -0
  22. raxe/async_sdk/wrappers/__init__.py +23 -0
  23. raxe/async_sdk/wrappers/openai.py +238 -0
  24. raxe/cli/__init__.py +21 -0
  25. raxe/cli/auth.py +1047 -0
  26. raxe/cli/branding.py +235 -0
  27. raxe/cli/config.py +334 -0
  28. raxe/cli/custom_rules.py +458 -0
  29. raxe/cli/doctor.py +686 -0
  30. raxe/cli/error_handler.py +665 -0
  31. raxe/cli/event.py +648 -0
  32. raxe/cli/exit_codes.py +57 -0
  33. raxe/cli/expiry_warning.py +302 -0
  34. raxe/cli/export.py +183 -0
  35. raxe/cli/history.py +247 -0
  36. raxe/cli/l2_formatter.py +872 -0
  37. raxe/cli/main.py +1137 -0
  38. raxe/cli/models.py +590 -0
  39. raxe/cli/output.py +403 -0
  40. raxe/cli/privacy.py +84 -0
  41. raxe/cli/profiler.py +262 -0
  42. raxe/cli/progress.py +379 -0
  43. raxe/cli/progress_context.py +101 -0
  44. raxe/cli/repl.py +394 -0
  45. raxe/cli/rules.py +542 -0
  46. raxe/cli/setup_wizard.py +721 -0
  47. raxe/cli/stats.py +292 -0
  48. raxe/cli/suppress.py +501 -0
  49. raxe/cli/telemetry.py +1384 -0
  50. raxe/cli/test.py +130 -0
  51. raxe/cli/tune.py +315 -0
  52. raxe/cli/validate.py +218 -0
  53. raxe/domain/__init__.py +30 -0
  54. raxe/domain/analytics/__init__.py +97 -0
  55. raxe/domain/analytics/achievements.py +306 -0
  56. raxe/domain/analytics/models.py +120 -0
  57. raxe/domain/analytics/retention.py +168 -0
  58. raxe/domain/analytics/statistics.py +207 -0
  59. raxe/domain/analytics/streaks.py +173 -0
  60. raxe/domain/engine/__init__.py +15 -0
  61. raxe/domain/engine/executor.py +396 -0
  62. raxe/domain/engine/matcher.py +212 -0
  63. raxe/domain/inline_suppression.py +176 -0
  64. raxe/domain/ml/__init__.py +133 -0
  65. raxe/domain/ml/embedding_cache.py +309 -0
  66. raxe/domain/ml/gemma_detector.py +921 -0
  67. raxe/domain/ml/gemma_models.py +346 -0
  68. raxe/domain/ml/l2_config.py +428 -0
  69. raxe/domain/ml/l2_output_schema.py +443 -0
  70. raxe/domain/ml/manifest_loader.py +309 -0
  71. raxe/domain/ml/manifest_schema.py +345 -0
  72. raxe/domain/ml/model_metadata.py +263 -0
  73. raxe/domain/ml/model_registry.py +786 -0
  74. raxe/domain/ml/protocol.py +282 -0
  75. raxe/domain/ml/scoring_models.py +419 -0
  76. raxe/domain/ml/stub_detector.py +397 -0
  77. raxe/domain/ml/threat_scorer.py +757 -0
  78. raxe/domain/ml/tokenizer_registry.py +372 -0
  79. raxe/domain/ml/voting/__init__.py +89 -0
  80. raxe/domain/ml/voting/config.py +595 -0
  81. raxe/domain/ml/voting/engine.py +465 -0
  82. raxe/domain/ml/voting/head_voters.py +378 -0
  83. raxe/domain/ml/voting/models.py +222 -0
  84. raxe/domain/models.py +82 -0
  85. raxe/domain/packs/__init__.py +17 -0
  86. raxe/domain/packs/models.py +304 -0
  87. raxe/domain/policies/__init__.py +20 -0
  88. raxe/domain/policies/evaluator.py +212 -0
  89. raxe/domain/policies/models.py +223 -0
  90. raxe/domain/rules/__init__.py +32 -0
  91. raxe/domain/rules/custom.py +286 -0
  92. raxe/domain/rules/models.py +273 -0
  93. raxe/domain/rules/schema.py +166 -0
  94. raxe/domain/rules/validator.py +556 -0
  95. raxe/domain/suppression.py +801 -0
  96. raxe/domain/suppression_factory.py +174 -0
  97. raxe/domain/telemetry/__init__.py +116 -0
  98. raxe/domain/telemetry/backpressure.py +424 -0
  99. raxe/domain/telemetry/event_creator.py +362 -0
  100. raxe/domain/telemetry/events.py +1282 -0
  101. raxe/domain/telemetry/priority.py +263 -0
  102. raxe/domain/telemetry/scan_telemetry_builder.py +670 -0
  103. raxe/infrastructure/__init__.py +25 -0
  104. raxe/infrastructure/analytics/__init__.py +18 -0
  105. raxe/infrastructure/analytics/aggregator.py +484 -0
  106. raxe/infrastructure/analytics/aggregator_optimized.py +184 -0
  107. raxe/infrastructure/analytics/engine.py +748 -0
  108. raxe/infrastructure/analytics/repository.py +409 -0
  109. raxe/infrastructure/analytics/streaks.py +467 -0
  110. raxe/infrastructure/analytics/views.py +178 -0
  111. raxe/infrastructure/cloud/__init__.py +9 -0
  112. raxe/infrastructure/config/__init__.py +56 -0
  113. raxe/infrastructure/config/endpoints.py +641 -0
  114. raxe/infrastructure/config/scan_config.py +352 -0
  115. raxe/infrastructure/config/yaml_config.py +459 -0
  116. raxe/infrastructure/database/__init__.py +10 -0
  117. raxe/infrastructure/database/connection.py +200 -0
  118. raxe/infrastructure/database/models.py +325 -0
  119. raxe/infrastructure/database/scan_history.py +764 -0
  120. raxe/infrastructure/ml/__init__.py +0 -0
  121. raxe/infrastructure/ml/download_progress.py +438 -0
  122. raxe/infrastructure/ml/model_downloader.py +457 -0
  123. raxe/infrastructure/models/__init__.py +16 -0
  124. raxe/infrastructure/models/discovery.py +461 -0
  125. raxe/infrastructure/packs/__init__.py +13 -0
  126. raxe/infrastructure/packs/loader.py +407 -0
  127. raxe/infrastructure/packs/registry.py +381 -0
  128. raxe/infrastructure/policies/__init__.py +16 -0
  129. raxe/infrastructure/policies/api_client.py +256 -0
  130. raxe/infrastructure/policies/validator.py +227 -0
  131. raxe/infrastructure/policies/yaml_loader.py +250 -0
  132. raxe/infrastructure/rules/__init__.py +18 -0
  133. raxe/infrastructure/rules/custom_loader.py +224 -0
  134. raxe/infrastructure/rules/versioning.py +222 -0
  135. raxe/infrastructure/rules/yaml_loader.py +286 -0
  136. raxe/infrastructure/security/__init__.py +31 -0
  137. raxe/infrastructure/security/auth.py +145 -0
  138. raxe/infrastructure/security/policy_validator.py +124 -0
  139. raxe/infrastructure/security/signatures.py +171 -0
  140. raxe/infrastructure/suppression/__init__.py +36 -0
  141. raxe/infrastructure/suppression/composite_repository.py +154 -0
  142. raxe/infrastructure/suppression/sqlite_repository.py +231 -0
  143. raxe/infrastructure/suppression/yaml_composite_repository.py +156 -0
  144. raxe/infrastructure/suppression/yaml_repository.py +510 -0
  145. raxe/infrastructure/telemetry/__init__.py +79 -0
  146. raxe/infrastructure/telemetry/acquisition.py +179 -0
  147. raxe/infrastructure/telemetry/config.py +254 -0
  148. raxe/infrastructure/telemetry/credential_store.py +947 -0
  149. raxe/infrastructure/telemetry/dual_queue.py +1123 -0
  150. raxe/infrastructure/telemetry/flush_helper.py +343 -0
  151. raxe/infrastructure/telemetry/flush_scheduler.py +776 -0
  152. raxe/infrastructure/telemetry/health_client.py +394 -0
  153. raxe/infrastructure/telemetry/hook.py +347 -0
  154. raxe/infrastructure/telemetry/queue.py +520 -0
  155. raxe/infrastructure/telemetry/sender.py +476 -0
  156. raxe/infrastructure/tracking/__init__.py +13 -0
  157. raxe/infrastructure/tracking/usage.py +389 -0
  158. raxe/integrations/__init__.py +55 -0
  159. raxe/integrations/availability.py +143 -0
  160. raxe/integrations/registry.py +122 -0
  161. raxe/integrations/utils.py +135 -0
  162. raxe/mcp/__init__.py +62 -0
  163. raxe/mcp/cli.py +97 -0
  164. raxe/mcp/server.py +409 -0
  165. raxe/monitoring/__init__.py +51 -0
  166. raxe/monitoring/metrics.py +372 -0
  167. raxe/monitoring/profiler.py +388 -0
  168. raxe/monitoring/server.py +136 -0
  169. raxe/packs/core/v1.0.0/pack.yaml +1394 -0
  170. raxe/packs/core/v1.0.0/rules/PI/pi-001@1.0.0.yaml +49 -0
  171. raxe/packs/core/v1.0.0/rules/PI/pi-006@1.0.0.yaml +48 -0
  172. raxe/packs/core/v1.0.0/rules/PI/pi-014@1.0.0.yaml +54 -0
  173. raxe/packs/core/v1.0.0/rules/PI/pi-017@1.0.0.yaml +52 -0
  174. raxe/packs/core/v1.0.0/rules/PI/pi-022@1.0.0.yaml +67 -0
  175. raxe/packs/core/v1.0.0/rules/PI/pi-023@1.0.0.yaml +91 -0
  176. raxe/packs/core/v1.0.0/rules/PI/pi-024@1.0.0.yaml +80 -0
  177. raxe/packs/core/v1.0.0/rules/PI/pi-025@1.0.0.yaml +81 -0
  178. raxe/packs/core/v1.0.0/rules/PI/pi-026@1.0.0.yaml +50 -0
  179. raxe/packs/core/v1.0.0/rules/PI/pi-027@1.0.0.yaml +77 -0
  180. raxe/packs/core/v1.0.0/rules/PI/pi-028@1.0.0.yaml +52 -0
  181. raxe/packs/core/v1.0.0/rules/PI/pi-029@1.0.0.yaml +51 -0
  182. raxe/packs/core/v1.0.0/rules/PI/pi-030@1.0.0.yaml +55 -0
  183. raxe/packs/core/v1.0.0/rules/PI/pi-033@1.0.0.yaml +50 -0
  184. raxe/packs/core/v1.0.0/rules/PI/pi-034@1.0.0.yaml +50 -0
  185. raxe/packs/core/v1.0.0/rules/PI/pi-035@1.0.0.yaml +50 -0
  186. raxe/packs/core/v1.0.0/rules/PI/pi-046@1.0.0.yaml +50 -0
  187. raxe/packs/core/v1.0.0/rules/PI/pi-047@1.0.0.yaml +50 -0
  188. raxe/packs/core/v1.0.0/rules/PI/pi-048@1.0.0.yaml +50 -0
  189. raxe/packs/core/v1.0.0/rules/PI/pi-049@1.0.0.yaml +50 -0
  190. raxe/packs/core/v1.0.0/rules/PI/pi-050@1.0.0.yaml +50 -0
  191. raxe/packs/core/v1.0.0/rules/PI/pi-068@1.0.0.yaml +50 -0
  192. raxe/packs/core/v1.0.0/rules/PI/pi-078@1.0.0.yaml +50 -0
  193. raxe/packs/core/v1.0.0/rules/PI/pi-2001@1.0.0.yaml +35 -0
  194. raxe/packs/core/v1.0.0/rules/PI/pi-2004@1.0.0.yaml +39 -0
  195. raxe/packs/core/v1.0.0/rules/PI/pi-201@1.0.0.yaml +43 -0
  196. raxe/packs/core/v1.0.0/rules/PI/pi-202@1.0.0.yaml +47 -0
  197. raxe/packs/core/v1.0.0/rules/PI/pi-203@1.0.0.yaml +46 -0
  198. raxe/packs/core/v1.0.0/rules/PI/pi-3007@1.0.0.yaml +44 -0
  199. raxe/packs/core/v1.0.0/rules/PI/pi-3016@1.0.0.yaml +44 -0
  200. raxe/packs/core/v1.0.0/rules/PI/pi-3026@1.0.0.yaml +39 -0
  201. raxe/packs/core/v1.0.0/rules/PI/pi-3027@1.0.0.yaml +64 -0
  202. raxe/packs/core/v1.0.0/rules/PI/pi-3028@1.0.0.yaml +51 -0
  203. raxe/packs/core/v1.0.0/rules/PI/pi-3029@1.0.0.yaml +53 -0
  204. raxe/packs/core/v1.0.0/rules/PI/pi-3030@1.0.0.yaml +50 -0
  205. raxe/packs/core/v1.0.0/rules/PI/pi-3031@1.0.0.yaml +50 -0
  206. raxe/packs/core/v1.0.0/rules/PI/pi-3032@1.0.0.yaml +50 -0
  207. raxe/packs/core/v1.0.0/rules/PI/pi-3033@1.0.0.yaml +56 -0
  208. raxe/packs/core/v1.0.0/rules/PI/pi-3034@1.0.0.yaml +50 -0
  209. raxe/packs/core/v1.0.0/rules/PI/pi-79@1.0.0.yaml +38 -0
  210. raxe/packs/core/v1.0.0/rules/PI/pi-80@1.0.0.yaml +38 -0
  211. raxe/packs/core/v1.0.0/rules/PI/pi-81@1.0.0.yaml +38 -0
  212. raxe/packs/core/v1.0.0/rules/PI/pi-82@1.0.0.yaml +38 -0
  213. raxe/packs/core/v1.0.0/rules/PI/pi-83@1.0.0.yaml +38 -0
  214. raxe/packs/core/v1.0.0/rules/PI/pi-84@1.0.0.yaml +38 -0
  215. raxe/packs/core/v1.0.0/rules/PI/pi-85@1.0.0.yaml +38 -0
  216. raxe/packs/core/v1.0.0/rules/PI/pi-86@1.0.0.yaml +38 -0
  217. raxe/packs/core/v1.0.0/rules/PI/pi-87@1.0.0.yaml +38 -0
  218. raxe/packs/core/v1.0.0/rules/PI/pi-88@1.0.0.yaml +38 -0
  219. raxe/packs/core/v1.0.0/rules/PI/pi-89@1.0.0.yaml +38 -0
  220. raxe/packs/core/v1.0.0/rules/PI/pi-90@1.0.0.yaml +38 -0
  221. raxe/packs/core/v1.0.0/rules/PI/pi-91@1.0.0.yaml +38 -0
  222. raxe/packs/core/v1.0.0/rules/PI/pi-92@1.0.0.yaml +38 -0
  223. raxe/packs/core/v1.0.0/rules/PI/pi-93@1.0.0.yaml +38 -0
  224. raxe/packs/core/v1.0.0/rules/PI/pi-94@1.0.0.yaml +38 -0
  225. raxe/packs/core/v1.0.0/rules/PI/pi-95@1.0.0.yaml +38 -0
  226. raxe/packs/core/v1.0.0/rules/PI/pi-96@1.0.0.yaml +38 -0
  227. raxe/packs/core/v1.0.0/rules/PI/pi-97@1.0.0.yaml +38 -0
  228. raxe/packs/core/v1.0.0/rules/PI/pi-98@1.0.0.yaml +38 -0
  229. raxe/packs/core/v1.0.0/rules/cmd/cmd-001@1.0.0.yaml +48 -0
  230. raxe/packs/core/v1.0.0/rules/cmd/cmd-007@1.0.0.yaml +48 -0
  231. raxe/packs/core/v1.0.0/rules/cmd/cmd-015@1.0.0.yaml +56 -0
  232. raxe/packs/core/v1.0.0/rules/cmd/cmd-016@1.0.0.yaml +46 -0
  233. raxe/packs/core/v1.0.0/rules/cmd/cmd-017@1.0.0.yaml +57 -0
  234. raxe/packs/core/v1.0.0/rules/cmd/cmd-021@1.0.0.yaml +46 -0
  235. raxe/packs/core/v1.0.0/rules/cmd/cmd-022@1.0.0.yaml +46 -0
  236. raxe/packs/core/v1.0.0/rules/cmd/cmd-023@1.0.0.yaml +78 -0
  237. raxe/packs/core/v1.0.0/rules/cmd/cmd-024@1.0.0.yaml +46 -0
  238. raxe/packs/core/v1.0.0/rules/cmd/cmd-025@1.0.0.yaml +93 -0
  239. raxe/packs/core/v1.0.0/rules/cmd/cmd-026@1.0.0.yaml +81 -0
  240. raxe/packs/core/v1.0.0/rules/cmd/cmd-027@1.0.0.yaml +82 -0
  241. raxe/packs/core/v1.0.0/rules/cmd/cmd-028@1.0.0.yaml +46 -0
  242. raxe/packs/core/v1.0.0/rules/cmd/cmd-033@1.0.0.yaml +48 -0
  243. raxe/packs/core/v1.0.0/rules/cmd/cmd-036@1.0.0.yaml +47 -0
  244. raxe/packs/core/v1.0.0/rules/cmd/cmd-037@1.0.0.yaml +44 -0
  245. raxe/packs/core/v1.0.0/rules/cmd/cmd-052@1.0.0.yaml +43 -0
  246. raxe/packs/core/v1.0.0/rules/cmd/cmd-054@1.0.0.yaml +44 -0
  247. raxe/packs/core/v1.0.0/rules/cmd/cmd-056@1.0.0.yaml +43 -0
  248. raxe/packs/core/v1.0.0/rules/cmd/cmd-065@1.0.0.yaml +46 -0
  249. raxe/packs/core/v1.0.0/rules/cmd/cmd-075@1.0.0.yaml +45 -0
  250. raxe/packs/core/v1.0.0/rules/cmd/cmd-079@1.0.0.yaml +44 -0
  251. raxe/packs/core/v1.0.0/rules/cmd/cmd-1080@1.0.0.yaml +41 -0
  252. raxe/packs/core/v1.0.0/rules/cmd/cmd-1090@1.0.0.yaml +41 -0
  253. raxe/packs/core/v1.0.0/rules/cmd/cmd-1104@1.0.0.yaml +44 -0
  254. raxe/packs/core/v1.0.0/rules/cmd/cmd-1105@1.0.0.yaml +41 -0
  255. raxe/packs/core/v1.0.0/rules/cmd/cmd-1112@1.0.0.yaml +44 -0
  256. raxe/packs/core/v1.0.0/rules/cmd/cmd-201@1.0.0.yaml +47 -0
  257. raxe/packs/core/v1.0.0/rules/cmd/cmd-202@1.0.0.yaml +42 -0
  258. raxe/packs/core/v1.0.0/rules/cmd/cmd-203@1.0.0.yaml +43 -0
  259. raxe/packs/core/v1.0.0/rules/cmd/cmd-204@1.0.0.yaml +47 -0
  260. raxe/packs/core/v1.0.0/rules/cmd/cmd-205@1.0.0.yaml +44 -0
  261. raxe/packs/core/v1.0.0/rules/cmd/cmd-206@1.0.0.yaml +47 -0
  262. raxe/packs/core/v1.0.0/rules/cmd/cmd-207@1.0.0.yaml +46 -0
  263. raxe/packs/core/v1.0.0/rules/cmd/cmd-208@1.0.0.yaml +42 -0
  264. raxe/packs/core/v1.0.0/rules/cmd/cmd-209@1.0.0.yaml +38 -0
  265. raxe/packs/core/v1.0.0/rules/cmd/cmd-210@1.0.0.yaml +38 -0
  266. raxe/packs/core/v1.0.0/rules/cmd/cmd-211@1.0.0.yaml +38 -0
  267. raxe/packs/core/v1.0.0/rules/cmd/cmd-212@1.0.0.yaml +38 -0
  268. raxe/packs/core/v1.0.0/rules/cmd/cmd-213@1.0.0.yaml +38 -0
  269. raxe/packs/core/v1.0.0/rules/cmd/cmd-214@1.0.0.yaml +38 -0
  270. raxe/packs/core/v1.0.0/rules/cmd/cmd-215@1.0.0.yaml +38 -0
  271. raxe/packs/core/v1.0.0/rules/cmd/cmd-216@1.0.0.yaml +38 -0
  272. raxe/packs/core/v1.0.0/rules/cmd/cmd-217@1.0.0.yaml +38 -0
  273. raxe/packs/core/v1.0.0/rules/cmd/cmd-218@1.0.0.yaml +38 -0
  274. raxe/packs/core/v1.0.0/rules/cmd/cmd-219@1.0.0.yaml +38 -0
  275. raxe/packs/core/v1.0.0/rules/cmd/cmd-220@1.0.0.yaml +38 -0
  276. raxe/packs/core/v1.0.0/rules/cmd/cmd-221@1.0.0.yaml +38 -0
  277. raxe/packs/core/v1.0.0/rules/cmd/cmd-222@1.0.0.yaml +38 -0
  278. raxe/packs/core/v1.0.0/rules/cmd/cmd-223@1.0.0.yaml +38 -0
  279. raxe/packs/core/v1.0.0/rules/cmd/cmd-224@1.0.0.yaml +38 -0
  280. raxe/packs/core/v1.0.0/rules/cmd/cmd-225@1.0.0.yaml +38 -0
  281. raxe/packs/core/v1.0.0/rules/cmd/cmd-226@1.0.0.yaml +38 -0
  282. raxe/packs/core/v1.0.0/rules/cmd/cmd-227@1.0.0.yaml +38 -0
  283. raxe/packs/core/v1.0.0/rules/cmd/cmd-228@1.0.0.yaml +38 -0
  284. raxe/packs/core/v1.0.0/rules/cmd/cmd-229@1.0.0.yaml +38 -0
  285. raxe/packs/core/v1.0.0/rules/cmd/cmd-230@1.0.0.yaml +38 -0
  286. raxe/packs/core/v1.0.0/rules/cmd/cmd-231@1.0.0.yaml +38 -0
  287. raxe/packs/core/v1.0.0/rules/cmd/cmd-232@1.0.0.yaml +38 -0
  288. raxe/packs/core/v1.0.0/rules/cmd/cmd-233@1.0.0.yaml +38 -0
  289. raxe/packs/core/v1.0.0/rules/cmd/cmd-234@1.0.0.yaml +38 -0
  290. raxe/packs/core/v1.0.0/rules/cmd/cmd-235@1.0.0.yaml +38 -0
  291. raxe/packs/core/v1.0.0/rules/cmd/cmd-236@1.0.0.yaml +38 -0
  292. raxe/packs/core/v1.0.0/rules/cmd/cmd-237@1.0.0.yaml +38 -0
  293. raxe/packs/core/v1.0.0/rules/cmd/cmd-238@1.0.0.yaml +38 -0
  294. raxe/packs/core/v1.0.0/rules/enc/enc-001@1.0.0.yaml +48 -0
  295. raxe/packs/core/v1.0.0/rules/enc/enc-013@1.0.0.yaml +46 -0
  296. raxe/packs/core/v1.0.0/rules/enc/enc-019@1.0.0.yaml +43 -0
  297. raxe/packs/core/v1.0.0/rules/enc/enc-020@1.0.0.yaml +47 -0
  298. raxe/packs/core/v1.0.0/rules/enc/enc-024@1.0.0.yaml +46 -0
  299. raxe/packs/core/v1.0.0/rules/enc/enc-029@1.0.0.yaml +44 -0
  300. raxe/packs/core/v1.0.0/rules/enc/enc-038@1.0.0.yaml +44 -0
  301. raxe/packs/core/v1.0.0/rules/enc/enc-044@1.0.0.yaml +46 -0
  302. raxe/packs/core/v1.0.0/rules/enc/enc-067@1.0.0.yaml +42 -0
  303. raxe/packs/core/v1.0.0/rules/enc/enc-069@1.0.0.yaml +42 -0
  304. raxe/packs/core/v1.0.0/rules/enc/enc-100@1.0.0.yaml +38 -0
  305. raxe/packs/core/v1.0.0/rules/enc/enc-101@1.0.0.yaml +38 -0
  306. raxe/packs/core/v1.0.0/rules/enc/enc-102@1.0.0.yaml +38 -0
  307. raxe/packs/core/v1.0.0/rules/enc/enc-103@1.0.0.yaml +38 -0
  308. raxe/packs/core/v1.0.0/rules/enc/enc-104@1.0.0.yaml +38 -0
  309. raxe/packs/core/v1.0.0/rules/enc/enc-105@1.0.0.yaml +38 -0
  310. raxe/packs/core/v1.0.0/rules/enc/enc-106@1.0.0.yaml +38 -0
  311. raxe/packs/core/v1.0.0/rules/enc/enc-107@1.0.0.yaml +38 -0
  312. raxe/packs/core/v1.0.0/rules/enc/enc-108@1.0.0.yaml +38 -0
  313. raxe/packs/core/v1.0.0/rules/enc/enc-109@1.0.0.yaml +38 -0
  314. raxe/packs/core/v1.0.0/rules/enc/enc-110@1.0.0.yaml +38 -0
  315. raxe/packs/core/v1.0.0/rules/enc/enc-111@1.0.0.yaml +38 -0
  316. raxe/packs/core/v1.0.0/rules/enc/enc-112@1.0.0.yaml +38 -0
  317. raxe/packs/core/v1.0.0/rules/enc/enc-113@1.0.0.yaml +38 -0
  318. raxe/packs/core/v1.0.0/rules/enc/enc-114@1.0.0.yaml +38 -0
  319. raxe/packs/core/v1.0.0/rules/enc/enc-115@1.0.0.yaml +38 -0
  320. raxe/packs/core/v1.0.0/rules/enc/enc-116@1.0.0.yaml +38 -0
  321. raxe/packs/core/v1.0.0/rules/enc/enc-117@1.0.0.yaml +38 -0
  322. raxe/packs/core/v1.0.0/rules/enc/enc-118@1.0.0.yaml +38 -0
  323. raxe/packs/core/v1.0.0/rules/enc/enc-119@1.0.0.yaml +38 -0
  324. raxe/packs/core/v1.0.0/rules/enc/enc-120@1.0.0.yaml +38 -0
  325. raxe/packs/core/v1.0.0/rules/enc/enc-201@1.0.0.yaml +37 -0
  326. raxe/packs/core/v1.0.0/rules/enc/enc-202@1.0.0.yaml +41 -0
  327. raxe/packs/core/v1.0.0/rules/enc/enc-203@1.0.0.yaml +41 -0
  328. raxe/packs/core/v1.0.0/rules/enc/enc-3004@1.0.0.yaml +40 -0
  329. raxe/packs/core/v1.0.0/rules/enc/enc-3006@1.0.0.yaml +40 -0
  330. raxe/packs/core/v1.0.0/rules/enc/enc-3011@1.0.0.yaml +40 -0
  331. raxe/packs/core/v1.0.0/rules/enc/enc-5016@1.0.0.yaml +46 -0
  332. raxe/packs/core/v1.0.0/rules/enc/enc-6001@1.0.0.yaml +53 -0
  333. raxe/packs/core/v1.0.0/rules/enc/enc-6002@1.0.0.yaml +41 -0
  334. raxe/packs/core/v1.0.0/rules/enc/enc-70@1.0.0.yaml +38 -0
  335. raxe/packs/core/v1.0.0/rules/enc/enc-71@1.0.0.yaml +38 -0
  336. raxe/packs/core/v1.0.0/rules/enc/enc-72@1.0.0.yaml +38 -0
  337. raxe/packs/core/v1.0.0/rules/enc/enc-73@1.0.0.yaml +38 -0
  338. raxe/packs/core/v1.0.0/rules/enc/enc-74@1.0.0.yaml +38 -0
  339. raxe/packs/core/v1.0.0/rules/enc/enc-75@1.0.0.yaml +38 -0
  340. raxe/packs/core/v1.0.0/rules/enc/enc-76@1.0.0.yaml +38 -0
  341. raxe/packs/core/v1.0.0/rules/enc/enc-77@1.0.0.yaml +38 -0
  342. raxe/packs/core/v1.0.0/rules/enc/enc-78@1.0.0.yaml +38 -0
  343. raxe/packs/core/v1.0.0/rules/enc/enc-79@1.0.0.yaml +38 -0
  344. raxe/packs/core/v1.0.0/rules/enc/enc-80@1.0.0.yaml +38 -0
  345. raxe/packs/core/v1.0.0/rules/enc/enc-81@1.0.0.yaml +38 -0
  346. raxe/packs/core/v1.0.0/rules/enc/enc-82@1.0.0.yaml +38 -0
  347. raxe/packs/core/v1.0.0/rules/enc/enc-83@1.0.0.yaml +38 -0
  348. raxe/packs/core/v1.0.0/rules/enc/enc-84@1.0.0.yaml +38 -0
  349. raxe/packs/core/v1.0.0/rules/enc/enc-85@1.0.0.yaml +38 -0
  350. raxe/packs/core/v1.0.0/rules/enc/enc-86@1.0.0.yaml +38 -0
  351. raxe/packs/core/v1.0.0/rules/enc/enc-87@1.0.0.yaml +38 -0
  352. raxe/packs/core/v1.0.0/rules/enc/enc-88@1.0.0.yaml +38 -0
  353. raxe/packs/core/v1.0.0/rules/enc/enc-89@1.0.0.yaml +38 -0
  354. raxe/packs/core/v1.0.0/rules/enc/enc-90@1.0.0.yaml +38 -0
  355. raxe/packs/core/v1.0.0/rules/enc/enc-91@1.0.0.yaml +38 -0
  356. raxe/packs/core/v1.0.0/rules/enc/enc-92@1.0.0.yaml +38 -0
  357. raxe/packs/core/v1.0.0/rules/enc/enc-93@1.0.0.yaml +38 -0
  358. raxe/packs/core/v1.0.0/rules/enc/enc-94@1.0.0.yaml +38 -0
  359. raxe/packs/core/v1.0.0/rules/enc/enc-95@1.0.0.yaml +38 -0
  360. raxe/packs/core/v1.0.0/rules/enc/enc-96@1.0.0.yaml +38 -0
  361. raxe/packs/core/v1.0.0/rules/enc/enc-97@1.0.0.yaml +38 -0
  362. raxe/packs/core/v1.0.0/rules/enc/enc-98@1.0.0.yaml +38 -0
  363. raxe/packs/core/v1.0.0/rules/enc/enc-99@1.0.0.yaml +38 -0
  364. raxe/packs/core/v1.0.0/rules/hc/hc-001@1.0.0.yaml +73 -0
  365. raxe/packs/core/v1.0.0/rules/hc/hc-002@1.0.0.yaml +71 -0
  366. raxe/packs/core/v1.0.0/rules/hc/hc-003@1.0.0.yaml +65 -0
  367. raxe/packs/core/v1.0.0/rules/hc/hc-004@1.0.0.yaml +73 -0
  368. raxe/packs/core/v1.0.0/rules/hc/hc-101@1.0.0.yaml +47 -0
  369. raxe/packs/core/v1.0.0/rules/hc/hc-102@1.0.0.yaml +47 -0
  370. raxe/packs/core/v1.0.0/rules/hc/hc-103@1.0.0.yaml +47 -0
  371. raxe/packs/core/v1.0.0/rules/hc/hc-104@1.0.0.yaml +47 -0
  372. raxe/packs/core/v1.0.0/rules/hc/hc-105@1.0.0.yaml +48 -0
  373. raxe/packs/core/v1.0.0/rules/hc/hc-106@1.0.0.yaml +40 -0
  374. raxe/packs/core/v1.0.0/rules/hc/hc-107@1.0.0.yaml +47 -0
  375. raxe/packs/core/v1.0.0/rules/hc/hc-108@1.0.0.yaml +47 -0
  376. raxe/packs/core/v1.0.0/rules/hc/hc-109@1.0.0.yaml +50 -0
  377. raxe/packs/core/v1.0.0/rules/hc/hc-110@1.0.0.yaml +56 -0
  378. raxe/packs/core/v1.0.0/rules/hc/hc-111@1.0.0.yaml +49 -0
  379. raxe/packs/core/v1.0.0/rules/hc/hc-112@1.0.0.yaml +53 -0
  380. raxe/packs/core/v1.0.0/rules/hc/hc-113@1.0.0.yaml +52 -0
  381. raxe/packs/core/v1.0.0/rules/hc/hc-114@1.0.0.yaml +52 -0
  382. raxe/packs/core/v1.0.0/rules/hc/hc-115@1.0.0.yaml +52 -0
  383. raxe/packs/core/v1.0.0/rules/hc/hc-116@1.0.0.yaml +53 -0
  384. raxe/packs/core/v1.0.0/rules/hc/hc-117@1.0.0.yaml +54 -0
  385. raxe/packs/core/v1.0.0/rules/hc/hc-118@1.0.0.yaml +52 -0
  386. raxe/packs/core/v1.0.0/rules/hc/hc-119@1.0.0.yaml +51 -0
  387. raxe/packs/core/v1.0.0/rules/hc/hc-120@1.0.0.yaml +52 -0
  388. raxe/packs/core/v1.0.0/rules/hc/hc-121@1.0.0.yaml +51 -0
  389. raxe/packs/core/v1.0.0/rules/hc/hc-122@1.0.0.yaml +51 -0
  390. raxe/packs/core/v1.0.0/rules/hc/hc-123@1.0.0.yaml +52 -0
  391. raxe/packs/core/v1.0.0/rules/hc/hc-124@1.0.0.yaml +53 -0
  392. raxe/packs/core/v1.0.0/rules/hc/hc-125@1.0.0.yaml +53 -0
  393. raxe/packs/core/v1.0.0/rules/hc/hc-126@1.0.0.yaml +53 -0
  394. raxe/packs/core/v1.0.0/rules/hc/hc-127@1.0.0.yaml +53 -0
  395. raxe/packs/core/v1.0.0/rules/hc/hc-128@1.0.0.yaml +53 -0
  396. raxe/packs/core/v1.0.0/rules/hc/hc-129@1.0.0.yaml +51 -0
  397. raxe/packs/core/v1.0.0/rules/hc/hc-130@1.0.0.yaml +51 -0
  398. raxe/packs/core/v1.0.0/rules/hc/hc-131@1.0.0.yaml +51 -0
  399. raxe/packs/core/v1.0.0/rules/hc/hc-132@1.0.0.yaml +51 -0
  400. raxe/packs/core/v1.0.0/rules/hc/hc-133@1.0.0.yaml +53 -0
  401. raxe/packs/core/v1.0.0/rules/hc/hc-134@1.0.0.yaml +51 -0
  402. raxe/packs/core/v1.0.0/rules/hc/hc-135@1.0.0.yaml +51 -0
  403. raxe/packs/core/v1.0.0/rules/hc/hc-136@1.0.0.yaml +51 -0
  404. raxe/packs/core/v1.0.0/rules/hc/hc-137@1.0.0.yaml +51 -0
  405. raxe/packs/core/v1.0.0/rules/hc/hc-138@1.0.0.yaml +51 -0
  406. raxe/packs/core/v1.0.0/rules/hc/hc-139@1.0.0.yaml +51 -0
  407. raxe/packs/core/v1.0.0/rules/hc/hc-140@1.0.0.yaml +51 -0
  408. raxe/packs/core/v1.0.0/rules/hc/hc-141@1.0.0.yaml +41 -0
  409. raxe/packs/core/v1.0.0/rules/hc/hc-142@1.0.0.yaml +37 -0
  410. raxe/packs/core/v1.0.0/rules/hc/hc-143@1.0.0.yaml +37 -0
  411. raxe/packs/core/v1.0.0/rules/hc/hc-144@1.0.0.yaml +37 -0
  412. raxe/packs/core/v1.0.0/rules/hc/hc-145@1.0.0.yaml +37 -0
  413. raxe/packs/core/v1.0.0/rules/hc/hc-146@1.0.0.yaml +37 -0
  414. raxe/packs/core/v1.0.0/rules/hc/hc-147@1.0.0.yaml +37 -0
  415. raxe/packs/core/v1.0.0/rules/hc/hc-148@1.0.0.yaml +37 -0
  416. raxe/packs/core/v1.0.0/rules/hc/hc-149@1.0.0.yaml +37 -0
  417. raxe/packs/core/v1.0.0/rules/hc/hc-150@1.0.0.yaml +37 -0
  418. raxe/packs/core/v1.0.0/rules/hc/hc-151@1.0.0.yaml +37 -0
  419. raxe/packs/core/v1.0.0/rules/hc/hc-152@1.0.0.yaml +37 -0
  420. raxe/packs/core/v1.0.0/rules/hc/hc-153@1.0.0.yaml +37 -0
  421. raxe/packs/core/v1.0.0/rules/hc/hc-154@1.0.0.yaml +37 -0
  422. raxe/packs/core/v1.0.0/rules/hc/hc-155@1.0.0.yaml +37 -0
  423. raxe/packs/core/v1.0.0/rules/hc/hc-156@1.0.0.yaml +37 -0
  424. raxe/packs/core/v1.0.0/rules/hc/hc-157@1.0.0.yaml +37 -0
  425. raxe/packs/core/v1.0.0/rules/hc/hc-158@1.0.0.yaml +37 -0
  426. raxe/packs/core/v1.0.0/rules/hc/hc-159@1.0.0.yaml +37 -0
  427. raxe/packs/core/v1.0.0/rules/hc/hc-160@1.0.0.yaml +37 -0
  428. raxe/packs/core/v1.0.0/rules/hc/hc-161@1.0.0.yaml +37 -0
  429. raxe/packs/core/v1.0.0/rules/jb/jb-001@1.0.0.yaml +47 -0
  430. raxe/packs/core/v1.0.0/rules/jb/jb-009@1.0.0.yaml +47 -0
  431. raxe/packs/core/v1.0.0/rules/jb/jb-020@1.0.0.yaml +47 -0
  432. raxe/packs/core/v1.0.0/rules/jb/jb-021@1.0.0.yaml +46 -0
  433. raxe/packs/core/v1.0.0/rules/jb/jb-022@1.0.0.yaml +47 -0
  434. raxe/packs/core/v1.0.0/rules/jb/jb-028@1.0.0.yaml +43 -0
  435. raxe/packs/core/v1.0.0/rules/jb/jb-033@1.0.0.yaml +46 -0
  436. raxe/packs/core/v1.0.0/rules/jb/jb-034@1.0.0.yaml +46 -0
  437. raxe/packs/core/v1.0.0/rules/jb/jb-036@1.0.0.yaml +41 -0
  438. raxe/packs/core/v1.0.0/rules/jb/jb-039@1.0.0.yaml +41 -0
  439. raxe/packs/core/v1.0.0/rules/jb/jb-056@1.0.0.yaml +38 -0
  440. raxe/packs/core/v1.0.0/rules/jb/jb-066@1.0.0.yaml +37 -0
  441. raxe/packs/core/v1.0.0/rules/jb/jb-076@1.0.0.yaml +37 -0
  442. raxe/packs/core/v1.0.0/rules/jb/jb-098@1.0.0.yaml +46 -0
  443. raxe/packs/core/v1.0.0/rules/jb/jb-103@1.0.0.yaml +47 -0
  444. raxe/packs/core/v1.0.0/rules/jb/jb-104@1.0.0.yaml +52 -0
  445. raxe/packs/core/v1.0.0/rules/jb/jb-105@1.0.0.yaml +56 -0
  446. raxe/packs/core/v1.0.0/rules/jb/jb-110@1.0.0.yaml +56 -0
  447. raxe/packs/core/v1.0.0/rules/jb/jb-111@1.0.0.yaml +57 -0
  448. raxe/packs/core/v1.0.0/rules/jb/jb-112@1.0.0.yaml +38 -0
  449. raxe/packs/core/v1.0.0/rules/jb/jb-113@1.0.0.yaml +38 -0
  450. raxe/packs/core/v1.0.0/rules/jb/jb-114@1.0.0.yaml +38 -0
  451. raxe/packs/core/v1.0.0/rules/jb/jb-115@1.0.0.yaml +38 -0
  452. raxe/packs/core/v1.0.0/rules/jb/jb-116@1.0.0.yaml +38 -0
  453. raxe/packs/core/v1.0.0/rules/jb/jb-117@1.0.0.yaml +38 -0
  454. raxe/packs/core/v1.0.0/rules/jb/jb-118@1.0.0.yaml +38 -0
  455. raxe/packs/core/v1.0.0/rules/jb/jb-119@1.0.0.yaml +38 -0
  456. raxe/packs/core/v1.0.0/rules/jb/jb-120@1.0.0.yaml +38 -0
  457. raxe/packs/core/v1.0.0/rules/jb/jb-121@1.0.0.yaml +38 -0
  458. raxe/packs/core/v1.0.0/rules/jb/jb-122@1.0.0.yaml +38 -0
  459. raxe/packs/core/v1.0.0/rules/jb/jb-123@1.0.0.yaml +38 -0
  460. raxe/packs/core/v1.0.0/rules/jb/jb-124@1.0.0.yaml +38 -0
  461. raxe/packs/core/v1.0.0/rules/jb/jb-125@1.0.0.yaml +38 -0
  462. raxe/packs/core/v1.0.0/rules/jb/jb-126@1.0.0.yaml +38 -0
  463. raxe/packs/core/v1.0.0/rules/jb/jb-127@1.0.0.yaml +38 -0
  464. raxe/packs/core/v1.0.0/rules/jb/jb-128@1.0.0.yaml +38 -0
  465. raxe/packs/core/v1.0.0/rules/jb/jb-129@1.0.0.yaml +38 -0
  466. raxe/packs/core/v1.0.0/rules/jb/jb-130@1.0.0.yaml +38 -0
  467. raxe/packs/core/v1.0.0/rules/jb/jb-131@1.0.0.yaml +38 -0
  468. raxe/packs/core/v1.0.0/rules/jb/jb-132@1.0.0.yaml +38 -0
  469. raxe/packs/core/v1.0.0/rules/jb/jb-133@1.0.0.yaml +38 -0
  470. raxe/packs/core/v1.0.0/rules/jb/jb-134@1.0.0.yaml +38 -0
  471. raxe/packs/core/v1.0.0/rules/jb/jb-135@1.0.0.yaml +38 -0
  472. raxe/packs/core/v1.0.0/rules/jb/jb-136@1.0.0.yaml +38 -0
  473. raxe/packs/core/v1.0.0/rules/jb/jb-137@1.0.0.yaml +38 -0
  474. raxe/packs/core/v1.0.0/rules/jb/jb-138@1.0.0.yaml +38 -0
  475. raxe/packs/core/v1.0.0/rules/jb/jb-139@1.0.0.yaml +38 -0
  476. raxe/packs/core/v1.0.0/rules/jb/jb-140@1.0.0.yaml +38 -0
  477. raxe/packs/core/v1.0.0/rules/jb/jb-141@1.0.0.yaml +38 -0
  478. raxe/packs/core/v1.0.0/rules/jb/jb-142@1.0.0.yaml +38 -0
  479. raxe/packs/core/v1.0.0/rules/jb/jb-143@1.0.0.yaml +38 -0
  480. raxe/packs/core/v1.0.0/rules/jb/jb-144@1.0.0.yaml +38 -0
  481. raxe/packs/core/v1.0.0/rules/jb/jb-145@1.0.0.yaml +38 -0
  482. raxe/packs/core/v1.0.0/rules/jb/jb-146@1.0.0.yaml +38 -0
  483. raxe/packs/core/v1.0.0/rules/jb/jb-147@1.0.0.yaml +38 -0
  484. raxe/packs/core/v1.0.0/rules/jb/jb-148@1.0.0.yaml +38 -0
  485. raxe/packs/core/v1.0.0/rules/jb/jb-149@1.0.0.yaml +38 -0
  486. raxe/packs/core/v1.0.0/rules/jb/jb-150@1.0.0.yaml +38 -0
  487. raxe/packs/core/v1.0.0/rules/jb/jb-151@1.0.0.yaml +38 -0
  488. raxe/packs/core/v1.0.0/rules/jb/jb-152@1.0.0.yaml +38 -0
  489. raxe/packs/core/v1.0.0/rules/jb/jb-153@1.0.0.yaml +38 -0
  490. raxe/packs/core/v1.0.0/rules/jb/jb-154@1.0.0.yaml +38 -0
  491. raxe/packs/core/v1.0.0/rules/jb/jb-155@1.0.0.yaml +38 -0
  492. raxe/packs/core/v1.0.0/rules/jb/jb-156@1.0.0.yaml +38 -0
  493. raxe/packs/core/v1.0.0/rules/jb/jb-157@1.0.0.yaml +38 -0
  494. raxe/packs/core/v1.0.0/rules/jb/jb-158@1.0.0.yaml +38 -0
  495. raxe/packs/core/v1.0.0/rules/jb/jb-159@1.0.0.yaml +38 -0
  496. raxe/packs/core/v1.0.0/rules/jb/jb-160@1.0.0.yaml +38 -0
  497. raxe/packs/core/v1.0.0/rules/jb/jb-161@1.0.0.yaml +38 -0
  498. raxe/packs/core/v1.0.0/rules/jb/jb-162@1.0.0.yaml +38 -0
  499. raxe/packs/core/v1.0.0/rules/jb/jb-201@1.0.0.yaml +40 -0
  500. raxe/packs/core/v1.0.0/rules/jb/jb-202@1.0.0.yaml +41 -0
  501. raxe/packs/core/v1.0.0/rules/jb/jb-203@1.0.0.yaml +51 -0
  502. raxe/packs/core/v1.0.0/rules/jb/jb-204@1.0.0.yaml +50 -0
  503. raxe/packs/core/v1.0.0/rules/jb/jb-205@1.0.0.yaml +50 -0
  504. raxe/packs/core/v1.0.0/rules/jb/jb-206@1.0.0.yaml +50 -0
  505. raxe/packs/core/v1.0.0/rules/jb/jb-207@1.0.0.yaml +49 -0
  506. raxe/packs/core/v1.0.0/rules/pii/pii-001@1.0.0.yaml +48 -0
  507. raxe/packs/core/v1.0.0/rules/pii/pii-009@1.0.0.yaml +48 -0
  508. raxe/packs/core/v1.0.0/rules/pii/pii-012@1.0.0.yaml +48 -0
  509. raxe/packs/core/v1.0.0/rules/pii/pii-017@1.0.0.yaml +48 -0
  510. raxe/packs/core/v1.0.0/rules/pii/pii-022@1.0.0.yaml +47 -0
  511. raxe/packs/core/v1.0.0/rules/pii/pii-025@1.0.0.yaml +47 -0
  512. raxe/packs/core/v1.0.0/rules/pii/pii-027@1.0.0.yaml +47 -0
  513. raxe/packs/core/v1.0.0/rules/pii/pii-028@1.0.0.yaml +47 -0
  514. raxe/packs/core/v1.0.0/rules/pii/pii-034@1.0.0.yaml +47 -0
  515. raxe/packs/core/v1.0.0/rules/pii/pii-037@1.0.0.yaml +47 -0
  516. raxe/packs/core/v1.0.0/rules/pii/pii-040@1.0.0.yaml +47 -0
  517. raxe/packs/core/v1.0.0/rules/pii/pii-041@1.0.0.yaml +47 -0
  518. raxe/packs/core/v1.0.0/rules/pii/pii-044@1.0.0.yaml +47 -0
  519. raxe/packs/core/v1.0.0/rules/pii/pii-050@1.0.0.yaml +57 -0
  520. raxe/packs/core/v1.0.0/rules/pii/pii-051@1.0.0.yaml +53 -0
  521. raxe/packs/core/v1.0.0/rules/pii/pii-052@1.0.0.yaml +52 -0
  522. raxe/packs/core/v1.0.0/rules/pii/pii-053@1.0.0.yaml +56 -0
  523. raxe/packs/core/v1.0.0/rules/pii/pii-054@1.0.0.yaml +53 -0
  524. raxe/packs/core/v1.0.0/rules/pii/pii-055@1.0.0.yaml +51 -0
  525. raxe/packs/core/v1.0.0/rules/pii/pii-056@1.0.0.yaml +51 -0
  526. raxe/packs/core/v1.0.0/rules/pii/pii-058@1.0.0.yaml +47 -0
  527. raxe/packs/core/v1.0.0/rules/pii/pii-2015@1.0.0.yaml +41 -0
  528. raxe/packs/core/v1.0.0/rules/pii/pii-2025@1.0.0.yaml +35 -0
  529. raxe/packs/core/v1.0.0/rules/pii/pii-2026@1.0.0.yaml +39 -0
  530. raxe/packs/core/v1.0.0/rules/pii/pii-2035@1.0.0.yaml +39 -0
  531. raxe/packs/core/v1.0.0/rules/pii/pii-2037@1.0.0.yaml +39 -0
  532. raxe/packs/core/v1.0.0/rules/pii/pii-2042@1.0.0.yaml +39 -0
  533. raxe/packs/core/v1.0.0/rules/pii/pii-3001@1.0.0.yaml +39 -0
  534. raxe/packs/core/v1.0.0/rules/pii/pii-3002@1.0.0.yaml +41 -0
  535. raxe/packs/core/v1.0.0/rules/pii/pii-3003@1.0.0.yaml +36 -0
  536. raxe/packs/core/v1.0.0/rules/pii/pii-3004@1.0.0.yaml +41 -0
  537. raxe/packs/core/v1.0.0/rules/pii/pii-3005@1.0.0.yaml +39 -0
  538. raxe/packs/core/v1.0.0/rules/pii/pii-3006@1.0.0.yaml +35 -0
  539. raxe/packs/core/v1.0.0/rules/pii/pii-3007@1.0.0.yaml +37 -0
  540. raxe/packs/core/v1.0.0/rules/pii/pii-3008@1.0.0.yaml +35 -0
  541. raxe/packs/core/v1.0.0/rules/pii/pii-3009@1.0.0.yaml +42 -0
  542. raxe/packs/core/v1.0.0/rules/pii/pii-3010@1.0.0.yaml +39 -0
  543. raxe/packs/core/v1.0.0/rules/pii/pii-3011@1.0.0.yaml +35 -0
  544. raxe/packs/core/v1.0.0/rules/pii/pii-3012@1.0.0.yaml +35 -0
  545. raxe/packs/core/v1.0.0/rules/pii/pii-3013@1.0.0.yaml +36 -0
  546. raxe/packs/core/v1.0.0/rules/pii/pii-3014@1.0.0.yaml +36 -0
  547. raxe/packs/core/v1.0.0/rules/pii/pii-3015@1.0.0.yaml +42 -0
  548. raxe/packs/core/v1.0.0/rules/pii/pii-3016@1.0.0.yaml +42 -0
  549. raxe/packs/core/v1.0.0/rules/pii/pii-3017@1.0.0.yaml +40 -0
  550. raxe/packs/core/v1.0.0/rules/pii/pii-3018@1.0.0.yaml +38 -0
  551. raxe/packs/core/v1.0.0/rules/pii/pii-3019@1.0.0.yaml +40 -0
  552. raxe/packs/core/v1.0.0/rules/pii/pii-3020@1.0.0.yaml +40 -0
  553. raxe/packs/core/v1.0.0/rules/pii/pii-3021@1.0.0.yaml +39 -0
  554. raxe/packs/core/v1.0.0/rules/pii/pii-3022@1.0.0.yaml +36 -0
  555. raxe/packs/core/v1.0.0/rules/pii/pii-3023@1.0.0.yaml +41 -0
  556. raxe/packs/core/v1.0.0/rules/pii/pii-3024@1.0.0.yaml +37 -0
  557. raxe/packs/core/v1.0.0/rules/pii/pii-3025@1.0.0.yaml +38 -0
  558. raxe/packs/core/v1.0.0/rules/pii/pii-3026@1.0.0.yaml +42 -0
  559. raxe/packs/core/v1.0.0/rules/pii/pii-3027@1.0.0.yaml +38 -0
  560. raxe/packs/core/v1.0.0/rules/pii/pii-3028@1.0.0.yaml +42 -0
  561. raxe/packs/core/v1.0.0/rules/pii/pii-3029@1.0.0.yaml +36 -0
  562. raxe/packs/core/v1.0.0/rules/pii/pii-3030@1.0.0.yaml +42 -0
  563. raxe/packs/core/v1.0.0/rules/pii/pii-3031@1.0.0.yaml +37 -0
  564. raxe/packs/core/v1.0.0/rules/pii/pii-3032@1.0.0.yaml +42 -0
  565. raxe/packs/core/v1.0.0/rules/pii/pii-3033@1.0.0.yaml +39 -0
  566. raxe/packs/core/v1.0.0/rules/pii/pii-3034@1.0.0.yaml +40 -0
  567. raxe/packs/core/v1.0.0/rules/pii/pii-3035@1.0.0.yaml +43 -0
  568. raxe/packs/core/v1.0.0/rules/pii/pii-3036@1.0.0.yaml +41 -0
  569. raxe/packs/core/v1.0.0/rules/pii/pii-3037@1.0.0.yaml +35 -0
  570. raxe/packs/core/v1.0.0/rules/pii/pii-3038@1.0.0.yaml +35 -0
  571. raxe/packs/core/v1.0.0/rules/pii/pii-3039@1.0.0.yaml +35 -0
  572. raxe/packs/core/v1.0.0/rules/pii/pii-3040@1.0.0.yaml +41 -0
  573. raxe/packs/core/v1.0.0/rules/pii/pii-3041@1.0.0.yaml +39 -0
  574. raxe/packs/core/v1.0.0/rules/pii/pii-3042@1.0.0.yaml +36 -0
  575. raxe/packs/core/v1.0.0/rules/pii/pii-3043@1.0.0.yaml +35 -0
  576. raxe/packs/core/v1.0.0/rules/pii/pii-3044@1.0.0.yaml +43 -0
  577. raxe/packs/core/v1.0.0/rules/pii/pii-3045@1.0.0.yaml +36 -0
  578. raxe/packs/core/v1.0.0/rules/pii/pii-3046@1.0.0.yaml +37 -0
  579. raxe/packs/core/v1.0.0/rules/pii/pii-3047@1.0.0.yaml +36 -0
  580. raxe/packs/core/v1.0.0/rules/pii/pii-3048@1.0.0.yaml +36 -0
  581. raxe/packs/core/v1.0.0/rules/pii/pii-3049@1.0.0.yaml +38 -0
  582. raxe/packs/core/v1.0.0/rules/pii/pii-3050@1.0.0.yaml +44 -0
  583. raxe/packs/core/v1.0.0/rules/pii/pii-3051@1.0.0.yaml +35 -0
  584. raxe/packs/core/v1.0.0/rules/pii/pii-3052@1.0.0.yaml +36 -0
  585. raxe/packs/core/v1.0.0/rules/pii/pii-3053@1.0.0.yaml +35 -0
  586. raxe/packs/core/v1.0.0/rules/pii/pii-3054@1.0.0.yaml +35 -0
  587. raxe/packs/core/v1.0.0/rules/pii/pii-3055@1.0.0.yaml +40 -0
  588. raxe/packs/core/v1.0.0/rules/pii/pii-3056@1.0.0.yaml +38 -0
  589. raxe/packs/core/v1.0.0/rules/pii/pii-3057@1.0.0.yaml +40 -0
  590. raxe/packs/core/v1.0.0/rules/pii/pii-3058@1.0.0.yaml +43 -0
  591. raxe/packs/core/v1.0.0/rules/pii/pii-3059@1.0.0.yaml +42 -0
  592. raxe/packs/core/v1.0.0/rules/pii/pii-3060@1.0.0.yaml +42 -0
  593. raxe/packs/core/v1.0.0/rules/pii/pii-3061@1.0.0.yaml +50 -0
  594. raxe/packs/core/v1.0.0/rules/pii/pii-3062@1.0.0.yaml +50 -0
  595. raxe/packs/core/v1.0.0/rules/pii/pii-3063@1.0.0.yaml +54 -0
  596. raxe/packs/core/v1.0.0/rules/pii/pii-3064@1.0.0.yaml +78 -0
  597. raxe/packs/core/v1.0.0/rules/pii/pii-3065@1.0.0.yaml +84 -0
  598. raxe/packs/core/v1.0.0/rules/pii/pii-3066@1.0.0.yaml +84 -0
  599. raxe/packs/core/v1.0.0/rules/pii/pii-3067@1.0.0.yaml +88 -0
  600. raxe/packs/core/v1.0.0/rules/pii/pii-3068@1.0.0.yaml +94 -0
  601. raxe/packs/core/v1.0.0/rules/pii/pii-3069@1.0.0.yaml +90 -0
  602. raxe/packs/core/v1.0.0/rules/pii/pii-3070@1.0.0.yaml +99 -0
  603. raxe/packs/core/v1.0.0/rules/pii/pii-3071@1.0.0.yaml +91 -0
  604. raxe/packs/core/v1.0.0/rules/pii/pii-3072@1.0.0.yaml +38 -0
  605. raxe/packs/core/v1.0.0/rules/pii/pii-3073@1.0.0.yaml +38 -0
  606. raxe/packs/core/v1.0.0/rules/pii/pii-3074@1.0.0.yaml +38 -0
  607. raxe/packs/core/v1.0.0/rules/pii/pii-3075@1.0.0.yaml +38 -0
  608. raxe/packs/core/v1.0.0/rules/pii/pii-3076@1.0.0.yaml +38 -0
  609. raxe/packs/core/v1.0.0/rules/pii/pii-3077@1.0.0.yaml +38 -0
  610. raxe/packs/core/v1.0.0/rules/pii/pii-3078@1.0.0.yaml +38 -0
  611. raxe/packs/core/v1.0.0/rules/pii/pii-3079@1.0.0.yaml +38 -0
  612. raxe/packs/core/v1.0.0/rules/pii/pii-3080@1.0.0.yaml +38 -0
  613. raxe/packs/core/v1.0.0/rules/pii/pii-3081@1.0.0.yaml +38 -0
  614. raxe/packs/core/v1.0.0/rules/pii/pii-3082@1.0.0.yaml +38 -0
  615. raxe/packs/core/v1.0.0/rules/pii/pii-3083@1.0.0.yaml +38 -0
  616. raxe/packs/core/v1.0.0/rules/pii/pii-3084@1.0.0.yaml +38 -0
  617. raxe/packs/core/v1.0.0/rules/pii/pii-3085@1.0.0.yaml +38 -0
  618. raxe/packs/core/v1.0.0/rules/rag/rag-016@1.0.0.yaml +47 -0
  619. raxe/packs/core/v1.0.0/rules/rag/rag-028@1.0.0.yaml +47 -0
  620. raxe/packs/core/v1.0.0/rules/rag/rag-042@1.0.0.yaml +47 -0
  621. raxe/packs/core/v1.0.0/rules/rag/rag-044@1.0.0.yaml +47 -0
  622. raxe/packs/core/v1.0.0/rules/rag/rag-045@1.0.0.yaml +47 -0
  623. raxe/packs/core/v1.0.0/rules/rag/rag-050@1.0.0.yaml +47 -0
  624. raxe/packs/core/v1.0.0/rules/rag/rag-201@1.0.0.yaml +41 -0
  625. raxe/packs/core/v1.0.0/rules/rag/rag-202@1.0.0.yaml +41 -0
  626. raxe/packs/core/v1.0.0/rules/rag/rag-3001@1.0.0.yaml +41 -0
  627. raxe/packs/core/v1.0.0/rules/rag/rag-3006@1.0.0.yaml +41 -0
  628. raxe/packs/core/v1.0.0/rules/rag/rag-3009@1.0.0.yaml +41 -0
  629. raxe/packs/core/v1.0.0/rules/rag/rag-3012@1.0.0.yaml +41 -0
  630. raxe/plugins/__init__.py +98 -0
  631. raxe/plugins/custom_rules.py +380 -0
  632. raxe/plugins/loader.py +389 -0
  633. raxe/plugins/manager.py +538 -0
  634. raxe/plugins/protocol.py +428 -0
  635. raxe/py.typed +0 -0
  636. raxe/sdk/__init__.py +77 -0
  637. raxe/sdk/agent_scanner.py +1918 -0
  638. raxe/sdk/client.py +1603 -0
  639. raxe/sdk/decorator.py +175 -0
  640. raxe/sdk/exceptions.py +859 -0
  641. raxe/sdk/integrations/__init__.py +277 -0
  642. raxe/sdk/integrations/agent_scanner.py +71 -0
  643. raxe/sdk/integrations/autogen.py +872 -0
  644. raxe/sdk/integrations/crewai.py +1368 -0
  645. raxe/sdk/integrations/dspy.py +845 -0
  646. raxe/sdk/integrations/extractors.py +363 -0
  647. raxe/sdk/integrations/huggingface.py +395 -0
  648. raxe/sdk/integrations/langchain.py +948 -0
  649. raxe/sdk/integrations/litellm.py +484 -0
  650. raxe/sdk/integrations/llamaindex.py +1049 -0
  651. raxe/sdk/integrations/portkey.py +831 -0
  652. raxe/sdk/suppression_context.py +215 -0
  653. raxe/sdk/wrappers/__init__.py +163 -0
  654. raxe/sdk/wrappers/anthropic.py +310 -0
  655. raxe/sdk/wrappers/openai.py +221 -0
  656. raxe/sdk/wrappers/vertexai.py +484 -0
  657. raxe/utils/__init__.py +12 -0
  658. raxe/utils/error_sanitizer.py +135 -0
  659. raxe/utils/logging.py +241 -0
  660. raxe/utils/performance.py +414 -0
  661. raxe/utils/profiler.py +339 -0
  662. raxe/utils/validators.py +170 -0
  663. raxe-0.4.6.dist-info/METADATA +471 -0
  664. raxe-0.4.6.dist-info/RECORD +668 -0
  665. raxe-0.4.6.dist-info/WHEEL +5 -0
  666. raxe-0.4.6.dist-info/entry_points.txt +2 -0
  667. raxe-0.4.6.dist-info/licenses/LICENSE +56 -0
  668. raxe-0.4.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1123 @@
1
+ """
2
+ SQLite-based dual-priority queue for telemetry events.
3
+
4
+ This module implements a persistent, dual-priority event queue using SQLite.
5
+ Events are routed to critical or standard priority queues based on their type.
6
+ The queue provides state persistence, dead letter queue support, and graceful
7
+ degradation when the database is unavailable.
8
+
9
+ Key Features:
10
+ - Dual priority queues (critical, standard)
11
+ - SQLite with WAL mode for concurrency
12
+ - State persistence across restarts
13
+ - Dead letter queue for failed events
14
+ - Thread-safe operations
15
+ - Graceful degradation on database errors
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import logging
22
+ import sqlite3
23
+ import threading
24
+ from contextlib import contextmanager
25
+ from datetime import datetime, timedelta, timezone
26
+ from enum import Enum
27
+ from pathlib import Path
28
+ from typing import TYPE_CHECKING, Any
29
+
30
+ if TYPE_CHECKING:
31
+ from collections.abc import Generator
32
+
33
+ from raxe.domain.telemetry.events import TelemetryEvent, event_to_dict
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class StateKey(str, Enum):
39
+ """Enumeration of state keys for persistent telemetry state tracking.
40
+
41
+ These keys are used to track installation state, activation milestones,
42
+ and session information across restarts.
43
+
44
+ Activation keys are aligned with backend canonical values.
45
+ """
46
+
47
+ INSTALLATION_FIRED = "installation_fired"
48
+ INSTALLATION_ID = "installation_id"
49
+ INSTALL_TIMESTAMP = "install_timestamp"
50
+ SESSION_COUNT = "session_count"
51
+ # Activation milestones (aligned with backend canonical values)
52
+ ACTIVATED_FIRST_SCAN = "activated_first_scan"
53
+ ACTIVATED_FIRST_THREAT = "activated_first_threat"
54
+ ACTIVATED_FIRST_BLOCK = "activated_first_block"
55
+ ACTIVATED_FIRST_CLI = "activated_first_cli"
56
+ ACTIVATED_FIRST_SDK = "activated_first_sdk"
57
+ ACTIVATED_FIRST_DECORATOR = "activated_first_decorator"
58
+ ACTIVATED_FIRST_WRAPPER = "activated_first_wrapper"
59
+ ACTIVATED_FIRST_LANGCHAIN = "activated_first_langchain"
60
+ ACTIVATED_FIRST_L2_DETECTION = "activated_first_l2_detection"
61
+ ACTIVATED_FIRST_CUSTOM_RULE = "activated_first_custom_rule"
62
+ # API key ID tracking for consistency across auth and telemetry flows
63
+ CURRENT_API_KEY_ID = "current_api_key_id"
64
+
65
+
66
+ class DualQueue:
67
+ """
68
+ SQLite-based dual-priority queue for telemetry events.
69
+
70
+ Provides separate queues for critical and standard priority events with
71
+ persistent state storage, dead letter queue support, and graceful error handling.
72
+
73
+ Features:
74
+ - Dual priority queues (critical sent first, standard batched)
75
+ - WAL mode for concurrent access
76
+ - State persistence across restarts
77
+ - Dead letter queue for failed events after max retries
78
+ - Thread-safe operations with connection pooling
79
+ - Graceful degradation on database errors
80
+
81
+ Example:
82
+ >>> queue = DualQueue()
83
+ >>> event = create_scan_event(...)
84
+ >>> event_id = queue.enqueue(event)
85
+ >>> batch = queue.dequeue_critical(batch_size=50)
86
+ >>> queue.mark_batch_sent([e["event_id"] for e in batch])
87
+ """
88
+
89
+ # Schema version for migrations
90
+ _SCHEMA_VERSION = 1
91
+
92
+ def __init__(
93
+ self,
94
+ db_path: Path | None = None,
95
+ critical_max_size: int = 10_000,
96
+ standard_max_size: int = 50_000,
97
+ *,
98
+ max_retry_count: int = 3,
99
+ enable_wal: bool = True,
100
+ ) -> None:
101
+ """
102
+ Initialize the dual-priority queue.
103
+
104
+ Args:
105
+ db_path: Path to SQLite database (default: ~/.raxe/telemetry.db).
106
+ critical_max_size: Maximum events in critical queue before overflow.
107
+ standard_max_size: Maximum events in standard queue before overflow.
108
+ max_retry_count: Maximum retries before moving to dead letter queue.
109
+ enable_wal: Enable Write-Ahead Logging for better concurrency.
110
+ """
111
+ self.db_path = db_path or Path.home() / ".raxe" / "telemetry.db"
112
+ self.critical_max_size = critical_max_size
113
+ self.standard_max_size = standard_max_size
114
+ self.max_retry_count = max_retry_count
115
+ self._enable_wal = enable_wal
116
+ self._lock = threading.Lock()
117
+ self._closed = False
118
+
119
+ # Ensure directory exists
120
+ try:
121
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
122
+ except OSError as e:
123
+ logger.error(f"Failed to create telemetry directory: {e}")
124
+ # Continue - we'll handle database errors gracefully
125
+
126
+ # Initialize database
127
+ self._init_database()
128
+
129
+ logger.debug(f"DualQueue initialized at {self.db_path}")
130
+
131
+ def _init_database(self) -> None:
132
+ """Initialize database schema with all required tables and indexes."""
133
+ try:
134
+ with self._get_connection() as conn:
135
+ # Enable WAL mode for better concurrency
136
+ if self._enable_wal:
137
+ conn.execute("PRAGMA journal_mode=WAL")
138
+ conn.execute("PRAGMA synchronous=NORMAL")
139
+
140
+ # Set busy timeout for concurrent access
141
+ conn.execute("PRAGMA busy_timeout=5000")
142
+
143
+ # Create events table with priority routing
144
+ conn.execute("""
145
+ CREATE TABLE IF NOT EXISTS telemetry_events (
146
+ event_id TEXT PRIMARY KEY,
147
+ event_type TEXT NOT NULL,
148
+ priority TEXT NOT NULL CHECK (priority IN ('critical', 'standard')),
149
+ payload TEXT NOT NULL,
150
+ created_at TEXT NOT NULL,
151
+ retry_count INTEGER DEFAULT 0,
152
+ retry_after TEXT,
153
+ batch_id TEXT
154
+ )
155
+ """)
156
+
157
+ # Create state flags table
158
+ conn.execute("""
159
+ CREATE TABLE IF NOT EXISTS telemetry_state (
160
+ key TEXT PRIMARY KEY,
161
+ value TEXT NOT NULL,
162
+ updated_at TEXT NOT NULL
163
+ )
164
+ """)
165
+
166
+ # Create dead letter queue
167
+ conn.execute("""
168
+ CREATE TABLE IF NOT EXISTS telemetry_dlq (
169
+ event_id TEXT PRIMARY KEY,
170
+ event_type TEXT NOT NULL,
171
+ priority TEXT NOT NULL,
172
+ payload TEXT NOT NULL,
173
+ created_at TEXT NOT NULL,
174
+ failed_at TEXT NOT NULL,
175
+ failure_reason TEXT,
176
+ retry_count INTEGER,
177
+ server_error_code TEXT,
178
+ server_error_message TEXT
179
+ )
180
+ """)
181
+
182
+ # Create stats table for tracking sent events
183
+ conn.execute("""
184
+ CREATE TABLE IF NOT EXISTS telemetry_stats (
185
+ stat_key TEXT PRIMARY KEY,
186
+ stat_value INTEGER DEFAULT 0,
187
+ updated_at TEXT NOT NULL
188
+ )
189
+ """)
190
+
191
+ # Initialize stats if not present
192
+ now = datetime.now(timezone.utc).isoformat()
193
+ conn.execute("""
194
+ INSERT OR IGNORE INTO telemetry_stats (stat_key, stat_value, updated_at)
195
+ VALUES ('events_sent_total', 0, ?), ('batches_sent_total', 0, ?), ('scans_sent_total', 0, ?)
196
+ """, (now, now, now))
197
+
198
+ # Create indexes for efficient queries
199
+ conn.execute("""
200
+ CREATE INDEX IF NOT EXISTS idx_events_priority
201
+ ON telemetry_events(priority, created_at)
202
+ """)
203
+
204
+ conn.execute("""
205
+ CREATE INDEX IF NOT EXISTS idx_events_retry
206
+ ON telemetry_events(retry_after)
207
+ """)
208
+
209
+ conn.execute("""
210
+ CREATE INDEX IF NOT EXISTS idx_dlq_failed
211
+ ON telemetry_dlq(failed_at)
212
+ """)
213
+
214
+ conn.commit()
215
+
216
+ except sqlite3.Error as e:
217
+ logger.error(f"Failed to initialize database: {e}")
218
+ # Continue - graceful degradation
219
+
220
+ @contextmanager
221
+ def _get_connection(self) -> Generator[sqlite3.Connection, None, None]:
222
+ """Get a database connection context manager.
223
+
224
+ Returns:
225
+ SQLite connection with proper cleanup.
226
+
227
+ Raises:
228
+ sqlite3.Error: If connection cannot be established.
229
+ """
230
+ conn = sqlite3.connect(str(self.db_path), timeout=10.0)
231
+ try:
232
+ yield conn
233
+ finally:
234
+ conn.close()
235
+
236
+ def enqueue(self, event: TelemetryEvent) -> str:
237
+ """
238
+ Add an event to the appropriate priority queue.
239
+
240
+ Events are routed to critical or standard queue based on their priority
241
+ attribute. Queue overflow is handled by dropping oldest events from the
242
+ same priority.
243
+
244
+ Args:
245
+ event: TelemetryEvent to enqueue.
246
+
247
+ Returns:
248
+ Event ID of the enqueued event.
249
+
250
+ Example:
251
+ >>> event = create_scan_event(...)
252
+ >>> event_id = queue.enqueue(event)
253
+ """
254
+ if self._closed:
255
+ logger.warning("Attempted to enqueue to closed queue")
256
+ return event.event_id
257
+
258
+ event_dict = event_to_dict(event)
259
+ priority = event.priority
260
+
261
+ try:
262
+ with self._lock:
263
+ with self._get_connection() as conn:
264
+ # Check queue size for this priority
265
+ count = conn.execute(
266
+ "SELECT COUNT(*) FROM telemetry_events WHERE priority = ?",
267
+ (priority,),
268
+ ).fetchone()[0]
269
+
270
+ max_size = (
271
+ self.critical_max_size if priority == "critical" else self.standard_max_size
272
+ )
273
+
274
+ if count >= max_size:
275
+ # Handle overflow - drop oldest event from same priority
276
+ self._handle_overflow(conn, priority)
277
+
278
+ # Insert new event
279
+ conn.execute(
280
+ """
281
+ INSERT INTO telemetry_events (
282
+ event_id, event_type, priority, payload, created_at
283
+ ) VALUES (?, ?, ?, ?, ?)
284
+ """,
285
+ (
286
+ event.event_id,
287
+ event.event_type,
288
+ priority,
289
+ json.dumps(event_dict["payload"]),
290
+ event.timestamp,
291
+ ),
292
+ )
293
+
294
+ conn.commit()
295
+
296
+ logger.debug(f"Enqueued {priority} event {event.event_id} ({event.event_type})")
297
+
298
+ except sqlite3.Error as e:
299
+ logger.error(f"Failed to enqueue event: {e}")
300
+ # Graceful degradation - event is lost but application continues
301
+
302
+ return event.event_id
303
+
304
+ def _handle_overflow(self, conn: sqlite3.Connection, priority: str) -> None:
305
+ """Handle queue overflow by dropping oldest event from same priority.
306
+
307
+ Args:
308
+ conn: Active database connection.
309
+ priority: Priority queue experiencing overflow.
310
+ """
311
+ result = conn.execute(
312
+ """
313
+ SELECT event_id FROM telemetry_events
314
+ WHERE priority = ?
315
+ ORDER BY created_at ASC
316
+ LIMIT 1
317
+ """,
318
+ (priority,),
319
+ ).fetchone()
320
+
321
+ if result:
322
+ event_id = result[0]
323
+ conn.execute("DELETE FROM telemetry_events WHERE event_id = ?", (event_id,))
324
+ logger.warning(f"Dropped oldest {priority} event {event_id} due to queue overflow")
325
+
326
+ def dequeue_critical(self, batch_size: int = 100) -> list[dict[str, Any]]:
327
+ """
328
+ Dequeue a batch of critical priority events.
329
+
330
+ Events are returned in FIFO order, skipping events with retry_after
331
+ in the future.
332
+
333
+ Args:
334
+ batch_size: Maximum number of events to dequeue.
335
+
336
+ Returns:
337
+ List of event dictionaries ready for sending.
338
+
339
+ Example:
340
+ >>> events = queue.dequeue_critical(batch_size=50)
341
+ >>> for event in events:
342
+ ... print(event["event_id"])
343
+ """
344
+ return self._dequeue_by_priority("critical", batch_size)
345
+
346
+ def dequeue_standard(self, batch_size: int = 100) -> list[dict[str, Any]]:
347
+ """
348
+ Dequeue a batch of standard priority events.
349
+
350
+ Events are returned in FIFO order, skipping events with retry_after
351
+ in the future.
352
+
353
+ Args:
354
+ batch_size: Maximum number of events to dequeue.
355
+
356
+ Returns:
357
+ List of event dictionaries ready for sending.
358
+
359
+ Example:
360
+ >>> events = queue.dequeue_standard(batch_size=100)
361
+ """
362
+ return self._dequeue_by_priority("standard", batch_size)
363
+
364
+ def _dequeue_by_priority(self, priority: str, batch_size: int) -> list[dict[str, Any]]:
365
+ """Dequeue events of a specific priority.
366
+
367
+ Args:
368
+ priority: Priority level to dequeue ("critical" or "standard").
369
+ batch_size: Maximum number of events to return.
370
+
371
+ Returns:
372
+ List of event dictionaries.
373
+ """
374
+ if self._closed:
375
+ logger.warning("Attempted to dequeue from closed queue")
376
+ return []
377
+
378
+ events: list[dict[str, Any]] = []
379
+ now = datetime.now(timezone.utc).isoformat()
380
+
381
+ try:
382
+ with self._lock:
383
+ with self._get_connection() as conn:
384
+ # Select events ready for processing (not in retry backoff)
385
+ cursor = conn.execute(
386
+ """
387
+ SELECT event_id, event_type, priority, payload, created_at,
388
+ retry_count, retry_after, batch_id
389
+ FROM telemetry_events
390
+ WHERE priority = ?
391
+ AND batch_id IS NULL
392
+ AND (retry_after IS NULL OR retry_after <= ?)
393
+ ORDER BY created_at ASC
394
+ LIMIT ?
395
+ """,
396
+ (priority, now, batch_size),
397
+ )
398
+
399
+ for row in cursor:
400
+ event_dict = {
401
+ "event_id": row[0],
402
+ "event_type": row[1],
403
+ "priority": row[2],
404
+ "payload": json.loads(row[3]),
405
+ "timestamp": row[4],
406
+ "retry_count": row[5],
407
+ }
408
+ events.append(event_dict)
409
+
410
+ logger.debug(f"Dequeued {len(events)} {priority} events")
411
+
412
+ except sqlite3.Error as e:
413
+ logger.error(f"Failed to dequeue events: {e}")
414
+
415
+ return events
416
+
417
+ def mark_batch_sent(self, event_ids: list[str]) -> None:
418
+ """
419
+ Mark events as successfully sent and remove from queue.
420
+
421
+ Also updates lifetime stats (events_sent_total, batches_sent_total).
422
+
423
+ Args:
424
+ event_ids: List of event IDs to mark as sent.
425
+
426
+ Example:
427
+ >>> queue.mark_batch_sent(["evt_abc123", "evt_def456"])
428
+ """
429
+ if not event_ids:
430
+ return
431
+
432
+ if self._closed:
433
+ logger.warning("Attempted to mark batch on closed queue")
434
+ return
435
+
436
+ try:
437
+ with self._lock:
438
+ with self._get_connection() as conn:
439
+ # Security: Safe - placeholders constructed from fixed '?' list
440
+ placeholders = ",".join(["?"] * len(event_ids))
441
+
442
+ # Count scan events BEFORE deleting (for scans_sent_total stat)
443
+ count_sql = f"SELECT COUNT(*) FROM telemetry_events WHERE event_id IN ({placeholders}) AND event_type = 'scan'" # noqa: S608
444
+ scan_count = conn.execute(count_sql, event_ids).fetchone()[0]
445
+
446
+ # Delete events
447
+ sql = f"DELETE FROM telemetry_events WHERE event_id IN ({placeholders})" # noqa: S608
448
+ conn.execute(sql, event_ids)
449
+
450
+ # Update lifetime stats
451
+ now = datetime.now(timezone.utc).isoformat()
452
+ conn.execute("""
453
+ UPDATE telemetry_stats
454
+ SET stat_value = stat_value + ?, updated_at = ?
455
+ WHERE stat_key = 'events_sent_total'
456
+ """, (len(event_ids), now))
457
+ conn.execute("""
458
+ UPDATE telemetry_stats
459
+ SET stat_value = stat_value + ?, updated_at = ?
460
+ WHERE stat_key = 'scans_sent_total'
461
+ """, (scan_count, now))
462
+ conn.execute("""
463
+ UPDATE telemetry_stats
464
+ SET stat_value = stat_value + 1, updated_at = ?
465
+ WHERE stat_key = 'batches_sent_total'
466
+ """, (now,))
467
+
468
+ conn.commit()
469
+
470
+ logger.debug(f"Marked {len(event_ids)} events ({scan_count} scans) as sent")
471
+
472
+ except sqlite3.Error as e:
473
+ logger.error(f"Failed to mark batch sent: {e}")
474
+
475
+ def mark_batch_failed(self, event_ids: list[str], error: str, retry_delay_seconds: int) -> None:
476
+ """
477
+ Mark events as failed and schedule for retry.
478
+
479
+ Events exceeding max_retry_count are moved to dead letter queue.
480
+
481
+ Args:
482
+ event_ids: List of event IDs that failed.
483
+ error: Error message describing failure.
484
+ retry_delay_seconds: Seconds to wait before retry.
485
+
486
+ Example:
487
+ >>> queue.mark_batch_failed(
488
+ ... ["evt_abc123"],
489
+ ... "Connection timeout",
490
+ ... retry_delay_seconds=60,
491
+ ... )
492
+ """
493
+ if not event_ids:
494
+ return
495
+
496
+ if self._closed:
497
+ logger.warning("Attempted to mark batch failed on closed queue")
498
+ return
499
+
500
+ try:
501
+ with self._lock:
502
+ with self._get_connection() as conn:
503
+ retry_after = datetime.now(timezone.utc) + timedelta(
504
+ seconds=retry_delay_seconds
505
+ )
506
+ retry_after_str = retry_after.isoformat()
507
+ now_str = datetime.now(timezone.utc).isoformat()
508
+
509
+ # Get current retry counts for all events
510
+ # Security: Safe - placeholders constructed from fixed '?' list
511
+ placeholders = ",".join(["?"] * len(event_ids))
512
+ sql = f"""
513
+ SELECT event_id, event_type, priority, payload, created_at, retry_count
514
+ FROM telemetry_events
515
+ WHERE event_id IN ({placeholders})
516
+ """ # noqa: S608
517
+ cursor = conn.execute(sql, event_ids)
518
+
519
+ dlq_events = []
520
+ retry_events = []
521
+
522
+ for row in cursor:
523
+ event_id = row[0]
524
+ retry_count = row[5]
525
+
526
+ if retry_count >= self.max_retry_count:
527
+ dlq_events.append(
528
+ {
529
+ "event_id": event_id,
530
+ "event_type": row[1],
531
+ "priority": row[2],
532
+ "payload": row[3],
533
+ "created_at": row[4],
534
+ "retry_count": retry_count,
535
+ }
536
+ )
537
+ else:
538
+ retry_events.append(event_id)
539
+
540
+ # Move max-retry events to dead letter queue
541
+ for dlq_event in dlq_events:
542
+ conn.execute(
543
+ """
544
+ INSERT INTO telemetry_dlq (
545
+ event_id, event_type, priority, payload, created_at,
546
+ failed_at, failure_reason, retry_count
547
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
548
+ """,
549
+ (
550
+ dlq_event["event_id"],
551
+ dlq_event["event_type"],
552
+ dlq_event["priority"],
553
+ dlq_event["payload"],
554
+ dlq_event["created_at"],
555
+ now_str,
556
+ error,
557
+ dlq_event["retry_count"],
558
+ ),
559
+ )
560
+
561
+ if dlq_events:
562
+ dlq_ids = [e["event_id"] for e in dlq_events]
563
+ placeholders = ",".join(["?"] * len(dlq_ids))
564
+ sql = f"DELETE FROM telemetry_events WHERE event_id IN ({placeholders})" # noqa: S608
565
+ conn.execute(sql, dlq_ids)
566
+ logger.warning(f"Moved {len(dlq_events)} events to dead letter queue")
567
+
568
+ # Update retry events
569
+ if retry_events:
570
+ placeholders = ",".join(["?"] * len(retry_events))
571
+ sql = f"""
572
+ UPDATE telemetry_events
573
+ SET batch_id = NULL,
574
+ retry_count = retry_count + 1,
575
+ retry_after = ?
576
+ WHERE event_id IN ({placeholders})
577
+ """ # noqa: S608
578
+ conn.execute(sql, [retry_after_str, *retry_events])
579
+
580
+ conn.commit()
581
+
582
+ logger.debug(
583
+ f"Marked {len(event_ids)} events as failed, "
584
+ f"{len(retry_events)} will retry, {len(dlq_events)} moved to DLQ"
585
+ )
586
+
587
+ except sqlite3.Error as e:
588
+ logger.error(f"Failed to mark batch failed: {e}")
589
+
590
+ def get_state(self, key: str | StateKey) -> str | None:
591
+ """
592
+ Get a state value by key.
593
+
594
+ Args:
595
+ key: State key (StateKey enum or string).
596
+
597
+ Returns:
598
+ State value if exists, None otherwise.
599
+
600
+ Example:
601
+ >>> if queue.get_state(StateKey.INSTALLATION_FIRED):
602
+ ... print("Installation already fired")
603
+ """
604
+ if self._closed:
605
+ return None
606
+
607
+ key_str = key.value if isinstance(key, StateKey) else key
608
+
609
+ try:
610
+ with self._get_connection() as conn:
611
+ result = conn.execute(
612
+ "SELECT value FROM telemetry_state WHERE key = ?", (key_str,)
613
+ ).fetchone()
614
+ return result[0] if result else None
615
+
616
+ except sqlite3.Error as e:
617
+ logger.error(f"Failed to get state: {e}")
618
+ return None
619
+
620
+ def set_state(self, key: str | StateKey, value: str) -> None:
621
+ """
622
+ Set a state value.
623
+
624
+ Args:
625
+ key: State key (StateKey enum or string).
626
+ value: Value to store.
627
+
628
+ Example:
629
+ >>> queue.set_state(StateKey.INSTALLATION_FIRED, "true")
630
+ """
631
+ if self._closed:
632
+ logger.warning("Attempted to set state on closed queue")
633
+ return
634
+
635
+ key_str = key.value if isinstance(key, StateKey) else key
636
+ now = datetime.now(timezone.utc).isoformat()
637
+
638
+ try:
639
+ with self._lock:
640
+ with self._get_connection() as conn:
641
+ conn.execute(
642
+ """
643
+ INSERT OR REPLACE INTO telemetry_state (key, value, updated_at)
644
+ VALUES (?, ?, ?)
645
+ """,
646
+ (key_str, value, now),
647
+ )
648
+ conn.commit()
649
+
650
+ except sqlite3.Error as e:
651
+ logger.error(f"Failed to set state: {e}")
652
+
653
+ def has_state(self, key: str | StateKey) -> bool:
654
+ """
655
+ Check if a state key exists.
656
+
657
+ Args:
658
+ key: State key to check.
659
+
660
+ Returns:
661
+ True if key exists, False otherwise.
662
+
663
+ Example:
664
+ >>> if not queue.has_state(StateKey.ACTIVATED_FIRST_SCAN):
665
+ ... # Fire first scan activation event
666
+ ... pass
667
+ """
668
+ return self.get_state(key) is not None
669
+
670
+ def increment_state(self, key: str | StateKey, default: int = 0) -> int:
671
+ """
672
+ Atomically increment a numeric state value.
673
+
674
+ Args:
675
+ key: State key to increment.
676
+ default: Default value if key doesn't exist.
677
+
678
+ Returns:
679
+ New value after increment.
680
+
681
+ Example:
682
+ >>> session_num = queue.increment_state(StateKey.SESSION_COUNT)
683
+ >>> print(f"Session #{session_num}")
684
+ """
685
+ if self._closed:
686
+ logger.warning("Attempted to increment state on closed queue")
687
+ return default
688
+
689
+ key_str = key.value if isinstance(key, StateKey) else key
690
+ now = datetime.now(timezone.utc).isoformat()
691
+
692
+ try:
693
+ with self._lock:
694
+ with self._get_connection() as conn:
695
+ # Get current value
696
+ result = conn.execute(
697
+ "SELECT value FROM telemetry_state WHERE key = ?", (key_str,)
698
+ ).fetchone()
699
+
700
+ current = int(result[0]) if result else default
701
+ new_value = current + 1
702
+
703
+ conn.execute(
704
+ """
705
+ INSERT OR REPLACE INTO telemetry_state (key, value, updated_at)
706
+ VALUES (?, ?, ?)
707
+ """,
708
+ (key_str, str(new_value), now),
709
+ )
710
+ conn.commit()
711
+
712
+ return new_value
713
+
714
+ except (sqlite3.Error, ValueError) as e:
715
+ logger.error(f"Failed to increment state: {e}")
716
+ return default
717
+
718
+ def move_to_dlq(
719
+ self,
720
+ event_id: str,
721
+ reason: str,
722
+ server_code: str | None = None,
723
+ server_message: str | None = None,
724
+ ) -> None:
725
+ """
726
+ Manually move an event to the dead letter queue.
727
+
728
+ Args:
729
+ event_id: Event ID to move.
730
+ reason: Reason for moving to DLQ.
731
+ server_code: HTTP error code from server.
732
+ server_message: Error message from server.
733
+
734
+ Example:
735
+ >>> queue.move_to_dlq(
736
+ ... "evt_abc123",
737
+ ... "Permanent failure",
738
+ ... server_code="400",
739
+ ... server_message="Invalid payload",
740
+ ... )
741
+ """
742
+ if self._closed:
743
+ logger.warning("Attempted to move to DLQ on closed queue")
744
+ return
745
+
746
+ now_str = datetime.now(timezone.utc).isoformat()
747
+
748
+ try:
749
+ with self._lock:
750
+ with self._get_connection() as conn:
751
+ # Get event from queue
752
+ result = conn.execute(
753
+ """
754
+ SELECT event_id, event_type, priority, payload, created_at, retry_count
755
+ FROM telemetry_events
756
+ WHERE event_id = ?
757
+ """,
758
+ (event_id,),
759
+ ).fetchone()
760
+
761
+ if not result:
762
+ logger.warning(f"Event {event_id} not found for DLQ move")
763
+ return
764
+
765
+ # Insert into DLQ
766
+ conn.execute(
767
+ """
768
+ INSERT INTO telemetry_dlq (
769
+ event_id, event_type, priority, payload, created_at,
770
+ failed_at, failure_reason, retry_count,
771
+ server_error_code, server_error_message
772
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
773
+ """,
774
+ (
775
+ result[0], # event_id
776
+ result[1], # event_type
777
+ result[2], # priority
778
+ result[3], # payload
779
+ result[4], # created_at
780
+ now_str,
781
+ reason,
782
+ result[5], # retry_count
783
+ server_code,
784
+ server_message,
785
+ ),
786
+ )
787
+
788
+ # Remove from main queue
789
+ conn.execute("DELETE FROM telemetry_events WHERE event_id = ?", (event_id,))
790
+
791
+ conn.commit()
792
+
793
+ logger.info(f"Moved event {event_id} to DLQ: {reason}")
794
+
795
+ except sqlite3.Error as e:
796
+ logger.error(f"Failed to move event to DLQ: {e}")
797
+
798
+ def get_dlq_events(self, limit: int = 100) -> list[dict[str, Any]]:
799
+ """
800
+ Get events from the dead letter queue.
801
+
802
+ Args:
803
+ limit: Maximum number of events to return.
804
+
805
+ Returns:
806
+ List of DLQ event dictionaries.
807
+
808
+ Example:
809
+ >>> dlq_events = queue.get_dlq_events(limit=50)
810
+ >>> for event in dlq_events:
811
+ ... print(f"{event['event_id']}: {event['failure_reason']}")
812
+ """
813
+ if self._closed:
814
+ return []
815
+
816
+ events: list[dict[str, Any]] = []
817
+
818
+ try:
819
+ with self._get_connection() as conn:
820
+ cursor = conn.execute(
821
+ """
822
+ SELECT event_id, event_type, priority, payload, created_at,
823
+ failed_at, failure_reason, retry_count,
824
+ server_error_code, server_error_message
825
+ FROM telemetry_dlq
826
+ ORDER BY failed_at DESC
827
+ LIMIT ?
828
+ """,
829
+ (limit,),
830
+ )
831
+
832
+ for row in cursor:
833
+ events.append(
834
+ {
835
+ "event_id": row[0],
836
+ "event_type": row[1],
837
+ "priority": row[2],
838
+ "payload": json.loads(row[3]),
839
+ "created_at": row[4],
840
+ "failed_at": row[5],
841
+ "failure_reason": row[6],
842
+ "retry_count": row[7],
843
+ "server_error_code": row[8],
844
+ "server_error_message": row[9],
845
+ }
846
+ )
847
+
848
+ except sqlite3.Error as e:
849
+ logger.error(f"Failed to get DLQ events: {e}")
850
+
851
+ return events
852
+
853
+ def retry_dlq_events(self, event_ids: list[str] | None = None) -> int:
854
+ """
855
+ Move events from DLQ back to main queue for retry.
856
+
857
+ Args:
858
+ event_ids: Specific event IDs to retry, or None for all.
859
+
860
+ Returns:
861
+ Number of events moved back to queue.
862
+
863
+ Example:
864
+ >>> # Retry specific events
865
+ >>> count = queue.retry_dlq_events(["evt_abc123"])
866
+ >>> # Retry all DLQ events
867
+ >>> count = queue.retry_dlq_events()
868
+ """
869
+ if self._closed:
870
+ logger.warning("Attempted to retry DLQ on closed queue")
871
+ return 0
872
+
873
+ moved_count = 0
874
+
875
+ try:
876
+ with self._lock:
877
+ with self._get_connection() as conn:
878
+ if event_ids:
879
+ # Retry specific events
880
+ # Security: Safe - placeholders constructed from fixed '?' list
881
+ placeholders = ",".join(["?"] * len(event_ids))
882
+ sql = f"""
883
+ SELECT event_id, event_type, priority, payload, created_at
884
+ FROM telemetry_dlq
885
+ WHERE event_id IN ({placeholders})
886
+ """ # noqa: S608
887
+ cursor = conn.execute(sql, event_ids)
888
+ else:
889
+ # Retry all events
890
+ cursor = conn.execute(
891
+ """
892
+ SELECT event_id, event_type, priority, payload, created_at
893
+ FROM telemetry_dlq
894
+ """
895
+ )
896
+
897
+ events_to_move = list(cursor)
898
+
899
+ for row in events_to_move:
900
+ # Insert back into main queue with reset retry count
901
+ conn.execute(
902
+ """
903
+ INSERT INTO telemetry_events (
904
+ event_id, event_type, priority, payload, created_at, retry_count
905
+ ) VALUES (?, ?, ?, ?, ?, 0)
906
+ """,
907
+ (row[0], row[1], row[2], row[3], row[4]),
908
+ )
909
+
910
+ # Remove from DLQ
911
+ conn.execute("DELETE FROM telemetry_dlq WHERE event_id = ?", (row[0],))
912
+
913
+ moved_count += 1
914
+
915
+ conn.commit()
916
+
917
+ if moved_count:
918
+ logger.info(f"Moved {moved_count} events from DLQ back to queue")
919
+
920
+ except sqlite3.Error as e:
921
+ logger.error(f"Failed to retry DLQ events: {e}")
922
+
923
+ return moved_count
924
+
925
+ def clear_dlq(self, older_than_days: int | None = None) -> int:
926
+ """
927
+ Clear events from the dead letter queue.
928
+
929
+ Args:
930
+ older_than_days: Only clear events older than this many days,
931
+ or None to clear all.
932
+
933
+ Returns:
934
+ Number of events cleared.
935
+
936
+ Example:
937
+ >>> # Clear events older than 30 days
938
+ >>> cleared = queue.clear_dlq(older_than_days=30)
939
+ >>> # Clear all DLQ events
940
+ >>> cleared = queue.clear_dlq()
941
+ """
942
+ if self._closed:
943
+ logger.warning("Attempted to clear DLQ on closed queue")
944
+ return 0
945
+
946
+ cleared_count = 0
947
+
948
+ try:
949
+ with self._lock:
950
+ with self._get_connection() as conn:
951
+ if older_than_days is not None:
952
+ cutoff = datetime.now(timezone.utc) - timedelta(days=older_than_days)
953
+ cutoff_str = cutoff.isoformat()
954
+
955
+ result = conn.execute(
956
+ "SELECT COUNT(*) FROM telemetry_dlq WHERE failed_at < ?",
957
+ (cutoff_str,),
958
+ ).fetchone()
959
+ cleared_count = result[0] if result else 0
960
+
961
+ conn.execute(
962
+ "DELETE FROM telemetry_dlq WHERE failed_at < ?",
963
+ (cutoff_str,),
964
+ )
965
+ else:
966
+ result = conn.execute("SELECT COUNT(*) FROM telemetry_dlq").fetchone()
967
+ cleared_count = result[0] if result else 0
968
+
969
+ conn.execute("DELETE FROM telemetry_dlq")
970
+
971
+ conn.commit()
972
+
973
+ if cleared_count:
974
+ logger.info(f"Cleared {cleared_count} events from DLQ")
975
+
976
+ except sqlite3.Error as e:
977
+ logger.error(f"Failed to clear DLQ: {e}")
978
+
979
+ return cleared_count
980
+
981
+ def get_stats(self) -> dict[str, Any]:
982
+ """
983
+ Get queue statistics.
984
+
985
+ Returns:
986
+ Dictionary with queue metrics including:
987
+ - critical_count: Events in critical queue
988
+ - standard_count: Events in standard queue
989
+ - dlq_count: Events in dead letter queue
990
+ - total_queued: Total events across all queues
991
+ - oldest_critical: Timestamp of oldest critical event
992
+ - oldest_standard: Timestamp of oldest standard event
993
+ - retry_pending: Events waiting for retry
994
+
995
+ Example:
996
+ >>> stats = queue.get_stats()
997
+ >>> print(f"Critical: {stats['critical_count']}")
998
+ >>> print(f"DLQ: {stats['dlq_count']}")
999
+ """
1000
+ if self._closed:
1001
+ return {
1002
+ "critical_count": 0,
1003
+ "standard_count": 0,
1004
+ "dlq_count": 0,
1005
+ "total_queued": 0,
1006
+ "oldest_critical": None,
1007
+ "oldest_standard": None,
1008
+ "retry_pending": 0,
1009
+ }
1010
+
1011
+ stats: dict[str, Any] = {}
1012
+
1013
+ try:
1014
+ with self._get_connection() as conn:
1015
+ # Count by priority
1016
+ critical_count = conn.execute(
1017
+ "SELECT COUNT(*) FROM telemetry_events WHERE priority = 'critical'"
1018
+ ).fetchone()[0]
1019
+
1020
+ standard_count = conn.execute(
1021
+ "SELECT COUNT(*) FROM telemetry_events WHERE priority = 'standard'"
1022
+ ).fetchone()[0]
1023
+
1024
+ dlq_count = conn.execute("SELECT COUNT(*) FROM telemetry_dlq").fetchone()[0]
1025
+
1026
+ # Get oldest events
1027
+ oldest_critical = conn.execute(
1028
+ """
1029
+ SELECT MIN(created_at) FROM telemetry_events WHERE priority = 'critical'
1030
+ """
1031
+ ).fetchone()[0]
1032
+
1033
+ oldest_standard = conn.execute(
1034
+ """
1035
+ SELECT MIN(created_at) FROM telemetry_events WHERE priority = 'standard'
1036
+ """
1037
+ ).fetchone()[0]
1038
+
1039
+ # Events in retry backoff
1040
+ now = datetime.now(timezone.utc).isoformat()
1041
+ retry_pending = conn.execute(
1042
+ """
1043
+ SELECT COUNT(*) FROM telemetry_events
1044
+ WHERE retry_after IS NOT NULL AND retry_after > ?
1045
+ """,
1046
+ (now,),
1047
+ ).fetchone()[0]
1048
+
1049
+ # Get lifetime stats (events_sent_total, batches_sent_total, scans_sent_total)
1050
+ events_sent_total = 0
1051
+ batches_sent_total = 0
1052
+ scans_sent_total = 0
1053
+ try:
1054
+ result = conn.execute(
1055
+ "SELECT stat_key, stat_value FROM telemetry_stats"
1056
+ ).fetchall()
1057
+ for row in result:
1058
+ if row[0] == "events_sent_total":
1059
+ events_sent_total = row[1]
1060
+ elif row[0] == "batches_sent_total":
1061
+ batches_sent_total = row[1]
1062
+ elif row[0] == "scans_sent_total":
1063
+ scans_sent_total = row[1]
1064
+ except sqlite3.Error:
1065
+ pass # Stats table might not exist in older DBs
1066
+
1067
+ stats = {
1068
+ "critical_count": critical_count,
1069
+ "standard_count": standard_count,
1070
+ "dlq_count": dlq_count,
1071
+ "total_queued": critical_count + standard_count,
1072
+ "oldest_critical": oldest_critical,
1073
+ "oldest_standard": oldest_standard,
1074
+ "retry_pending": retry_pending,
1075
+ "scans_sent_total": scans_sent_total,
1076
+ "events_sent_total": events_sent_total,
1077
+ "batches_sent_total": batches_sent_total,
1078
+ }
1079
+
1080
+ except sqlite3.Error as e:
1081
+ logger.error(f"Failed to get stats: {e}")
1082
+ stats = {
1083
+ "critical_count": 0,
1084
+ "standard_count": 0,
1085
+ "dlq_count": 0,
1086
+ "total_queued": 0,
1087
+ "oldest_critical": None,
1088
+ "oldest_standard": None,
1089
+ "retry_pending": 0,
1090
+ "scans_sent_total": 0,
1091
+ "events_sent_total": 0,
1092
+ "batches_sent_total": 0,
1093
+ "error": str(e),
1094
+ }
1095
+
1096
+ return stats
1097
+
1098
+ def close(self) -> None:
1099
+ """
1100
+ Close the queue and release resources.
1101
+
1102
+ After closing, all operations will be no-ops and return empty/default values.
1103
+
1104
+ Example:
1105
+ >>> queue = DualQueue()
1106
+ >>> # ... use queue ...
1107
+ >>> queue.close()
1108
+ """
1109
+ self._closed = True
1110
+ logger.debug("DualQueue closed")
1111
+
1112
+ def __enter__(self) -> DualQueue:
1113
+ """Context manager entry."""
1114
+ return self
1115
+
1116
+ def __exit__(
1117
+ self,
1118
+ exc_type: type[BaseException] | None,
1119
+ exc_val: BaseException | None,
1120
+ exc_tb: Any,
1121
+ ) -> None:
1122
+ """Context manager exit."""
1123
+ self.close()