empathy-framework 2.4.0__py3-none-any.whl → 3.8.2__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 (329) hide show
  1. coach_wizards/__init__.py +13 -12
  2. coach_wizards/accessibility_wizard.py +12 -12
  3. coach_wizards/api_wizard.py +12 -12
  4. coach_wizards/base_wizard.py +26 -20
  5. coach_wizards/cicd_wizard.py +15 -13
  6. coach_wizards/code_reviewer_README.md +60 -0
  7. coach_wizards/code_reviewer_wizard.py +180 -0
  8. coach_wizards/compliance_wizard.py +12 -12
  9. coach_wizards/database_wizard.py +12 -12
  10. coach_wizards/debugging_wizard.py +12 -12
  11. coach_wizards/documentation_wizard.py +12 -12
  12. coach_wizards/generate_wizards.py +1 -2
  13. coach_wizards/localization_wizard.py +101 -19
  14. coach_wizards/migration_wizard.py +12 -12
  15. coach_wizards/monitoring_wizard.py +12 -12
  16. coach_wizards/observability_wizard.py +12 -12
  17. coach_wizards/performance_wizard.py +12 -12
  18. coach_wizards/prompt_engineering_wizard.py +661 -0
  19. coach_wizards/refactoring_wizard.py +12 -12
  20. coach_wizards/scaling_wizard.py +12 -12
  21. coach_wizards/security_wizard.py +12 -12
  22. coach_wizards/testing_wizard.py +12 -12
  23. empathy_framework-3.8.2.dist-info/METADATA +1176 -0
  24. empathy_framework-3.8.2.dist-info/RECORD +333 -0
  25. empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
  26. {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
  27. empathy_healthcare_plugin/__init__.py +1 -2
  28. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  29. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  30. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  31. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  32. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  33. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  34. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  35. empathy_llm_toolkit/__init__.py +7 -7
  36. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  37. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  38. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  39. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  40. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  41. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  42. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  43. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  44. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  45. empathy_llm_toolkit/agent_factory/base.py +305 -0
  46. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  47. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  48. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  49. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  50. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  51. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  52. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  53. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  54. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  55. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  56. empathy_llm_toolkit/claude_memory.py +14 -15
  57. empathy_llm_toolkit/cli/__init__.py +8 -0
  58. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  59. empathy_llm_toolkit/code_health.py +186 -28
  60. empathy_llm_toolkit/config/__init__.py +29 -0
  61. empathy_llm_toolkit/config/unified.py +295 -0
  62. empathy_llm_toolkit/contextual_patterns.py +11 -12
  63. empathy_llm_toolkit/core.py +168 -53
  64. empathy_llm_toolkit/git_pattern_extractor.py +17 -13
  65. empathy_llm_toolkit/levels.py +6 -13
  66. empathy_llm_toolkit/pattern_confidence.py +14 -18
  67. empathy_llm_toolkit/pattern_resolver.py +10 -12
  68. empathy_llm_toolkit/pattern_summary.py +16 -14
  69. empathy_llm_toolkit/providers.py +194 -28
  70. empathy_llm_toolkit/routing/__init__.py +32 -0
  71. empathy_llm_toolkit/routing/model_router.py +362 -0
  72. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  73. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  74. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  75. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  76. empathy_llm_toolkit/security/README.md +262 -0
  77. empathy_llm_toolkit/security/__init__.py +62 -0
  78. empathy_llm_toolkit/security/audit_logger.py +929 -0
  79. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  80. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  81. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  82. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  83. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  84. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  85. empathy_llm_toolkit/session_status.py +20 -22
  86. empathy_llm_toolkit/state.py +28 -21
  87. empathy_llm_toolkit/wizards/__init__.py +38 -0
  88. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  89. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  90. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  91. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  92. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  93. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  94. empathy_os/__init__.py +125 -84
  95. empathy_os/adaptive/__init__.py +13 -0
  96. empathy_os/adaptive/task_complexity.py +127 -0
  97. empathy_os/{monitoring.py → agent_monitoring.py} +28 -28
  98. empathy_os/cache/__init__.py +117 -0
  99. empathy_os/cache/base.py +166 -0
  100. empathy_os/cache/dependency_manager.py +253 -0
  101. empathy_os/cache/hash_only.py +248 -0
  102. empathy_os/cache/hybrid.py +390 -0
  103. empathy_os/cache/storage.py +282 -0
  104. empathy_os/cli.py +1516 -70
  105. empathy_os/cli_unified.py +597 -0
  106. empathy_os/config/__init__.py +63 -0
  107. empathy_os/config/xml_config.py +239 -0
  108. empathy_os/config.py +95 -37
  109. empathy_os/coordination.py +72 -68
  110. empathy_os/core.py +94 -107
  111. empathy_os/cost_tracker.py +74 -55
  112. empathy_os/dashboard/__init__.py +15 -0
  113. empathy_os/dashboard/server.py +743 -0
  114. empathy_os/discovery.py +17 -14
  115. empathy_os/emergence.py +21 -22
  116. empathy_os/exceptions.py +18 -30
  117. empathy_os/feedback_loops.py +30 -33
  118. empathy_os/levels.py +32 -35
  119. empathy_os/leverage_points.py +31 -32
  120. empathy_os/logging_config.py +19 -16
  121. empathy_os/memory/__init__.py +195 -0
  122. empathy_os/memory/claude_memory.py +466 -0
  123. empathy_os/memory/config.py +224 -0
  124. empathy_os/memory/control_panel.py +1298 -0
  125. empathy_os/memory/edges.py +179 -0
  126. empathy_os/memory/graph.py +567 -0
  127. empathy_os/memory/long_term.py +1194 -0
  128. empathy_os/memory/nodes.py +179 -0
  129. empathy_os/memory/redis_bootstrap.py +540 -0
  130. empathy_os/memory/security/__init__.py +31 -0
  131. empathy_os/memory/security/audit_logger.py +930 -0
  132. empathy_os/memory/security/pii_scrubber.py +640 -0
  133. empathy_os/memory/security/secrets_detector.py +678 -0
  134. empathy_os/memory/short_term.py +2119 -0
  135. empathy_os/memory/storage/__init__.py +15 -0
  136. empathy_os/memory/summary_index.py +583 -0
  137. empathy_os/memory/unified.py +619 -0
  138. empathy_os/metrics/__init__.py +12 -0
  139. empathy_os/metrics/prompt_metrics.py +190 -0
  140. empathy_os/models/__init__.py +136 -0
  141. empathy_os/models/__main__.py +13 -0
  142. empathy_os/models/cli.py +655 -0
  143. empathy_os/models/empathy_executor.py +354 -0
  144. empathy_os/models/executor.py +252 -0
  145. empathy_os/models/fallback.py +671 -0
  146. empathy_os/models/provider_config.py +563 -0
  147. empathy_os/models/registry.py +382 -0
  148. empathy_os/models/tasks.py +302 -0
  149. empathy_os/models/telemetry.py +548 -0
  150. empathy_os/models/token_estimator.py +378 -0
  151. empathy_os/models/validation.py +274 -0
  152. empathy_os/monitoring/__init__.py +52 -0
  153. empathy_os/monitoring/alerts.py +23 -0
  154. empathy_os/monitoring/alerts_cli.py +268 -0
  155. empathy_os/monitoring/multi_backend.py +271 -0
  156. empathy_os/monitoring/otel_backend.py +363 -0
  157. empathy_os/optimization/__init__.py +19 -0
  158. empathy_os/optimization/context_optimizer.py +272 -0
  159. empathy_os/pattern_library.py +30 -29
  160. empathy_os/persistence.py +35 -37
  161. empathy_os/platform_utils.py +261 -0
  162. empathy_os/plugins/__init__.py +28 -0
  163. empathy_os/plugins/base.py +361 -0
  164. empathy_os/plugins/registry.py +268 -0
  165. empathy_os/project_index/__init__.py +30 -0
  166. empathy_os/project_index/cli.py +335 -0
  167. empathy_os/project_index/crew_integration.py +430 -0
  168. empathy_os/project_index/index.py +425 -0
  169. empathy_os/project_index/models.py +501 -0
  170. empathy_os/project_index/reports.py +473 -0
  171. empathy_os/project_index/scanner.py +538 -0
  172. empathy_os/prompts/__init__.py +61 -0
  173. empathy_os/prompts/config.py +77 -0
  174. empathy_os/prompts/context.py +177 -0
  175. empathy_os/prompts/parser.py +285 -0
  176. empathy_os/prompts/registry.py +313 -0
  177. empathy_os/prompts/templates.py +208 -0
  178. empathy_os/redis_config.py +144 -58
  179. empathy_os/redis_memory.py +79 -77
  180. empathy_os/resilience/__init__.py +56 -0
  181. empathy_os/resilience/circuit_breaker.py +256 -0
  182. empathy_os/resilience/fallback.py +179 -0
  183. empathy_os/resilience/health.py +300 -0
  184. empathy_os/resilience/retry.py +209 -0
  185. empathy_os/resilience/timeout.py +135 -0
  186. empathy_os/routing/__init__.py +43 -0
  187. empathy_os/routing/chain_executor.py +433 -0
  188. empathy_os/routing/classifier.py +217 -0
  189. empathy_os/routing/smart_router.py +234 -0
  190. empathy_os/routing/wizard_registry.py +307 -0
  191. empathy_os/templates.py +19 -14
  192. empathy_os/trust/__init__.py +28 -0
  193. empathy_os/trust/circuit_breaker.py +579 -0
  194. empathy_os/trust_building.py +67 -58
  195. empathy_os/validation/__init__.py +19 -0
  196. empathy_os/validation/xml_validator.py +281 -0
  197. empathy_os/wizard_factory_cli.py +170 -0
  198. empathy_os/{workflows.py → workflow_commands.py} +131 -37
  199. empathy_os/workflows/__init__.py +360 -0
  200. empathy_os/workflows/base.py +1660 -0
  201. empathy_os/workflows/bug_predict.py +962 -0
  202. empathy_os/workflows/code_review.py +960 -0
  203. empathy_os/workflows/code_review_adapters.py +310 -0
  204. empathy_os/workflows/code_review_pipeline.py +720 -0
  205. empathy_os/workflows/config.py +600 -0
  206. empathy_os/workflows/dependency_check.py +648 -0
  207. empathy_os/workflows/document_gen.py +1069 -0
  208. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  209. empathy_os/workflows/health_check.py +679 -0
  210. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  211. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  212. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  213. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  214. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  215. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  216. empathy_os/workflows/manage_documentation.py +804 -0
  217. empathy_os/workflows/new_sample_workflow1.py +146 -0
  218. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  219. empathy_os/workflows/perf_audit.py +687 -0
  220. empathy_os/workflows/pr_review.py +748 -0
  221. empathy_os/workflows/progress.py +445 -0
  222. empathy_os/workflows/progress_server.py +322 -0
  223. empathy_os/workflows/refactor_plan.py +693 -0
  224. empathy_os/workflows/release_prep.py +808 -0
  225. empathy_os/workflows/research_synthesis.py +404 -0
  226. empathy_os/workflows/secure_release.py +585 -0
  227. empathy_os/workflows/security_adapters.py +297 -0
  228. empathy_os/workflows/security_audit.py +1046 -0
  229. empathy_os/workflows/step_config.py +234 -0
  230. empathy_os/workflows/test5.py +125 -0
  231. empathy_os/workflows/test5_README.md +158 -0
  232. empathy_os/workflows/test_gen.py +1855 -0
  233. empathy_os/workflows/test_lifecycle.py +526 -0
  234. empathy_os/workflows/test_maintenance.py +626 -0
  235. empathy_os/workflows/test_maintenance_cli.py +590 -0
  236. empathy_os/workflows/test_maintenance_crew.py +821 -0
  237. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  238. empathy_software_plugin/__init__.py +1 -2
  239. empathy_software_plugin/cli/__init__.py +120 -0
  240. empathy_software_plugin/cli/inspect.py +362 -0
  241. empathy_software_plugin/cli.py +49 -27
  242. empathy_software_plugin/plugin.py +4 -8
  243. empathy_software_plugin/wizards/__init__.py +42 -0
  244. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  245. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  246. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  247. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  248. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  249. empathy_software_plugin/wizards/base_wizard.py +288 -0
  250. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  251. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  252. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  253. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  254. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  255. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  256. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  257. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  258. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  259. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  260. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  261. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  262. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  263. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  264. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  265. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  266. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  267. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  268. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  269. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  270. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  271. empathy_software_plugin/wizards/security/__init__.py +32 -0
  272. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  273. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  274. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  275. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  276. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  277. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  278. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  279. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  280. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  281. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  282. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  283. hot_reload/README.md +473 -0
  284. hot_reload/__init__.py +62 -0
  285. hot_reload/config.py +84 -0
  286. hot_reload/integration.py +228 -0
  287. hot_reload/reloader.py +298 -0
  288. hot_reload/watcher.py +179 -0
  289. hot_reload/websocket.py +176 -0
  290. scaffolding/README.md +589 -0
  291. scaffolding/__init__.py +35 -0
  292. scaffolding/__main__.py +14 -0
  293. scaffolding/cli.py +240 -0
  294. test_generator/__init__.py +38 -0
  295. test_generator/__main__.py +14 -0
  296. test_generator/cli.py +226 -0
  297. test_generator/generator.py +325 -0
  298. test_generator/risk_analyzer.py +216 -0
  299. workflow_patterns/__init__.py +33 -0
  300. workflow_patterns/behavior.py +249 -0
  301. workflow_patterns/core.py +76 -0
  302. workflow_patterns/output.py +99 -0
  303. workflow_patterns/registry.py +255 -0
  304. workflow_patterns/structural.py +288 -0
  305. workflow_scaffolding/__init__.py +11 -0
  306. workflow_scaffolding/__main__.py +12 -0
  307. workflow_scaffolding/cli.py +206 -0
  308. workflow_scaffolding/generator.py +265 -0
  309. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  310. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  311. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  312. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  313. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  314. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  315. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  316. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  317. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  318. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  319. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  320. agents/compliance_anticipation_agent.py +0 -1427
  321. agents/epic_integration_wizard.py +0 -541
  322. agents/trust_building_behaviors.py +0 -891
  323. empathy_framework-2.4.0.dist-info/METADATA +0 -485
  324. empathy_framework-2.4.0.dist-info/RECORD +0 -102
  325. empathy_framework-2.4.0.dist-info/entry_points.txt +0 -6
  326. empathy_llm_toolkit/htmlcov/status.json +0 -1
  327. empathy_llm_toolkit/security/htmlcov/status.json +0 -1
  328. {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
  329. {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1194 @@
1
+ """Secure MemDocs Integration for Enterprise Privacy
2
+
3
+ Combines PII scrubbing, secrets detection, and audit logging with MemDocs pattern storage.
4
+ Implements three-tier classification (PUBLIC/INTERNAL/SENSITIVE) with encryption support.
5
+
6
+ This module provides the complete security pipeline for storing and retrieving
7
+ patterns with full compliance for GDPR, HIPAA, and SOC2 requirements.
8
+
9
+ Key Features:
10
+ - Automatic PII scrubbing before storage
11
+ - Secrets detection with blocking
12
+ - Three-tier classification system
13
+ - AES-256-GCM encryption for SENSITIVE patterns
14
+ - Comprehensive audit logging
15
+ - Access control enforcement
16
+ - Retention policy management
17
+
18
+ Architecture:
19
+ User Input → [PII Scrubbing + Secrets Detection (PARALLEL)] → Classification
20
+ → Encryption (if SENSITIVE) → MemDocs Storage → Audit Logging
21
+
22
+ Reference:
23
+ - SECURE_MEMORY_ARCHITECTURE.md: MemDocs Integration Patterns
24
+ - ENTERPRISE_PRIVACY_INTEGRATION.md: Phase 2 Implementation
25
+
26
+ Copyright 2025 Smart AI Memory, LLC
27
+ Licensed under Fair Source 0.9
28
+ """
29
+
30
+ import base64
31
+ import binascii
32
+ import concurrent.futures
33
+ import hashlib
34
+ import json
35
+ import os
36
+ from dataclasses import dataclass, field
37
+ from datetime import datetime, timedelta
38
+ from enum import Enum
39
+ from pathlib import Path
40
+ from typing import Any
41
+
42
+ import structlog
43
+
44
+ from .security.audit_logger import AuditEvent, AuditLogger
45
+ from .security.pii_scrubber import PIIScrubber
46
+ from .security.secrets_detector import SecretsDetector
47
+
48
+ logger = structlog.get_logger(__name__)
49
+
50
+ # Check for cryptography library
51
+ try:
52
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
53
+
54
+ HAS_ENCRYPTION = True
55
+ except ImportError:
56
+ HAS_ENCRYPTION = False
57
+ logger.warning("cryptography library not available - encryption disabled")
58
+
59
+
60
+ class Classification(Enum):
61
+ """Three-tier classification system for MemDocs patterns"""
62
+
63
+ PUBLIC = "PUBLIC" # Shareable across organization, anonymized
64
+ INTERNAL = "INTERNAL" # Team/project only, no PII or secrets
65
+ SENSITIVE = "SENSITIVE" # Encrypted at rest, access-controlled (HIPAA, finance)
66
+
67
+
68
+ @dataclass
69
+ class ClassificationRules:
70
+ """Security rules for each classification level"""
71
+
72
+ classification: Classification
73
+ encryption_required: bool
74
+ retention_days: int
75
+ access_level: str # "all_users", "project_team", "explicit_permission"
76
+ audit_all_access: bool = False
77
+
78
+
79
+ # Default classification rules based on enterprise security policy
80
+ DEFAULT_CLASSIFICATION_RULES: dict[Classification, ClassificationRules] = {
81
+ Classification.PUBLIC: ClassificationRules(
82
+ classification=Classification.PUBLIC,
83
+ encryption_required=False,
84
+ retention_days=365,
85
+ access_level="all_users",
86
+ audit_all_access=False,
87
+ ),
88
+ Classification.INTERNAL: ClassificationRules(
89
+ classification=Classification.INTERNAL,
90
+ encryption_required=False,
91
+ retention_days=180,
92
+ access_level="project_team",
93
+ audit_all_access=False,
94
+ ),
95
+ Classification.SENSITIVE: ClassificationRules(
96
+ classification=Classification.SENSITIVE,
97
+ encryption_required=True,
98
+ retention_days=90,
99
+ access_level="explicit_permission",
100
+ audit_all_access=True,
101
+ ),
102
+ }
103
+
104
+
105
+ @dataclass
106
+ class PatternMetadata:
107
+ """Metadata for stored MemDocs patterns"""
108
+
109
+ pattern_id: str
110
+ created_by: str
111
+ created_at: str
112
+ classification: str
113
+ retention_days: int
114
+ encrypted: bool
115
+ pattern_type: str
116
+ sanitization_applied: bool
117
+ pii_removed: int
118
+ secrets_detected: int
119
+ access_control: dict[str, Any] = field(default_factory=dict)
120
+ custom_metadata: dict[str, Any] = field(default_factory=dict)
121
+
122
+
123
+ @dataclass
124
+ class SecurePattern:
125
+ """Represents a securely stored pattern"""
126
+
127
+ pattern_id: str
128
+ content: str
129
+ metadata: PatternMetadata
130
+
131
+
132
+ class SecurityError(Exception):
133
+ """Raised when security policy is violated"""
134
+
135
+
136
+ class PermissionError(Exception):
137
+ """Raised when access is denied"""
138
+
139
+
140
+ class EncryptionManager:
141
+ """Manages encryption/decryption for SENSITIVE patterns.
142
+
143
+ Uses AES-256-GCM (Galois/Counter Mode) for authenticated encryption.
144
+ Keys are derived from a master key using HKDF.
145
+ """
146
+
147
+ def __init__(self, master_key: bytes | None = None):
148
+ """Initialize encryption manager.
149
+
150
+ Args:
151
+ master_key: 32-byte master key (or None to generate/load)
152
+
153
+ """
154
+ if not HAS_ENCRYPTION:
155
+ logger.warning("Encryption not available - install cryptography library")
156
+ self.enabled = False
157
+ return
158
+
159
+ self.enabled = True
160
+ self.master_key = master_key or self._load_or_generate_key()
161
+
162
+ def _load_or_generate_key(self) -> bytes:
163
+ """Load master key from environment or generate new one.
164
+
165
+ Production: Set EMPATHY_MASTER_KEY environment variable
166
+ Development: Generates ephemeral key (warning logged)
167
+ """
168
+ # Check environment variable first
169
+ if env_key := os.getenv("EMPATHY_MASTER_KEY"):
170
+ try:
171
+ return base64.b64decode(env_key)
172
+ except (binascii.Error, ValueError) as e:
173
+ logger.error("invalid_master_key_in_env", error=str(e))
174
+ raise ValueError("Invalid EMPATHY_MASTER_KEY format") from e
175
+
176
+ # Check key file
177
+ key_file = Path.home() / ".empathy" / "master.key"
178
+ if key_file.exists():
179
+ try:
180
+ return key_file.read_bytes()
181
+ except (OSError, PermissionError) as e:
182
+ logger.error("failed_to_load_key_file", error=str(e))
183
+
184
+ # Generate ephemeral key (NOT for production)
185
+ logger.warning(
186
+ "no_master_key_found",
187
+ message="Generating ephemeral encryption key - set EMPATHY_MASTER_KEY for production",
188
+ )
189
+ return AESGCM.generate_key(bit_length=256)
190
+
191
+ def encrypt(self, plaintext: str) -> str:
192
+ """Encrypt plaintext using AES-256-GCM.
193
+
194
+ Args:
195
+ plaintext: Content to encrypt
196
+
197
+ Returns:
198
+ Base64-encoded ciphertext with format: nonce||ciphertext||tag
199
+
200
+ Raises:
201
+ SecurityError: If encryption fails
202
+
203
+ """
204
+ if not self.enabled:
205
+ raise SecurityError("Encryption not available - install cryptography library")
206
+
207
+ try:
208
+ # Generate random 96-bit nonce (12 bytes)
209
+ nonce = os.urandom(12)
210
+
211
+ # Create AESGCM cipher
212
+ aesgcm = AESGCM(self.master_key)
213
+
214
+ # Encrypt and authenticate
215
+ ciphertext = aesgcm.encrypt(nonce, plaintext.encode("utf-8"), None)
216
+
217
+ # Combine nonce + ciphertext for storage
218
+ encrypted_data = nonce + ciphertext
219
+
220
+ # Return base64-encoded
221
+ return base64.b64encode(encrypted_data).decode("utf-8")
222
+
223
+ except (ValueError, TypeError, UnicodeEncodeError) as e:
224
+ logger.error("encryption_failed", error=str(e))
225
+ raise SecurityError(f"Encryption failed: {e}") from e
226
+
227
+ def decrypt(self, ciphertext_b64: str) -> str:
228
+ """Decrypt ciphertext using AES-256-GCM.
229
+
230
+ Args:
231
+ ciphertext_b64: Base64-encoded encrypted data
232
+
233
+ Returns:
234
+ Decrypted plaintext
235
+
236
+ Raises:
237
+ SecurityError: If decryption fails (invalid key, corrupted data, etc.)
238
+
239
+ """
240
+ if not self.enabled:
241
+ raise SecurityError("Encryption not available - install cryptography library")
242
+
243
+ try:
244
+ # Decode from base64
245
+ encrypted_data = base64.b64decode(ciphertext_b64)
246
+
247
+ # Extract nonce (first 12 bytes) and ciphertext (rest)
248
+ nonce = encrypted_data[:12]
249
+ ciphertext = encrypted_data[12:]
250
+
251
+ # Create AESGCM cipher
252
+ aesgcm = AESGCM(self.master_key)
253
+
254
+ # Decrypt and verify
255
+ plaintext_bytes = aesgcm.decrypt(nonce, ciphertext, None)
256
+
257
+ return plaintext_bytes.decode("utf-8")
258
+
259
+ except (ValueError, TypeError, UnicodeDecodeError, binascii.Error) as e:
260
+ logger.error("decryption_failed", error=str(e))
261
+ raise SecurityError(f"Decryption failed: {e}") from e
262
+
263
+
264
+ class MemDocsStorage:
265
+ """Mock/Simple MemDocs storage backend.
266
+
267
+ In production, this would integrate with the actual MemDocs library.
268
+ For now, provides a simple file-based storage for testing.
269
+ """
270
+
271
+ def __init__(self, storage_dir: str = "./memdocs_storage"):
272
+ """Initialize storage backend.
273
+
274
+ Args:
275
+ storage_dir: Directory for pattern storage
276
+
277
+ """
278
+ self.storage_dir = Path(storage_dir)
279
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
280
+ logger.info("memdocs_storage_initialized", storage_dir=str(self.storage_dir))
281
+
282
+ def store(self, pattern_id: str, content: str, metadata: dict[str, Any]) -> bool:
283
+ """Store a pattern.
284
+
285
+ Args:
286
+ pattern_id: Unique pattern identifier
287
+ content: Pattern content (may be encrypted)
288
+ metadata: Pattern metadata
289
+
290
+ Returns:
291
+ True if successful
292
+
293
+ Raises:
294
+ IOError: If storage fails
295
+
296
+ """
297
+ try:
298
+ pattern_file = self.storage_dir / f"{pattern_id}.json"
299
+
300
+ # Ensure parent directory exists
301
+ pattern_file.parent.mkdir(parents=True, exist_ok=True)
302
+
303
+ pattern_data = {"pattern_id": pattern_id, "content": content, "metadata": metadata}
304
+
305
+ with open(pattern_file, "w", encoding="utf-8") as f:
306
+ json.dump(pattern_data, f, indent=2)
307
+
308
+ logger.debug("pattern_stored", pattern_id=pattern_id)
309
+ return True
310
+
311
+ except (OSError, PermissionError, json.JSONDecodeError) as e:
312
+ logger.error("pattern_storage_failed", pattern_id=pattern_id, error=str(e))
313
+ raise
314
+
315
+ def retrieve(self, pattern_id: str) -> dict[str, Any] | None:
316
+ """Retrieve a pattern.
317
+
318
+ Args:
319
+ pattern_id: Unique pattern identifier
320
+
321
+ Returns:
322
+ Pattern data dictionary or None if not found
323
+
324
+ """
325
+ try:
326
+ pattern_file = self.storage_dir / f"{pattern_id}.json"
327
+
328
+ if not pattern_file.exists():
329
+ logger.warning("pattern_not_found", pattern_id=pattern_id)
330
+ return None
331
+
332
+ with open(pattern_file, encoding="utf-8") as f:
333
+ pattern_data: dict[str, Any] = json.load(f)
334
+
335
+ logger.debug("pattern_retrieved", pattern_id=pattern_id)
336
+ return pattern_data
337
+
338
+ except (OSError, PermissionError, json.JSONDecodeError) as e:
339
+ logger.error("pattern_retrieval_failed", pattern_id=pattern_id, error=str(e))
340
+ return None
341
+
342
+ def delete(self, pattern_id: str) -> bool:
343
+ """Delete a pattern.
344
+
345
+ Args:
346
+ pattern_id: Unique pattern identifier
347
+
348
+ Returns:
349
+ True if deleted, False if not found
350
+
351
+ """
352
+ try:
353
+ pattern_file = self.storage_dir / f"{pattern_id}.json"
354
+
355
+ if not pattern_file.exists():
356
+ return False
357
+
358
+ pattern_file.unlink()
359
+ logger.info("pattern_deleted", pattern_id=pattern_id)
360
+ return True
361
+
362
+ except (OSError, PermissionError) as e:
363
+ logger.error("pattern_deletion_failed", pattern_id=pattern_id, error=str(e))
364
+ return False
365
+
366
+ def list_patterns(
367
+ self,
368
+ classification: str | None = None,
369
+ created_by: str | None = None,
370
+ ) -> list[str]:
371
+ """List pattern IDs matching criteria.
372
+
373
+ Args:
374
+ classification: Filter by classification
375
+ created_by: Filter by creator
376
+
377
+ Returns:
378
+ List of pattern IDs
379
+
380
+ """
381
+ pattern_ids = []
382
+
383
+ for pattern_file in self.storage_dir.glob("*.json"):
384
+ try:
385
+ with open(pattern_file, encoding="utf-8") as f:
386
+ data = json.load(f)
387
+ metadata = data.get("metadata", {})
388
+
389
+ # Apply filters
390
+ if classification and metadata.get("classification") != classification:
391
+ continue
392
+ if created_by and metadata.get("created_by") != created_by:
393
+ continue
394
+
395
+ pattern_ids.append(data.get("pattern_id"))
396
+
397
+ except Exception:
398
+ continue
399
+
400
+ return pattern_ids
401
+
402
+
403
+ class SecureMemDocsIntegration:
404
+ """Secure integration between Claude Memory and MemDocs.
405
+
406
+ Enforces enterprise security policies from CLAUDE.md with:
407
+ - Automatic PII scrubbing
408
+ - Secrets detection and blocking
409
+ - Three-tier classification
410
+ - Encryption for SENSITIVE data
411
+ - Comprehensive audit logging
412
+ - Access control enforcement
413
+
414
+ Example:
415
+ >>> from empathy_llm_toolkit.claude_memory import ClaudeMemoryConfig
416
+ >>> config = ClaudeMemoryConfig(enabled=True, load_enterprise=True)
417
+ >>> integration = SecureMemDocsIntegration(config)
418
+ >>>
419
+ >>> # Store pattern with full security pipeline
420
+ >>> result = integration.store_pattern(
421
+ ... content="Patient diagnosis: diabetes type 2",
422
+ ... pattern_type="clinical_protocol",
423
+ ... user_id="doctor@hospital.com"
424
+ ... )
425
+ >>> # Automatically: PII scrubbed, classified as SENSITIVE, encrypted
426
+ >>>
427
+ >>> # Retrieve with access control
428
+ >>> pattern = integration.retrieve_pattern(
429
+ ... pattern_id=result["pattern_id"],
430
+ ... user_id="doctor@hospital.com"
431
+ ... )
432
+
433
+ """
434
+
435
+ def __init__(
436
+ self,
437
+ claude_memory_config=None,
438
+ storage_dir: str = "./memdocs_storage",
439
+ audit_log_dir: str | None = None, # Uses platform-appropriate default if None
440
+ classification_rules: dict[Classification, ClassificationRules] | None = None,
441
+ enable_encryption: bool = True,
442
+ master_key: bytes | None = None,
443
+ ):
444
+ """Initialize Secure MemDocs Integration.
445
+
446
+ Args:
447
+ claude_memory_config: Configuration for Claude memory integration
448
+ storage_dir: Directory for MemDocs storage
449
+ audit_log_dir: Directory for audit logs
450
+ classification_rules: Custom classification rules (uses defaults if None)
451
+ enable_encryption: Enable encryption for SENSITIVE patterns
452
+ master_key: Encryption master key (auto-generated if None)
453
+
454
+ """
455
+ self.claude_memory_config = claude_memory_config
456
+ self.classification_rules = classification_rules or DEFAULT_CLASSIFICATION_RULES
457
+
458
+ # Initialize security components
459
+ self.pii_scrubber = PIIScrubber()
460
+ self.secrets_detector = SecretsDetector()
461
+ self.audit_logger = AuditLogger(
462
+ log_dir=audit_log_dir,
463
+ enable_console_logging=True, # Development mode
464
+ )
465
+
466
+ # Initialize encryption
467
+ self.encryption_enabled = enable_encryption and HAS_ENCRYPTION
468
+ self.encryption_manager: EncryptionManager | None = None
469
+ if self.encryption_enabled:
470
+ self.encryption_manager = EncryptionManager(master_key)
471
+ elif enable_encryption:
472
+ logger.warning("encryption_disabled", reason="cryptography library not available")
473
+
474
+ # Initialize storage backend
475
+ self.storage = MemDocsStorage(storage_dir)
476
+
477
+ # Load security policies from enterprise CLAUDE.md
478
+ self.security_policies = self._load_security_policies()
479
+
480
+ logger.info(
481
+ "secure_memdocs_initialized",
482
+ encryption_enabled=self.encryption_enabled,
483
+ storage_dir=storage_dir,
484
+ audit_dir=audit_log_dir,
485
+ )
486
+
487
+ def _load_security_policies(self) -> dict[str, Any]:
488
+ """Load security policies from enterprise Claude memory.
489
+
490
+ In production, this would parse the enterprise CLAUDE.md file
491
+ to extract PII patterns, secret patterns, and classification rules.
492
+
493
+ For now, returns default policies that match the architecture spec.
494
+ """
495
+ policies = {
496
+ "pii_scrubbing_enabled": True,
497
+ "secrets_detection_enabled": True,
498
+ "classification_required": True,
499
+ "audit_logging_enabled": True,
500
+ "retention_enforcement_enabled": True,
501
+ }
502
+
503
+ logger.debug("security_policies_loaded", policies=policies)
504
+ return policies
505
+
506
+ def store_pattern(
507
+ self,
508
+ content: str,
509
+ pattern_type: str,
510
+ user_id: str,
511
+ auto_classify: bool = True,
512
+ explicit_classification: Classification | None = None,
513
+ session_id: str = "",
514
+ custom_metadata: dict[str, Any] | None = None,
515
+ ) -> dict[str, Any]:
516
+ """Store a pattern with full security pipeline.
517
+
518
+ Pipeline:
519
+ 1. PII scrubbing
520
+ 2. Secrets detection (blocks if found)
521
+ 3. Classification (auto or explicit)
522
+ 4. Encryption (if SENSITIVE)
523
+ 5. MemDocs storage
524
+ 6. Audit logging
525
+
526
+ Args:
527
+ content: Pattern content to store
528
+ pattern_type: Type of pattern (code, architecture, clinical, etc.)
529
+ user_id: User storing the pattern
530
+ auto_classify: Enable automatic classification
531
+ explicit_classification: Override auto-classification
532
+ session_id: Session identifier for audit
533
+ custom_metadata: Additional metadata
534
+
535
+ Returns:
536
+ Dictionary with:
537
+ - pattern_id: Unique identifier
538
+ - classification: Applied classification
539
+ - sanitization_report: PII and secrets detection results
540
+
541
+ Raises:
542
+ SecurityError: If secrets detected or security policy violated
543
+ ValueError: If invalid classification specified
544
+
545
+ Example:
546
+ >>> result = integration.store_pattern(
547
+ ... content="Patient vital signs protocol",
548
+ ... pattern_type="clinical_protocol",
549
+ ... user_id="nurse@hospital.com"
550
+ ... )
551
+ >>> print(f"Stored as {result['classification']}")
552
+
553
+ """
554
+ logger.info(
555
+ "store_pattern_started",
556
+ user_id=user_id,
557
+ pattern_type=pattern_type,
558
+ auto_classify=auto_classify,
559
+ )
560
+
561
+ try:
562
+ # Validate content
563
+ if not content or not content.strip():
564
+ raise ValueError("Content cannot be empty")
565
+
566
+ # Step 1 & 2: PII Scrubbing + Secrets Detection (PARALLEL for performance)
567
+ # Run both operations in parallel since they're independent
568
+ # Secrets detection runs on original content to catch secrets before PII scrubbing
569
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
570
+ # Submit both tasks in parallel
571
+ pii_future = executor.submit(self.pii_scrubber.scrub, content)
572
+ secrets_future = executor.submit(self.secrets_detector.detect, content)
573
+
574
+ # Wait for both to complete
575
+ sanitized_content, pii_detections = pii_future.result()
576
+ secrets_found = secrets_future.result()
577
+
578
+ pii_count = len(pii_detections)
579
+
580
+ if pii_count > 0:
581
+ logger.info(
582
+ "pii_scrubbed",
583
+ user_id=user_id,
584
+ pii_count=pii_count,
585
+ types=[d.pii_type for d in pii_detections],
586
+ )
587
+
588
+ if secrets_found:
589
+ # CRITICAL: Block storage if secrets detected
590
+ secret_types = [s.secret_type.value for s in secrets_found]
591
+ logger.error(
592
+ "secrets_detected_blocking_storage",
593
+ user_id=user_id,
594
+ secret_count=len(secrets_found),
595
+ types=secret_types,
596
+ )
597
+
598
+ # Log to audit trail
599
+ self.audit_logger.log_security_violation(
600
+ user_id=user_id,
601
+ violation_type="secrets_in_storage_attempt",
602
+ severity="CRITICAL",
603
+ details={
604
+ "secret_count": len(secrets_found),
605
+ "secret_types": secret_types,
606
+ "pattern_type": pattern_type,
607
+ },
608
+ session_id=session_id,
609
+ blocked=True,
610
+ )
611
+
612
+ raise SecurityError(
613
+ f"Secrets detected in pattern. Cannot store. Found: {secret_types}",
614
+ )
615
+
616
+ # Step 3: Classification
617
+ if explicit_classification:
618
+ classification = explicit_classification
619
+ logger.info("explicit_classification", classification=classification.value)
620
+ elif auto_classify:
621
+ classification = self._classify_pattern(sanitized_content, pattern_type)
622
+ logger.info("auto_classification", classification=classification.value)
623
+ else:
624
+ # Default to INTERNAL if not specified
625
+ classification = Classification.INTERNAL
626
+ logger.info("default_classification", classification=classification.value)
627
+
628
+ # Step 4: Apply classification-specific controls
629
+ rules = self.classification_rules[classification]
630
+
631
+ # Encrypt if required
632
+ final_content = sanitized_content
633
+ encrypted = False
634
+
635
+ if rules.encryption_required and self.encryption_enabled and self.encryption_manager:
636
+ final_content = self.encryption_manager.encrypt(sanitized_content)
637
+ encrypted = True
638
+ logger.info("pattern_encrypted", classification=classification.value)
639
+ elif rules.encryption_required and not self.encryption_enabled:
640
+ logger.warning(
641
+ "encryption_required_but_unavailable",
642
+ classification=classification.value,
643
+ action="storing_unencrypted",
644
+ )
645
+
646
+ # Generate pattern ID
647
+ pattern_id = self._generate_pattern_id(user_id, pattern_type)
648
+
649
+ # Step 5: Store in MemDocs with metadata
650
+ metadata = PatternMetadata(
651
+ pattern_id=pattern_id,
652
+ created_by=user_id,
653
+ created_at=datetime.utcnow().isoformat() + "Z",
654
+ classification=classification.value,
655
+ retention_days=rules.retention_days,
656
+ encrypted=encrypted,
657
+ pattern_type=pattern_type,
658
+ sanitization_applied=True,
659
+ pii_removed=pii_count,
660
+ secrets_detected=0,
661
+ access_control={
662
+ "access_level": rules.access_level,
663
+ "audit_required": rules.audit_all_access,
664
+ },
665
+ custom_metadata=custom_metadata or {},
666
+ )
667
+
668
+ self.storage.store(
669
+ pattern_id=pattern_id,
670
+ content=final_content,
671
+ metadata=metadata.__dict__,
672
+ )
673
+
674
+ # Step 6: Audit logging
675
+ self.audit_logger.log_pattern_store(
676
+ user_id=user_id,
677
+ pattern_id=pattern_id,
678
+ pattern_type=pattern_type,
679
+ classification=classification.value,
680
+ pii_scrubbed=pii_count,
681
+ secrets_detected=0,
682
+ retention_days=rules.retention_days,
683
+ encrypted=encrypted,
684
+ session_id=session_id,
685
+ status="success",
686
+ )
687
+
688
+ logger.info(
689
+ "pattern_stored_successfully",
690
+ pattern_id=pattern_id,
691
+ classification=classification.value,
692
+ encrypted=encrypted,
693
+ )
694
+
695
+ return {
696
+ "pattern_id": pattern_id,
697
+ "classification": classification.value,
698
+ "sanitization_report": {
699
+ "pii_removed": [{"type": d.pii_type, "count": 1} for d in pii_detections],
700
+ "pii_count": pii_count,
701
+ "secrets_detected": 0,
702
+ },
703
+ "metadata": {
704
+ "encrypted": encrypted,
705
+ "retention_days": rules.retention_days,
706
+ "created_at": metadata.created_at,
707
+ },
708
+ }
709
+
710
+ except SecurityError:
711
+ # Re-raise security errors
712
+ raise
713
+ except Exception as e:
714
+ # Log unexpected errors
715
+ logger.error("pattern_storage_failed", user_id=user_id, error=str(e))
716
+
717
+ self.audit_logger.log_pattern_store(
718
+ user_id=user_id,
719
+ pattern_id="",
720
+ pattern_type=pattern_type,
721
+ classification="UNKNOWN",
722
+ pii_scrubbed=0,
723
+ secrets_detected=0,
724
+ retention_days=0,
725
+ encrypted=False,
726
+ session_id=session_id,
727
+ status="failed",
728
+ error=str(e),
729
+ )
730
+
731
+ raise
732
+
733
+ def retrieve_pattern(
734
+ self,
735
+ pattern_id: str,
736
+ user_id: str,
737
+ check_permissions: bool = True,
738
+ session_id: str = "",
739
+ ) -> dict[str, Any]:
740
+ """Retrieve a pattern with access control and decryption.
741
+
742
+ Pipeline:
743
+ 1. Retrieve from MemDocs
744
+ 2. Check access permissions
745
+ 3. Decrypt (if SENSITIVE)
746
+ 4. Check retention policy
747
+ 5. Audit logging
748
+
749
+ Args:
750
+ pattern_id: Unique pattern identifier
751
+ user_id: User retrieving the pattern
752
+ check_permissions: Enforce access control
753
+ session_id: Session identifier for audit
754
+
755
+ Returns:
756
+ Dictionary with:
757
+ - content: Pattern content (decrypted if needed)
758
+ - metadata: Pattern metadata
759
+
760
+ Raises:
761
+ PermissionError: If access denied
762
+ ValueError: If pattern not found or retention expired
763
+ SecurityError: If decryption fails
764
+
765
+ Example:
766
+ >>> pattern = integration.retrieve_pattern(
767
+ ... pattern_id="pat_abc123",
768
+ ... user_id="user@company.com"
769
+ ... )
770
+ >>> print(pattern["content"])
771
+
772
+ """
773
+ logger.info(
774
+ "retrieve_pattern_started",
775
+ pattern_id=pattern_id,
776
+ user_id=user_id,
777
+ check_permissions=check_permissions,
778
+ )
779
+
780
+ try:
781
+ # Step 1: Retrieve from MemDocs
782
+ pattern_data = self.storage.retrieve(pattern_id)
783
+
784
+ if not pattern_data:
785
+ logger.warning("pattern_not_found", pattern_id=pattern_id)
786
+ raise ValueError(f"Pattern {pattern_id} not found")
787
+
788
+ content = pattern_data["content"]
789
+ metadata = pattern_data["metadata"]
790
+ classification = Classification[metadata["classification"]]
791
+
792
+ # Step 2: Check access permissions
793
+ access_granted = True
794
+ if check_permissions:
795
+ access_granted = self._check_access(
796
+ user_id=user_id,
797
+ classification=classification,
798
+ metadata=metadata,
799
+ )
800
+
801
+ if not access_granted:
802
+ logger.warning(
803
+ "access_denied",
804
+ pattern_id=pattern_id,
805
+ user_id=user_id,
806
+ classification=classification.value,
807
+ )
808
+
809
+ # Log access denial
810
+ self.audit_logger.log_pattern_retrieve(
811
+ user_id=user_id,
812
+ pattern_id=pattern_id,
813
+ classification=classification.value,
814
+ access_granted=False,
815
+ session_id=session_id,
816
+ status="blocked",
817
+ error="Access denied",
818
+ )
819
+
820
+ raise PermissionError(
821
+ f"User {user_id} does not have access to {classification.value} pattern",
822
+ )
823
+
824
+ # Step 3: Decrypt if needed
825
+ if metadata.get("encrypted", False):
826
+ if not self.encryption_enabled:
827
+ logger.error("decryption_required_but_unavailable", pattern_id=pattern_id)
828
+ raise SecurityError("Encryption not available for decryption")
829
+
830
+ if self.encryption_manager:
831
+ content = self.encryption_manager.decrypt(content)
832
+ logger.debug("pattern_decrypted", pattern_id=pattern_id)
833
+
834
+ # Step 4: Check retention policy
835
+ created_at = datetime.fromisoformat(metadata["created_at"].rstrip("Z"))
836
+ retention_days = metadata["retention_days"]
837
+ expiration_date = created_at + timedelta(days=retention_days)
838
+
839
+ if datetime.utcnow() > expiration_date:
840
+ logger.warning(
841
+ "pattern_retention_expired",
842
+ pattern_id=pattern_id,
843
+ created_at=metadata["created_at"],
844
+ retention_days=retention_days,
845
+ )
846
+ raise ValueError(
847
+ f"Pattern {pattern_id} has expired retention period "
848
+ f"(created: {metadata['created_at']}, retention: {retention_days} days)",
849
+ )
850
+
851
+ # Step 5: Audit logging
852
+ self.audit_logger.log_pattern_retrieve(
853
+ user_id=user_id,
854
+ pattern_id=pattern_id,
855
+ classification=classification.value,
856
+ access_granted=True,
857
+ permission_level=metadata["access_control"]["access_level"],
858
+ session_id=session_id,
859
+ status="success",
860
+ )
861
+
862
+ logger.info(
863
+ "pattern_retrieved_successfully",
864
+ pattern_id=pattern_id,
865
+ classification=classification.value,
866
+ )
867
+
868
+ return {"content": content, "metadata": metadata}
869
+
870
+ except (PermissionError, ValueError, SecurityError):
871
+ # Re-raise expected errors
872
+ raise
873
+ except Exception as e:
874
+ # Log unexpected errors
875
+ logger.error("pattern_retrieval_failed", pattern_id=pattern_id, error=str(e))
876
+
877
+ self.audit_logger.log_pattern_retrieve(
878
+ user_id=user_id,
879
+ pattern_id=pattern_id,
880
+ classification="UNKNOWN",
881
+ access_granted=False,
882
+ session_id=session_id,
883
+ status="failed",
884
+ error=str(e),
885
+ )
886
+
887
+ raise
888
+
889
+ def _classify_pattern(self, content: str, pattern_type: str) -> Classification:
890
+ """Auto-classify pattern based on content and type.
891
+
892
+ Classification heuristics:
893
+ - SENSITIVE: Healthcare, financial, regulated data keywords
894
+ - INTERNAL: Proprietary, confidential, internal keywords
895
+ - PUBLIC: Everything else (general patterns)
896
+
897
+ Args:
898
+ content: Pattern content (already PII-scrubbed)
899
+ pattern_type: Type of pattern
900
+
901
+ Returns:
902
+ Classification level
903
+
904
+ """
905
+ content_lower = content.lower()
906
+
907
+ # SENSITIVE: Healthcare keywords (HIPAA)
908
+ healthcare_keywords = [
909
+ "patient",
910
+ "medical",
911
+ "diagnosis",
912
+ "treatment",
913
+ "healthcare",
914
+ "clinical",
915
+ "hipaa",
916
+ "phi",
917
+ "medical record",
918
+ "prescription",
919
+ ]
920
+
921
+ # SENSITIVE: Financial keywords
922
+ financial_keywords = [
923
+ "financial",
924
+ "payment",
925
+ "credit card",
926
+ "banking",
927
+ "transaction",
928
+ "pci dss",
929
+ "payment card",
930
+ ]
931
+
932
+ # INTERNAL: Proprietary keywords
933
+ proprietary_keywords = [
934
+ "proprietary",
935
+ "confidential",
936
+ "internal",
937
+ "trade secret",
938
+ "company confidential",
939
+ "restricted",
940
+ ]
941
+
942
+ # Check for SENSITIVE indicators
943
+ if any(keyword in content_lower for keyword in healthcare_keywords):
944
+ return Classification.SENSITIVE
945
+
946
+ if any(keyword in content_lower for keyword in financial_keywords):
947
+ return Classification.SENSITIVE
948
+
949
+ # Pattern type based classification
950
+ if pattern_type in [
951
+ "clinical_protocol",
952
+ "medical_guideline",
953
+ "patient_workflow",
954
+ "financial_procedure",
955
+ ]:
956
+ return Classification.SENSITIVE
957
+
958
+ # Check for INTERNAL indicators
959
+ if any(keyword in content_lower for keyword in proprietary_keywords):
960
+ return Classification.INTERNAL
961
+
962
+ if pattern_type in ["architecture", "business_logic", "company_process"]:
963
+ return Classification.INTERNAL
964
+
965
+ # Default to PUBLIC for general patterns
966
+ return Classification.PUBLIC
967
+
968
+ def _check_access(
969
+ self,
970
+ user_id: str,
971
+ classification: Classification,
972
+ metadata: dict[str, Any],
973
+ ) -> bool:
974
+ """Check if user has access to pattern based on classification.
975
+
976
+ Access rules:
977
+ - PUBLIC: All users
978
+ - INTERNAL: Users on project team (simplified: always granted for demo)
979
+ - SENSITIVE: Explicit permission required (simplified: creator only)
980
+
981
+ Args:
982
+ user_id: User requesting access
983
+ classification: Pattern classification
984
+ metadata: Pattern metadata
985
+
986
+ Returns:
987
+ True if access granted, False otherwise
988
+
989
+ """
990
+ # PUBLIC: Everyone has access
991
+ if classification == Classification.PUBLIC:
992
+ return True
993
+
994
+ # INTERNAL: Check project team membership
995
+ # Simplified: Grant access (production would check team membership)
996
+ if classification == Classification.INTERNAL:
997
+ logger.debug("internal_access_check", user_id=user_id, granted=True)
998
+ return True
999
+
1000
+ # SENSITIVE: Require explicit permission
1001
+ # Simplified: Only pattern creator has access
1002
+ if classification == Classification.SENSITIVE:
1003
+ created_by = str(metadata.get("created_by", ""))
1004
+ granted = user_id == created_by
1005
+
1006
+ logger.debug(
1007
+ "sensitive_access_check",
1008
+ user_id=user_id,
1009
+ created_by=created_by,
1010
+ granted=granted,
1011
+ )
1012
+
1013
+ return bool(granted)
1014
+
1015
+ # Default deny
1016
+ return False
1017
+
1018
+ def _generate_pattern_id(self, user_id: str, pattern_type: str) -> str:
1019
+ """Generate unique pattern ID.
1020
+
1021
+ Format: pat_{timestamp}_{hash}
1022
+
1023
+ Args:
1024
+ user_id: User creating the pattern
1025
+ pattern_type: Type of pattern
1026
+
1027
+ Returns:
1028
+ Unique pattern identifier
1029
+
1030
+ """
1031
+ timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
1032
+
1033
+ # Create hash from user_id, pattern_type, and random component
1034
+ hash_input = f"{user_id}:{pattern_type}:{timestamp}:{os.urandom(8).hex()}"
1035
+ hash_digest = hashlib.sha256(hash_input.encode()).hexdigest()[:12]
1036
+
1037
+ return f"pat_{timestamp}_{hash_digest}"
1038
+
1039
+ def list_patterns(
1040
+ self,
1041
+ user_id: str,
1042
+ classification: Classification | None = None,
1043
+ pattern_type: str | None = None,
1044
+ ) -> list[dict[str, Any]]:
1045
+ """List patterns accessible to user.
1046
+
1047
+ Args:
1048
+ user_id: User listing patterns
1049
+ classification: Filter by classification
1050
+ pattern_type: Filter by pattern type
1051
+
1052
+ Returns:
1053
+ List of pattern summaries
1054
+
1055
+ """
1056
+ all_pattern_ids = self.storage.list_patterns()
1057
+ accessible_patterns = []
1058
+
1059
+ for pattern_id in all_pattern_ids:
1060
+ try:
1061
+ pattern_data = self.storage.retrieve(pattern_id)
1062
+ if not pattern_data:
1063
+ continue
1064
+
1065
+ metadata = pattern_data["metadata"]
1066
+ pat_classification = Classification[metadata["classification"]]
1067
+
1068
+ # Apply filters
1069
+ if classification and pat_classification != classification:
1070
+ continue
1071
+
1072
+ if pattern_type and metadata.get("pattern_type") != pattern_type:
1073
+ continue
1074
+
1075
+ # Check access
1076
+ if self._check_access(user_id, pat_classification, metadata):
1077
+ accessible_patterns.append(
1078
+ {
1079
+ "pattern_id": pattern_id,
1080
+ "pattern_type": metadata.get("pattern_type"),
1081
+ "classification": metadata["classification"],
1082
+ "created_by": metadata.get("created_by"),
1083
+ "created_at": metadata.get("created_at"),
1084
+ "encrypted": metadata.get("encrypted", False),
1085
+ },
1086
+ )
1087
+
1088
+ except Exception as e:
1089
+ logger.warning(
1090
+ "failed_to_load_pattern_metadata",
1091
+ pattern_id=pattern_id,
1092
+ error=str(e),
1093
+ )
1094
+ continue
1095
+
1096
+ return accessible_patterns
1097
+
1098
+ def delete_pattern(self, pattern_id: str, user_id: str, session_id: str = "") -> bool:
1099
+ """Delete a pattern (with access control).
1100
+
1101
+ Args:
1102
+ pattern_id: Pattern to delete
1103
+ user_id: User requesting deletion
1104
+ session_id: Session identifier
1105
+
1106
+ Returns:
1107
+ True if deleted successfully
1108
+
1109
+ Raises:
1110
+ PermissionError: If user doesn't have permission to delete
1111
+
1112
+ """
1113
+ # Retrieve pattern to check permissions
1114
+ pattern_data = self.storage.retrieve(pattern_id)
1115
+
1116
+ if not pattern_data:
1117
+ logger.warning("pattern_not_found_for_deletion", pattern_id=pattern_id)
1118
+ return False
1119
+
1120
+ metadata = pattern_data["metadata"]
1121
+
1122
+ # Only creator can delete (simplified access control)
1123
+ if metadata.get("created_by") != user_id:
1124
+ logger.warning(
1125
+ "delete_permission_denied",
1126
+ pattern_id=pattern_id,
1127
+ user_id=user_id,
1128
+ created_by=metadata.get("created_by"),
1129
+ )
1130
+ raise PermissionError(f"User {user_id} cannot delete pattern {pattern_id}")
1131
+
1132
+ # Delete pattern
1133
+ deleted = self.storage.delete(pattern_id)
1134
+
1135
+ if deleted:
1136
+ # Log deletion
1137
+ self.audit_logger._write_event(
1138
+ AuditEvent(
1139
+ event_type="delete_pattern",
1140
+ user_id=user_id,
1141
+ session_id=session_id,
1142
+ status="success",
1143
+ data={
1144
+ "pattern_id": pattern_id,
1145
+ "classification": metadata["classification"],
1146
+ },
1147
+ ),
1148
+ )
1149
+
1150
+ logger.info("pattern_deleted", pattern_id=pattern_id, user_id=user_id)
1151
+
1152
+ return deleted
1153
+
1154
+ def get_statistics(self) -> dict[str, Any]:
1155
+ """Get statistics about stored patterns.
1156
+
1157
+ Returns:
1158
+ Dictionary with pattern statistics
1159
+
1160
+ """
1161
+ all_patterns = self.storage.list_patterns()
1162
+
1163
+ stats: dict[str, Any] = {
1164
+ "total_patterns": len(all_patterns),
1165
+ "by_classification": {
1166
+ "PUBLIC": 0,
1167
+ "INTERNAL": 0,
1168
+ "SENSITIVE": 0,
1169
+ },
1170
+ "encrypted_count": 0,
1171
+ "with_pii_scrubbed": 0,
1172
+ }
1173
+
1174
+ for pattern_id in all_patterns:
1175
+ try:
1176
+ pattern_data = self.storage.retrieve(pattern_id)
1177
+ if not pattern_data:
1178
+ continue
1179
+
1180
+ metadata = pattern_data["metadata"]
1181
+ classification = metadata.get("classification", "INTERNAL")
1182
+
1183
+ stats["by_classification"][classification] += 1
1184
+
1185
+ if metadata.get("encrypted", False):
1186
+ stats["encrypted_count"] += 1
1187
+
1188
+ if metadata.get("pii_removed", 0) > 0:
1189
+ stats["with_pii_scrubbed"] += 1
1190
+
1191
+ except Exception:
1192
+ continue
1193
+
1194
+ return stats