attune-ai 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
@@ -0,0 +1,929 @@
1
+ """Audit Logging Framework for Empathy Framework
2
+
3
+ Comprehensive audit logging for SOC2, HIPAA, and GDPR compliance.
4
+ Implements tamper-evident, append-only logging with structured JSON format.
5
+
6
+ Key Features:
7
+ - JSON Lines format (one event per line)
8
+ - ISO-8601 timestamps (UTC)
9
+ - Unique event IDs (UUID)
10
+ - Tamper-evident (append-only)
11
+ - Query/search capability
12
+ - Log rotation support
13
+
14
+ Reference:
15
+ - SECURE_MEMORY_ARCHITECTURE.md: Audit Trail Implementation
16
+ - SOC2 CC7.2: System Monitoring
17
+ - HIPAA 164.312(b): Audit Controls
18
+ - GDPR Article 30: Records of Processing
19
+
20
+ Copyright 2025 Smart AI Memory, LLC
21
+ Licensed under Fair Source 0.9
22
+ """
23
+
24
+ import json
25
+ import logging
26
+ import os
27
+ import uuid
28
+ from dataclasses import asdict, dataclass, field
29
+ from datetime import datetime, timedelta
30
+ from pathlib import Path
31
+ from typing import Any
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ @dataclass
37
+ class AuditEvent:
38
+ """Represents a single audit event.
39
+
40
+ All audit events share these core fields for compliance tracking.
41
+ """
42
+
43
+ # Core identification
44
+ event_id: str = field(default_factory=lambda: f"evt_{uuid.uuid4().hex[:12]}")
45
+ timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
46
+ version: str = "1.0"
47
+
48
+ # Event classification
49
+ event_type: str = "" # llm_request, store_pattern, retrieve_pattern, security_violation
50
+ user_id: str = ""
51
+ session_id: str = ""
52
+
53
+ # Status tracking
54
+ status: str = "success" # success, failed, blocked
55
+ error: str = ""
56
+
57
+ # Custom fields (populated by specific event types)
58
+ data: dict[str, Any] = field(default_factory=dict)
59
+
60
+ def to_dict(self) -> dict[str, Any]:
61
+ """Convert to dictionary for JSON serialization"""
62
+ result = asdict(self)
63
+ # Flatten data dict into top level for easier querying
64
+ data = result.pop("data", {})
65
+ result.update(data)
66
+ return result
67
+
68
+
69
+ @dataclass
70
+ class SecurityViolation:
71
+ """Represents a security policy violation.
72
+
73
+ Used for tracking and alerting on security issues.
74
+ """
75
+
76
+ violation_type: str # secrets_detected, pii_in_storage, classification_error, etc.
77
+ severity: str # LOW, MEDIUM, HIGH, CRITICAL
78
+ details: dict[str, Any] = field(default_factory=dict)
79
+ user_notified: bool = False
80
+ manager_notified: bool = False
81
+ security_team_notified: bool = False
82
+
83
+
84
+ class AuditLogger:
85
+ """Comprehensive audit logging for Empathy Framework.
86
+
87
+ Implements SOC2, HIPAA, and GDPR compliant audit trails with:
88
+ - Tamper-evident append-only logging
89
+ - Structured JSON Lines format
90
+ - Comprehensive event tracking
91
+ - Query and search capabilities
92
+ - Log rotation support
93
+
94
+ Example:
95
+ >>> logger = AuditLogger(log_dir="/var/log/empathy")
96
+ >>> logger.log_llm_request(
97
+ ... user_id="user@company.com",
98
+ ... empathy_level=3,
99
+ ... provider="anthropic",
100
+ ... model="claude-sonnet-4",
101
+ ... memory_sources=["enterprise", "user", "project"],
102
+ ... pii_count=0,
103
+ ... secrets_count=0
104
+ ... )
105
+
106
+ Log Format:
107
+ Each line is a complete JSON object representing one event.
108
+ Format: JSON Lines (.jsonl) - one event per line, append-only.
109
+
110
+ Compliance:
111
+ - SOC2 CC7.2: System Monitoring and Logging
112
+ - HIPAA 164.312(b): Audit Controls
113
+ - GDPR Article 30: Records of Processing Activities
114
+
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ log_dir: str = "/var/log/empathy",
120
+ log_filename: str = "audit.jsonl",
121
+ max_file_size_mb: int = 100,
122
+ retention_days: int = 365,
123
+ enable_rotation: bool = True,
124
+ enable_console_logging: bool = False,
125
+ ):
126
+ """Initialize the audit logger.
127
+
128
+ Args:
129
+ log_dir: Directory for audit logs
130
+ log_filename: Name of the audit log file
131
+ max_file_size_mb: Maximum file size before rotation (if enabled)
132
+ retention_days: Number of days to retain audit logs
133
+ enable_rotation: Whether to enable automatic log rotation
134
+ enable_console_logging: Whether to also log to console (for development)
135
+
136
+ """
137
+ self.log_dir = Path(log_dir)
138
+ self.log_filename = log_filename
139
+ self.log_path = self.log_dir / log_filename
140
+ self.max_file_size_bytes = max_file_size_mb * 1024 * 1024
141
+ self.retention_days = retention_days
142
+ self.enable_rotation = enable_rotation
143
+ self.enable_console_logging = enable_console_logging
144
+
145
+ # Track security violations for alerting
146
+ self._violation_counts: dict[str, int] = {}
147
+
148
+ # Initialize log directory
149
+ self._initialize_log_directory()
150
+
151
+ def _initialize_log_directory(self):
152
+ """Create log directory if it doesn't exist"""
153
+ try:
154
+ self.log_dir.mkdir(parents=True, exist_ok=True)
155
+ # Set restrictive permissions (owner read/write only)
156
+ os.chmod(self.log_dir, 0o700)
157
+ logger.info(f"Audit log directory initialized: {self.log_dir}")
158
+ except Exception as e:
159
+ logger.error(f"Failed to initialize audit log directory: {e}")
160
+ # Fallback to local directory
161
+ self.log_dir = Path("./logs")
162
+ self.log_dir.mkdir(parents=True, exist_ok=True)
163
+ self.log_path = self.log_dir / self.log_filename
164
+ logger.warning(f"Using fallback log directory: {self.log_dir}")
165
+
166
+ def _write_event(self, event: AuditEvent):
167
+ """Write an audit event to the log file.
168
+
169
+ Uses append-only mode for tamper-evidence.
170
+ """
171
+ try:
172
+ # Check if rotation is needed
173
+ if self.enable_rotation and self.log_path.exists():
174
+ if self.log_path.stat().st_size > self.max_file_size_bytes:
175
+ self._rotate_log()
176
+
177
+ # Write event as single line JSON
178
+ with open(self.log_path, "a", encoding="utf-8") as f:
179
+ json.dump(event.to_dict(), f, ensure_ascii=False)
180
+ f.write("\n")
181
+
182
+ # Optional console logging for development
183
+ if self.enable_console_logging:
184
+ logger.debug(f"Audit event: {event.event_type} - {event.status}")
185
+
186
+ except Exception as e:
187
+ logger.error(f"Failed to write audit event: {e}")
188
+ # Critical: audit logging failure should be visible
189
+ if self.enable_console_logging:
190
+ print(f"AUDIT LOG FAILURE: {e}", flush=True)
191
+
192
+ def _rotate_log(self):
193
+ """Rotate the audit log file.
194
+
195
+ Renames current log with timestamp and creates new file.
196
+ """
197
+ try:
198
+ timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
199
+ rotated_name = f"{self.log_filename}.{timestamp}"
200
+ rotated_path = self.log_dir / rotated_name
201
+
202
+ self.log_path.rename(rotated_path)
203
+ logger.info(f"Audit log rotated: {rotated_path}")
204
+
205
+ # Clean up old logs beyond retention period
206
+ self._cleanup_old_logs()
207
+
208
+ except Exception as e:
209
+ logger.error(f"Failed to rotate audit log: {e}")
210
+
211
+ def _cleanup_old_logs(self):
212
+ """Remove audit logs older than retention period"""
213
+ try:
214
+ cutoff_date = datetime.utcnow() - timedelta(days=self.retention_days)
215
+
216
+ for log_file in self.log_dir.glob(f"{self.log_filename}.*"):
217
+ # Extract timestamp from filename
218
+ try:
219
+ timestamp_str = log_file.suffix[1:] # Remove leading dot
220
+ file_date = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
221
+
222
+ if file_date < cutoff_date:
223
+ log_file.unlink()
224
+ logger.info(f"Removed old audit log: {log_file}")
225
+ except (ValueError, IndexError):
226
+ # Skip files that don't match expected format
227
+ continue
228
+
229
+ except Exception as e:
230
+ logger.error(f"Failed to cleanup old audit logs: {e}")
231
+
232
+ def log_llm_request(
233
+ self,
234
+ user_id: str,
235
+ empathy_level: int,
236
+ provider: str,
237
+ model: str,
238
+ memory_sources: list[str],
239
+ pii_count: int = 0,
240
+ secrets_count: int = 0,
241
+ request_size_bytes: int = 0,
242
+ response_size_bytes: int = 0,
243
+ duration_ms: int = 0,
244
+ memdocs_patterns_used: list[str] | None = None,
245
+ sanitization_applied: bool = True,
246
+ classification_verified: bool = True,
247
+ session_id: str = "",
248
+ ip_address: str = "",
249
+ temperature: float = 0.7,
250
+ status: str = "success",
251
+ error: str = "",
252
+ **kwargs,
253
+ ):
254
+ """Log an LLM API request.
255
+
256
+ Tracks all LLM interactions for compliance and monitoring.
257
+
258
+ Args:
259
+ user_id: User or service account making the request
260
+ empathy_level: Empathy level (1-5) used for this request
261
+ provider: LLM provider (anthropic, openai, local)
262
+ model: Specific model used
263
+ memory_sources: Which memory sources were loaded (enterprise, user, project)
264
+ pii_count: Number of PII items detected (not the items themselves)
265
+ secrets_count: Number of secrets detected
266
+ request_size_bytes: Size of the request payload
267
+ response_size_bytes: Size of the response payload
268
+ duration_ms: Request duration in milliseconds
269
+ memdocs_patterns_used: List of MemDocs pattern IDs used
270
+ sanitization_applied: Whether PII sanitization was applied
271
+ classification_verified: Whether data classification was verified
272
+ session_id: Session identifier
273
+ ip_address: Anonymized IP address (e.g., first 3 octets only)
274
+ temperature: LLM temperature setting
275
+ status: success, failed, or blocked
276
+ error: Error message if failed
277
+ **kwargs: Additional custom fields
278
+
279
+ Example:
280
+ >>> logger.log_llm_request(
281
+ ... user_id="user@company.com",
282
+ ... empathy_level=3,
283
+ ... provider="anthropic",
284
+ ... model="claude-sonnet-4",
285
+ ... memory_sources=["enterprise", "user"],
286
+ ... pii_count=0,
287
+ ... secrets_count=0
288
+ ... )
289
+
290
+ """
291
+ event = AuditEvent(
292
+ event_type="llm_request",
293
+ user_id=user_id,
294
+ session_id=session_id,
295
+ status=status,
296
+ error=error,
297
+ data={
298
+ "llm": {
299
+ "provider": provider,
300
+ "model": model,
301
+ "empathy_level": empathy_level,
302
+ "temperature": temperature,
303
+ },
304
+ "memory": {
305
+ "sources": memory_sources,
306
+ "total_sources": len(memory_sources),
307
+ "security_policies_applied": "enterprise" in memory_sources,
308
+ },
309
+ "memdocs": {
310
+ "patterns_used": memdocs_patterns_used or [],
311
+ "pattern_count": len(memdocs_patterns_used or []),
312
+ },
313
+ "security": {
314
+ "pii_detected": pii_count,
315
+ "secrets_detected": secrets_count,
316
+ "sanitization_applied": sanitization_applied,
317
+ "classification_verified": classification_verified,
318
+ },
319
+ "request": {
320
+ "size_bytes": request_size_bytes,
321
+ "duration_ms": duration_ms,
322
+ "ip_address": ip_address,
323
+ },
324
+ "response": {
325
+ "size_bytes": response_size_bytes,
326
+ },
327
+ "compliance": {
328
+ "gdpr_compliant": pii_count == 0 or sanitization_applied,
329
+ "hipaa_compliant": secrets_count == 0 and sanitization_applied,
330
+ "soc2_compliant": True,
331
+ },
332
+ **kwargs,
333
+ },
334
+ )
335
+
336
+ self._write_event(event)
337
+
338
+ # Check for security violations
339
+ if secrets_count > 0:
340
+ self._handle_security_violation(
341
+ user_id=user_id,
342
+ violation_type="secrets_detected",
343
+ severity="HIGH",
344
+ details={"secrets_count": secrets_count, "event_type": "llm_request"},
345
+ )
346
+
347
+ def log_pattern_store(
348
+ self,
349
+ user_id: str,
350
+ pattern_id: str,
351
+ pattern_type: str,
352
+ classification: str,
353
+ pii_scrubbed: int = 0,
354
+ secrets_detected: int = 0,
355
+ retention_days: int = 180,
356
+ encrypted: bool = False,
357
+ session_id: str = "",
358
+ status: str = "success",
359
+ error: str = "",
360
+ **kwargs,
361
+ ):
362
+ """Log MemDocs pattern storage.
363
+
364
+ Tracks pattern creation for compliance and data governance.
365
+
366
+ Args:
367
+ user_id: User storing the pattern
368
+ pattern_id: Unique identifier for the pattern
369
+ pattern_type: Type of pattern (code, architecture, workflow, etc.)
370
+ classification: PUBLIC, INTERNAL, or SENSITIVE
371
+ pii_scrubbed: Number of PII items scrubbed before storage
372
+ secrets_detected: Number of secrets found (should be 0 for storage)
373
+ retention_days: Retention period in days
374
+ encrypted: Whether pattern is encrypted at rest
375
+ session_id: Session identifier
376
+ status: success, failed, or blocked
377
+ error: Error message if failed
378
+ **kwargs: Additional custom fields
379
+
380
+ Example:
381
+ >>> logger.log_pattern_store(
382
+ ... user_id="user@company.com",
383
+ ... pattern_id="pattern_abc123",
384
+ ... pattern_type="architecture",
385
+ ... classification="INTERNAL",
386
+ ... pii_scrubbed=2,
387
+ ... retention_days=180
388
+ ... )
389
+
390
+ """
391
+ event = AuditEvent(
392
+ event_type="store_pattern",
393
+ user_id=user_id,
394
+ session_id=session_id,
395
+ status=status,
396
+ error=error,
397
+ data={
398
+ "pattern": {
399
+ "pattern_id": pattern_id,
400
+ "pattern_type": pattern_type,
401
+ "classification": classification,
402
+ "encrypted": encrypted,
403
+ "retention_days": retention_days,
404
+ },
405
+ "security": {
406
+ "pii_scrubbed": pii_scrubbed,
407
+ "secrets_detected": secrets_detected,
408
+ "sanitization_applied": pii_scrubbed > 0,
409
+ },
410
+ "compliance": {
411
+ "gdpr_compliant": secrets_detected == 0,
412
+ "hipaa_compliant": (classification == "SENSITIVE" and encrypted)
413
+ or classification != "SENSITIVE",
414
+ "soc2_compliant": secrets_detected == 0
415
+ and classification in ["PUBLIC", "INTERNAL", "SENSITIVE"],
416
+ "classification_verified": classification
417
+ in ["PUBLIC", "INTERNAL", "SENSITIVE"],
418
+ },
419
+ **kwargs,
420
+ },
421
+ )
422
+
423
+ self._write_event(event)
424
+
425
+ # Check for security violations
426
+ if secrets_detected > 0:
427
+ self._handle_security_violation(
428
+ user_id=user_id,
429
+ violation_type="secrets_in_storage",
430
+ severity="CRITICAL",
431
+ details={
432
+ "secrets_detected": secrets_detected,
433
+ "pattern_id": pattern_id,
434
+ "event_type": "store_pattern",
435
+ },
436
+ )
437
+
438
+ if classification == "SENSITIVE" and not encrypted:
439
+ self._handle_security_violation(
440
+ user_id=user_id,
441
+ violation_type="sensitive_not_encrypted",
442
+ severity="HIGH",
443
+ details={
444
+ "pattern_id": pattern_id,
445
+ "classification": classification,
446
+ "event_type": "store_pattern",
447
+ },
448
+ )
449
+
450
+ def log_pattern_retrieve(
451
+ self,
452
+ user_id: str,
453
+ pattern_id: str,
454
+ classification: str,
455
+ access_granted: bool = True,
456
+ permission_level: str = "",
457
+ session_id: str = "",
458
+ status: str = "success",
459
+ error: str = "",
460
+ **kwargs,
461
+ ):
462
+ """Log MemDocs pattern retrieval.
463
+
464
+ Tracks pattern access for compliance and security monitoring.
465
+
466
+ Args:
467
+ user_id: User retrieving the pattern
468
+ pattern_id: Unique identifier for the pattern
469
+ classification: PUBLIC, INTERNAL, or SENSITIVE
470
+ access_granted: Whether access was granted
471
+ permission_level: Permission level used for access decision
472
+ session_id: Session identifier
473
+ status: success, failed, or blocked
474
+ error: Error message if failed
475
+ **kwargs: Additional custom fields
476
+
477
+ Example:
478
+ >>> logger.log_pattern_retrieve(
479
+ ... user_id="user@company.com",
480
+ ... pattern_id="pattern_abc123",
481
+ ... classification="SENSITIVE",
482
+ ... access_granted=True,
483
+ ... permission_level="explicit"
484
+ ... )
485
+
486
+ """
487
+ event = AuditEvent(
488
+ event_type="retrieve_pattern",
489
+ user_id=user_id,
490
+ session_id=session_id,
491
+ status="success" if access_granted else "blocked",
492
+ error=error,
493
+ data={
494
+ "pattern": {
495
+ "pattern_id": pattern_id,
496
+ "classification": classification,
497
+ },
498
+ "access": {
499
+ "granted": access_granted,
500
+ "permission_level": permission_level,
501
+ "audit_required": classification == "SENSITIVE",
502
+ },
503
+ "compliance": {
504
+ "access_logged": True,
505
+ "hipaa_compliant": classification == "SENSITIVE",
506
+ },
507
+ **kwargs,
508
+ },
509
+ )
510
+
511
+ self._write_event(event)
512
+
513
+ # Log unauthorized access attempts
514
+ if not access_granted:
515
+ self._handle_security_violation(
516
+ user_id=user_id,
517
+ violation_type="unauthorized_access",
518
+ severity="MEDIUM" if classification == "INTERNAL" else "HIGH",
519
+ details={
520
+ "pattern_id": pattern_id,
521
+ "classification": classification,
522
+ "event_type": "retrieve_pattern",
523
+ },
524
+ )
525
+
526
+ def log_security_violation(
527
+ self,
528
+ user_id: str,
529
+ violation_type: str,
530
+ severity: str,
531
+ details: dict[str, Any],
532
+ session_id: str = "",
533
+ blocked: bool = True,
534
+ **kwargs,
535
+ ):
536
+ """Log a security policy violation.
537
+
538
+ Tracks security incidents for monitoring and response.
539
+
540
+ Args:
541
+ user_id: User who triggered the violation
542
+ violation_type: Type of violation (secrets_detected, pii_in_storage, etc.)
543
+ severity: LOW, MEDIUM, HIGH, or CRITICAL
544
+ details: Additional details about the violation
545
+ session_id: Session identifier
546
+ blocked: Whether the action was blocked
547
+ **kwargs: Additional custom fields
548
+
549
+ Example:
550
+ >>> logger.log_security_violation(
551
+ ... user_id="user@company.com",
552
+ ... violation_type="secrets_detected",
553
+ ... severity="HIGH",
554
+ ... details={"secret_type": "api_key", "action": "llm_request"},
555
+ ... blocked=True
556
+ ... )
557
+
558
+ """
559
+ violation = SecurityViolation(
560
+ violation_type=violation_type,
561
+ severity=severity,
562
+ details=details,
563
+ )
564
+
565
+ event = AuditEvent(
566
+ event_type="security_violation",
567
+ user_id=user_id,
568
+ session_id=session_id,
569
+ status="blocked" if blocked else "logged",
570
+ data={
571
+ "violation": {
572
+ "type": violation_type,
573
+ "severity": severity,
574
+ "details": details,
575
+ "blocked": blocked,
576
+ },
577
+ "response": {
578
+ "user_notified": violation.user_notified,
579
+ "manager_notified": violation.manager_notified,
580
+ "security_team_notified": violation.security_team_notified,
581
+ },
582
+ "compliance": {
583
+ "gdpr_compliant": blocked,
584
+ "hipaa_compliant": blocked,
585
+ "soc2_compliant": blocked,
586
+ },
587
+ **kwargs,
588
+ },
589
+ )
590
+
591
+ self._write_event(event)
592
+
593
+ def _handle_security_violation(
594
+ self,
595
+ user_id: str,
596
+ violation_type: str,
597
+ severity: str,
598
+ details: dict[str, Any],
599
+ ):
600
+ """Internal handler for security violations.
601
+
602
+ Tracks violation counts and triggers alerts.
603
+ """
604
+ # Track violations per user
605
+ key = f"{user_id}:{violation_type}"
606
+ self._violation_counts[key] = self._violation_counts.get(key, 0) + 1
607
+
608
+ # Log the violation
609
+ self.log_security_violation(
610
+ user_id=user_id,
611
+ violation_type=violation_type,
612
+ severity=severity,
613
+ details=details,
614
+ )
615
+
616
+ # Alert logic
617
+ count = self._violation_counts[key]
618
+ if severity == "CRITICAL" or count >= 3:
619
+ logger.warning(
620
+ f"Security violation threshold reached: {user_id} - "
621
+ f"{violation_type} (count: {count}, severity: {severity})",
622
+ )
623
+
624
+ def query(
625
+ self,
626
+ event_type: str | None = None,
627
+ user_id: str | None = None,
628
+ status: str | None = None,
629
+ start_date: datetime | None = None,
630
+ end_date: datetime | None = None,
631
+ limit: int = 1000,
632
+ **filters,
633
+ ) -> list[dict]:
634
+ """Query audit logs with filters.
635
+
636
+ Provides search and analysis capabilities for audit data.
637
+
638
+ Args:
639
+ event_type: Filter by event type (llm_request, store_pattern, etc.)
640
+ user_id: Filter by user ID
641
+ status: Filter by status (success, failed, blocked)
642
+ start_date: Filter events after this date
643
+ end_date: Filter events before this date
644
+ limit: Maximum number of events to return
645
+ **filters: Additional key-value filters (supports nested keys with __)
646
+
647
+ Returns:
648
+ List of matching audit events as dictionaries
649
+
650
+ Example:
651
+ >>> # Find all failed LLM requests
652
+ >>> events = logger.query(event_type="llm_request", status="failed")
653
+ >>>
654
+ >>> # Find security violations in last 24 hours
655
+ >>> from datetime import datetime, timedelta
656
+ >>> events = logger.query(
657
+ ... event_type="security_violation",
658
+ ... start_date=datetime.utcnow() - timedelta(days=1)
659
+ ... )
660
+ >>>
661
+ >>> # Find patterns with high PII counts (nested filter)
662
+ >>> events = logger.query(security__pii_detected__gt=5)
663
+
664
+ """
665
+ results: list[dict[str, Any]] = []
666
+
667
+ try:
668
+ if not self.log_path.exists():
669
+ return results
670
+
671
+ with open(self.log_path, encoding="utf-8") as f:
672
+ for line in f:
673
+ if len(results) >= limit:
674
+ break
675
+
676
+ try:
677
+ event = json.loads(line.strip())
678
+
679
+ # Apply filters
680
+ if event_type and event.get("event_type") != event_type:
681
+ continue
682
+ if user_id and event.get("user_id") != user_id:
683
+ continue
684
+ if status and event.get("status") != status:
685
+ continue
686
+
687
+ # Date range filtering
688
+ if start_date or end_date:
689
+ event_time = datetime.fromisoformat(
690
+ event.get("timestamp", "").rstrip("Z"),
691
+ )
692
+ if start_date and event_time < start_date:
693
+ continue
694
+ if end_date and event_time > end_date:
695
+ continue
696
+
697
+ # Custom filters (supports nested keys with __)
698
+ if filters and not self._apply_custom_filters(event, filters):
699
+ continue
700
+
701
+ results.append(event)
702
+
703
+ except json.JSONDecodeError:
704
+ logger.warning("Skipping malformed audit log line")
705
+ continue
706
+
707
+ except Exception as e:
708
+ logger.error(f"Failed to query audit logs: {e}")
709
+
710
+ return results
711
+
712
+ def _apply_custom_filters(self, event: dict, filters: dict) -> bool:
713
+ """Apply custom filters to an event.
714
+
715
+ Supports nested key access with __ separator and comparison operators.
716
+ """
717
+ for key, value in filters.items():
718
+ # Handle comparison operators (e.g., security__pii_detected__gt=5)
719
+ parts = key.split("__")
720
+ operator = None
721
+
722
+ if len(parts) > 1 and parts[-1] in ["gt", "gte", "lt", "lte", "ne"]:
723
+ operator = parts[-1]
724
+ parts = parts[:-1]
725
+
726
+ # Navigate nested dictionary
727
+ current = event
728
+ for part in parts:
729
+ if isinstance(current, dict) and part in current:
730
+ current = current[part]
731
+ else:
732
+ return False
733
+
734
+ # Apply comparison
735
+ if (
736
+ operator == "gt" and not (isinstance(current, int | float) and current > value)
737
+ ) or (
738
+ operator == "gte" and not (isinstance(current, int | float) and current >= value)
739
+ ):
740
+ return False
741
+ if (
742
+ (operator == "lt" and not (isinstance(current, int | float) and current < value))
743
+ or (
744
+ operator == "lte"
745
+ and not (isinstance(current, int | float) and current <= value)
746
+ )
747
+ or (operator == "ne" and current == value)
748
+ or (operator is None and current != value)
749
+ ):
750
+ return False
751
+
752
+ return True
753
+
754
+ def get_violation_summary(self, user_id: str | None = None) -> dict[str, Any]:
755
+ """Get summary of security violations.
756
+
757
+ Args:
758
+ user_id: Optional user ID to filter by
759
+
760
+ Returns:
761
+ Dictionary with violation statistics
762
+
763
+ Example:
764
+ >>> summary = logger.get_violation_summary(user_id="user@company.com")
765
+ >>> print(f"Total violations: {summary['total_violations']}")
766
+
767
+ """
768
+ violations = self.query(event_type="security_violation", user_id=user_id)
769
+
770
+ by_type: dict[str, int] = {}
771
+ by_severity: dict[str, int] = {}
772
+ by_user: dict[str, int] = {}
773
+
774
+ for violation in violations:
775
+ viol_data = violation.get("violation", {})
776
+ vtype = (
777
+ str(viol_data.get("type", "unknown")) if isinstance(viol_data, dict) else "unknown"
778
+ )
779
+ severity = (
780
+ str(viol_data.get("severity", "unknown"))
781
+ if isinstance(viol_data, dict)
782
+ else "unknown"
783
+ )
784
+ vid = str(violation.get("user_id", "unknown"))
785
+
786
+ by_type[vtype] = by_type.get(vtype, 0) + 1
787
+ by_severity[severity] = by_severity.get(severity, 0) + 1
788
+ by_user[vid] = by_user.get(vid, 0) + 1
789
+
790
+ return {
791
+ "total_violations": len(violations),
792
+ "by_type": by_type,
793
+ "by_severity": by_severity,
794
+ "by_user": by_user,
795
+ }
796
+
797
+ def get_compliance_report(
798
+ self,
799
+ start_date: datetime | None = None,
800
+ end_date: datetime | None = None,
801
+ ) -> dict[str, Any]:
802
+ """Generate compliance report for audit period.
803
+
804
+ Provides statistics for compliance audits (SOC2, HIPAA, GDPR).
805
+
806
+ Args:
807
+ start_date: Start of audit period
808
+ end_date: End of audit period
809
+
810
+ Returns:
811
+ Dictionary with compliance statistics
812
+
813
+ Example:
814
+ >>> from datetime import datetime, timedelta
815
+ >>> report = logger.get_compliance_report(
816
+ ... start_date=datetime.utcnow() - timedelta(days=30)
817
+ ... )
818
+ >>> print(f"Total LLM requests: {report['llm_requests']['total']}")
819
+
820
+ """
821
+ # Query all events in period
822
+ all_events = self.query(start_date=start_date, end_date=end_date, limit=100000)
823
+
824
+ report: dict[str, Any] = {
825
+ "period": {
826
+ "start": start_date.isoformat() if start_date else "all_time",
827
+ "end": end_date.isoformat() if end_date else "now",
828
+ },
829
+ "llm_requests": {
830
+ "total": 0,
831
+ "with_pii_detected": 0,
832
+ "with_secrets_detected": 0,
833
+ "sanitization_applied": 0,
834
+ },
835
+ "pattern_storage": {
836
+ "total": 0,
837
+ "by_classification": {"PUBLIC": 0, "INTERNAL": 0, "SENSITIVE": 0},
838
+ "with_pii_scrubbed": 0,
839
+ "encrypted": 0,
840
+ },
841
+ "pattern_retrieval": {
842
+ "total": 0,
843
+ "by_classification": {"PUBLIC": 0, "INTERNAL": 0, "SENSITIVE": 0},
844
+ "access_denied": 0,
845
+ },
846
+ "security_violations": {"total": 0, "by_severity": {}, "by_type": {}},
847
+ "compliance_metrics": {
848
+ "gdpr_compliant_rate": 0.0,
849
+ "hipaa_compliant_rate": 0.0,
850
+ "soc2_compliant_rate": 0.0,
851
+ },
852
+ }
853
+
854
+ total_compliance_checks = 0
855
+ gdpr_compliant = 0
856
+ hipaa_compliant = 0
857
+ soc2_compliant = 0
858
+
859
+ for event in all_events:
860
+ event_type = event.get("event_type")
861
+
862
+ if event_type == "llm_request":
863
+ report["llm_requests"]["total"] += 1
864
+ security = event.get("security", {})
865
+ if security.get("pii_detected", 0) > 0:
866
+ report["llm_requests"]["with_pii_detected"] += 1
867
+ if security.get("secrets_detected", 0) > 0:
868
+ report["llm_requests"]["with_secrets_detected"] += 1
869
+ if security.get("sanitization_applied"):
870
+ report["llm_requests"]["sanitization_applied"] += 1
871
+
872
+ elif event_type == "store_pattern":
873
+ report["pattern_storage"]["total"] += 1
874
+ pattern = event.get("pattern", {})
875
+ classification = pattern.get("classification", "INTERNAL")
876
+ report["pattern_storage"]["by_classification"][classification] = (
877
+ report["pattern_storage"]["by_classification"].get(classification, 0) + 1
878
+ )
879
+ if event.get("security", {}).get("pii_scrubbed", 0) > 0:
880
+ report["pattern_storage"]["with_pii_scrubbed"] += 1
881
+ if pattern.get("encrypted"):
882
+ report["pattern_storage"]["encrypted"] += 1
883
+
884
+ elif event_type == "retrieve_pattern":
885
+ report["pattern_retrieval"]["total"] += 1
886
+ pattern = event.get("pattern", {})
887
+ classification = pattern.get("classification", "INTERNAL")
888
+ report["pattern_retrieval"]["by_classification"][classification] = (
889
+ report["pattern_retrieval"]["by_classification"].get(classification, 0) + 1
890
+ )
891
+ if not event.get("access", {}).get("granted", True):
892
+ report["pattern_retrieval"]["access_denied"] += 1
893
+
894
+ elif event_type == "security_violation":
895
+ report["security_violations"]["total"] += 1
896
+ violation = event.get("violation", {})
897
+ vtype = violation.get("type", "unknown")
898
+ severity = violation.get("severity", "unknown")
899
+ report["security_violations"]["by_type"][vtype] = (
900
+ report["security_violations"]["by_type"].get(vtype, 0) + 1
901
+ )
902
+ report["security_violations"]["by_severity"][severity] = (
903
+ report["security_violations"]["by_severity"].get(severity, 0) + 1
904
+ )
905
+
906
+ # Track compliance rates
907
+ compliance = event.get("compliance", {})
908
+ if compliance:
909
+ total_compliance_checks += 1
910
+ if compliance.get("gdpr_compliant"):
911
+ gdpr_compliant += 1
912
+ if compliance.get("hipaa_compliant"):
913
+ hipaa_compliant += 1
914
+ if compliance.get("soc2_compliant"):
915
+ soc2_compliant += 1
916
+
917
+ # Calculate compliance rates
918
+ if total_compliance_checks > 0:
919
+ report["compliance_metrics"]["gdpr_compliant_rate"] = (
920
+ gdpr_compliant / total_compliance_checks
921
+ )
922
+ report["compliance_metrics"]["hipaa_compliant_rate"] = (
923
+ hipaa_compliant / total_compliance_checks
924
+ )
925
+ report["compliance_metrics"]["soc2_compliant_rate"] = (
926
+ soc2_compliant / total_compliance_checks
927
+ )
928
+
929
+ return report