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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,913 @@
|
|
|
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 concurrent.futures
|
|
31
|
+
import hashlib
|
|
32
|
+
import os
|
|
33
|
+
from datetime import datetime, timedelta
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
import structlog
|
|
37
|
+
|
|
38
|
+
from .encryption import HAS_ENCRYPTION, EncryptionManager
|
|
39
|
+
|
|
40
|
+
# Import extracted modules for backward compatibility
|
|
41
|
+
from .long_term_types import (
|
|
42
|
+
DEFAULT_CLASSIFICATION_RULES,
|
|
43
|
+
Classification,
|
|
44
|
+
ClassificationRules,
|
|
45
|
+
PatternMetadata,
|
|
46
|
+
PermissionError,
|
|
47
|
+
SecurePattern,
|
|
48
|
+
SecurityError,
|
|
49
|
+
)
|
|
50
|
+
from .security.audit_logger import AuditEvent, AuditLogger
|
|
51
|
+
from .security.pii_scrubber import PIIScrubber
|
|
52
|
+
from .security.secrets_detector import SecretsDetector
|
|
53
|
+
from .simple_storage import LongTermMemory
|
|
54
|
+
from .storage_backend import MemDocsStorage
|
|
55
|
+
|
|
56
|
+
logger = structlog.get_logger(__name__)
|
|
57
|
+
|
|
58
|
+
# HAS_ENCRYPTION is now imported from encryption module
|
|
59
|
+
|
|
60
|
+
# NOTE: Classification, ClassificationRules, PatternMetadata, SecurePattern,
|
|
61
|
+
# EncryptionManager, MemDocsStorage, and LongTermMemory have been extracted to
|
|
62
|
+
# separate modules for better modularity. They are imported above and re-exported
|
|
63
|
+
# below for backward compatibility.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SecureMemDocsIntegration:
|
|
67
|
+
"""Secure integration between Claude Memory and MemDocs.
|
|
68
|
+
|
|
69
|
+
Enforces enterprise security policies from CLAUDE.md with:
|
|
70
|
+
- Automatic PII scrubbing
|
|
71
|
+
- Secrets detection and blocking
|
|
72
|
+
- Three-tier classification
|
|
73
|
+
- Encryption for SENSITIVE data
|
|
74
|
+
- Comprehensive audit logging
|
|
75
|
+
- Access control enforcement
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> from attune_llm.claude_memory import ClaudeMemoryConfig
|
|
79
|
+
>>> config = ClaudeMemoryConfig(enabled=True, load_enterprise=True)
|
|
80
|
+
>>> integration = SecureMemDocsIntegration(config)
|
|
81
|
+
>>>
|
|
82
|
+
>>> # Store pattern with full security pipeline
|
|
83
|
+
>>> result = integration.store_pattern(
|
|
84
|
+
... content="Patient diagnosis: diabetes type 2",
|
|
85
|
+
... pattern_type="clinical_protocol",
|
|
86
|
+
... user_id="doctor@hospital.com"
|
|
87
|
+
... )
|
|
88
|
+
>>> # Automatically: PII scrubbed, classified as SENSITIVE, encrypted
|
|
89
|
+
>>>
|
|
90
|
+
>>> # Retrieve with access control
|
|
91
|
+
>>> pattern = integration.retrieve_pattern(
|
|
92
|
+
... pattern_id=result["pattern_id"],
|
|
93
|
+
... user_id="doctor@hospital.com"
|
|
94
|
+
... )
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
claude_memory_config=None,
|
|
101
|
+
storage_dir: str = "./memdocs_storage",
|
|
102
|
+
audit_log_dir: str | None = None, # Uses platform-appropriate default if None
|
|
103
|
+
classification_rules: dict[Classification, ClassificationRules] | None = None,
|
|
104
|
+
enable_encryption: bool = True,
|
|
105
|
+
master_key: bytes | None = None,
|
|
106
|
+
):
|
|
107
|
+
"""Initialize Secure MemDocs Integration.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
claude_memory_config: Configuration for Claude memory integration
|
|
111
|
+
storage_dir: Directory for MemDocs storage
|
|
112
|
+
audit_log_dir: Directory for audit logs
|
|
113
|
+
classification_rules: Custom classification rules (uses defaults if None)
|
|
114
|
+
enable_encryption: Enable encryption for SENSITIVE patterns
|
|
115
|
+
master_key: Encryption master key (auto-generated if None)
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
self.claude_memory_config = claude_memory_config
|
|
119
|
+
self.classification_rules = classification_rules or DEFAULT_CLASSIFICATION_RULES
|
|
120
|
+
|
|
121
|
+
# Initialize security components
|
|
122
|
+
self.pii_scrubber = PIIScrubber()
|
|
123
|
+
self.secrets_detector = SecretsDetector()
|
|
124
|
+
self.audit_logger = AuditLogger(
|
|
125
|
+
log_dir=audit_log_dir,
|
|
126
|
+
enable_console_logging=True, # Development mode
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Initialize encryption
|
|
130
|
+
self.encryption_enabled = enable_encryption and HAS_ENCRYPTION
|
|
131
|
+
self.encryption_manager: EncryptionManager | None = None
|
|
132
|
+
if self.encryption_enabled:
|
|
133
|
+
self.encryption_manager = EncryptionManager(master_key)
|
|
134
|
+
elif enable_encryption:
|
|
135
|
+
logger.warning("encryption_disabled", reason="cryptography library not available")
|
|
136
|
+
|
|
137
|
+
# Initialize storage backend
|
|
138
|
+
self.storage = MemDocsStorage(storage_dir)
|
|
139
|
+
|
|
140
|
+
# Load security policies from enterprise CLAUDE.md
|
|
141
|
+
self.security_policies = self._load_security_policies()
|
|
142
|
+
|
|
143
|
+
logger.info(
|
|
144
|
+
"secure_memdocs_initialized",
|
|
145
|
+
encryption_enabled=self.encryption_enabled,
|
|
146
|
+
storage_dir=storage_dir,
|
|
147
|
+
audit_dir=audit_log_dir,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _load_security_policies(self) -> dict[str, Any]:
|
|
151
|
+
"""Load security policies from enterprise Claude memory.
|
|
152
|
+
|
|
153
|
+
In production, this would parse the enterprise CLAUDE.md file
|
|
154
|
+
to extract PII patterns, secret patterns, and classification rules.
|
|
155
|
+
|
|
156
|
+
For now, returns default policies that match the architecture spec.
|
|
157
|
+
"""
|
|
158
|
+
policies = {
|
|
159
|
+
"pii_scrubbing_enabled": True,
|
|
160
|
+
"secrets_detection_enabled": True,
|
|
161
|
+
"classification_required": True,
|
|
162
|
+
"audit_logging_enabled": True,
|
|
163
|
+
"retention_enforcement_enabled": True,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logger.debug("security_policies_loaded", policies=policies)
|
|
167
|
+
return policies
|
|
168
|
+
|
|
169
|
+
def store_pattern(
|
|
170
|
+
self,
|
|
171
|
+
content: str,
|
|
172
|
+
pattern_type: str,
|
|
173
|
+
user_id: str,
|
|
174
|
+
auto_classify: bool = True,
|
|
175
|
+
explicit_classification: Classification | None = None,
|
|
176
|
+
session_id: str = "",
|
|
177
|
+
custom_metadata: dict[str, Any] | None = None,
|
|
178
|
+
) -> dict[str, Any]:
|
|
179
|
+
"""Store a pattern with full security pipeline.
|
|
180
|
+
|
|
181
|
+
Pipeline:
|
|
182
|
+
1. PII scrubbing
|
|
183
|
+
2. Secrets detection (blocks if found)
|
|
184
|
+
3. Classification (auto or explicit)
|
|
185
|
+
4. Encryption (if SENSITIVE)
|
|
186
|
+
5. MemDocs storage
|
|
187
|
+
6. Audit logging
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
content: Pattern content to store
|
|
191
|
+
pattern_type: Type of pattern (code, architecture, clinical, etc.)
|
|
192
|
+
user_id: User storing the pattern
|
|
193
|
+
auto_classify: Enable automatic classification
|
|
194
|
+
explicit_classification: Override auto-classification
|
|
195
|
+
session_id: Session identifier for audit
|
|
196
|
+
custom_metadata: Additional metadata
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dictionary with:
|
|
200
|
+
- pattern_id: Unique identifier
|
|
201
|
+
- classification: Applied classification
|
|
202
|
+
- sanitization_report: PII and secrets detection results
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
SecurityError: If secrets detected or security policy violated
|
|
206
|
+
ValueError: If content/pattern_type/user_id empty or invalid classification
|
|
207
|
+
TypeError: If custom_metadata is not dict
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
>>> result = integration.store_pattern(
|
|
211
|
+
... content="Patient vital signs protocol",
|
|
212
|
+
... pattern_type="clinical_protocol",
|
|
213
|
+
... user_id="nurse@hospital.com"
|
|
214
|
+
... )
|
|
215
|
+
>>> print(f"Stored as {result['classification']}")
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
logger.info(
|
|
219
|
+
"store_pattern_started",
|
|
220
|
+
user_id=user_id,
|
|
221
|
+
pattern_type=pattern_type,
|
|
222
|
+
auto_classify=auto_classify,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
# Pattern 1: String ID validation
|
|
227
|
+
if not content or not content.strip():
|
|
228
|
+
raise ValueError(f"content cannot be empty. Got: {content!r}")
|
|
229
|
+
if not pattern_type or not pattern_type.strip():
|
|
230
|
+
raise ValueError(f"pattern_type cannot be empty. Got: {pattern_type!r}")
|
|
231
|
+
if not user_id or not user_id.strip():
|
|
232
|
+
raise ValueError(f"user_id cannot be empty. Got: {user_id!r}")
|
|
233
|
+
|
|
234
|
+
# Pattern 5: Type validation
|
|
235
|
+
if custom_metadata is not None and not isinstance(custom_metadata, dict):
|
|
236
|
+
raise TypeError(
|
|
237
|
+
f"custom_metadata must be dict, got {type(custom_metadata).__name__}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Step 1 & 2: PII Scrubbing + Secrets Detection (PARALLEL for performance)
|
|
241
|
+
# Run both operations in parallel since they're independent
|
|
242
|
+
# Secrets detection runs on original content to catch secrets before PII scrubbing
|
|
243
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
|
|
244
|
+
# Submit both tasks in parallel
|
|
245
|
+
pii_future = executor.submit(self.pii_scrubber.scrub, content)
|
|
246
|
+
secrets_future = executor.submit(self.secrets_detector.detect, content)
|
|
247
|
+
|
|
248
|
+
# Wait for both to complete
|
|
249
|
+
sanitized_content, pii_detections = pii_future.result()
|
|
250
|
+
secrets_found = secrets_future.result()
|
|
251
|
+
|
|
252
|
+
pii_count = len(pii_detections)
|
|
253
|
+
|
|
254
|
+
if pii_count > 0:
|
|
255
|
+
logger.info(
|
|
256
|
+
"pii_scrubbed",
|
|
257
|
+
user_id=user_id,
|
|
258
|
+
pii_count=pii_count,
|
|
259
|
+
types=[d.pii_type for d in pii_detections],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if secrets_found:
|
|
263
|
+
# CRITICAL: Block storage if secrets detected
|
|
264
|
+
secret_types = [s.secret_type.value for s in secrets_found]
|
|
265
|
+
logger.error(
|
|
266
|
+
"secrets_detected_blocking_storage",
|
|
267
|
+
user_id=user_id,
|
|
268
|
+
secret_count=len(secrets_found),
|
|
269
|
+
types=secret_types,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Log to audit trail
|
|
273
|
+
self.audit_logger.log_security_violation(
|
|
274
|
+
user_id=user_id,
|
|
275
|
+
violation_type="secrets_in_storage_attempt",
|
|
276
|
+
severity="CRITICAL",
|
|
277
|
+
details={
|
|
278
|
+
"secret_count": len(secrets_found),
|
|
279
|
+
"secret_types": secret_types,
|
|
280
|
+
"pattern_type": pattern_type,
|
|
281
|
+
},
|
|
282
|
+
session_id=session_id,
|
|
283
|
+
blocked=True,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
raise SecurityError(
|
|
287
|
+
f"Secrets detected in pattern. Cannot store. Found: {secret_types}",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Step 3: Classification
|
|
291
|
+
if explicit_classification:
|
|
292
|
+
classification = explicit_classification
|
|
293
|
+
logger.info("explicit_classification", classification=classification.value)
|
|
294
|
+
elif auto_classify:
|
|
295
|
+
classification = self._classify_pattern(sanitized_content, pattern_type)
|
|
296
|
+
logger.info("auto_classification", classification=classification.value)
|
|
297
|
+
else:
|
|
298
|
+
# Default to INTERNAL if not specified
|
|
299
|
+
classification = Classification.INTERNAL
|
|
300
|
+
logger.info("default_classification", classification=classification.value)
|
|
301
|
+
|
|
302
|
+
# Step 4: Apply classification-specific controls
|
|
303
|
+
rules = self.classification_rules[classification]
|
|
304
|
+
|
|
305
|
+
# Encrypt if required
|
|
306
|
+
final_content = sanitized_content
|
|
307
|
+
encrypted = False
|
|
308
|
+
|
|
309
|
+
if rules.encryption_required and self.encryption_enabled and self.encryption_manager:
|
|
310
|
+
final_content = self.encryption_manager.encrypt(sanitized_content)
|
|
311
|
+
encrypted = True
|
|
312
|
+
logger.info("pattern_encrypted", classification=classification.value)
|
|
313
|
+
elif rules.encryption_required and not self.encryption_enabled:
|
|
314
|
+
logger.warning(
|
|
315
|
+
"encryption_required_but_unavailable",
|
|
316
|
+
classification=classification.value,
|
|
317
|
+
action="storing_unencrypted",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Generate pattern ID
|
|
321
|
+
pattern_id = self._generate_pattern_id(user_id, pattern_type)
|
|
322
|
+
|
|
323
|
+
# Step 5: Store in MemDocs with metadata
|
|
324
|
+
metadata = PatternMetadata(
|
|
325
|
+
pattern_id=pattern_id,
|
|
326
|
+
created_by=user_id,
|
|
327
|
+
created_at=datetime.utcnow().isoformat() + "Z",
|
|
328
|
+
classification=classification.value,
|
|
329
|
+
retention_days=rules.retention_days,
|
|
330
|
+
encrypted=encrypted,
|
|
331
|
+
pattern_type=pattern_type,
|
|
332
|
+
sanitization_applied=True,
|
|
333
|
+
pii_removed=pii_count,
|
|
334
|
+
secrets_detected=0,
|
|
335
|
+
access_control={
|
|
336
|
+
"access_level": rules.access_level,
|
|
337
|
+
"audit_required": rules.audit_all_access,
|
|
338
|
+
},
|
|
339
|
+
custom_metadata=custom_metadata or {},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
self.storage.store(
|
|
343
|
+
pattern_id=pattern_id,
|
|
344
|
+
content=final_content,
|
|
345
|
+
metadata=metadata.__dict__,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Step 6: Audit logging
|
|
349
|
+
self.audit_logger.log_pattern_store(
|
|
350
|
+
user_id=user_id,
|
|
351
|
+
pattern_id=pattern_id,
|
|
352
|
+
pattern_type=pattern_type,
|
|
353
|
+
classification=classification.value,
|
|
354
|
+
pii_scrubbed=pii_count,
|
|
355
|
+
secrets_detected=0,
|
|
356
|
+
retention_days=rules.retention_days,
|
|
357
|
+
encrypted=encrypted,
|
|
358
|
+
session_id=session_id,
|
|
359
|
+
status="success",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
logger.info(
|
|
363
|
+
"pattern_stored_successfully",
|
|
364
|
+
pattern_id=pattern_id,
|
|
365
|
+
classification=classification.value,
|
|
366
|
+
encrypted=encrypted,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
"pattern_id": pattern_id,
|
|
371
|
+
"classification": classification.value,
|
|
372
|
+
"sanitization_report": {
|
|
373
|
+
"pii_removed": [{"type": d.pii_type, "count": 1} for d in pii_detections],
|
|
374
|
+
"pii_count": pii_count,
|
|
375
|
+
"secrets_detected": 0,
|
|
376
|
+
},
|
|
377
|
+
"metadata": {
|
|
378
|
+
"encrypted": encrypted,
|
|
379
|
+
"retention_days": rules.retention_days,
|
|
380
|
+
"created_at": metadata.created_at,
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
except SecurityError:
|
|
385
|
+
# Re-raise security errors
|
|
386
|
+
raise
|
|
387
|
+
except Exception as e:
|
|
388
|
+
# Log unexpected errors
|
|
389
|
+
logger.error("pattern_storage_failed", user_id=user_id, error=str(e))
|
|
390
|
+
|
|
391
|
+
self.audit_logger.log_pattern_store(
|
|
392
|
+
user_id=user_id,
|
|
393
|
+
pattern_id="",
|
|
394
|
+
pattern_type=pattern_type,
|
|
395
|
+
classification="UNKNOWN",
|
|
396
|
+
pii_scrubbed=0,
|
|
397
|
+
secrets_detected=0,
|
|
398
|
+
retention_days=0,
|
|
399
|
+
encrypted=False,
|
|
400
|
+
session_id=session_id,
|
|
401
|
+
status="failed",
|
|
402
|
+
error=str(e),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
raise
|
|
406
|
+
|
|
407
|
+
def retrieve_pattern(
|
|
408
|
+
self,
|
|
409
|
+
pattern_id: str,
|
|
410
|
+
user_id: str,
|
|
411
|
+
check_permissions: bool = True,
|
|
412
|
+
session_id: str = "",
|
|
413
|
+
) -> dict[str, Any]:
|
|
414
|
+
"""Retrieve a pattern with access control and decryption.
|
|
415
|
+
|
|
416
|
+
Pipeline:
|
|
417
|
+
1. Retrieve from MemDocs
|
|
418
|
+
2. Check access permissions
|
|
419
|
+
3. Decrypt (if SENSITIVE)
|
|
420
|
+
4. Check retention policy
|
|
421
|
+
5. Audit logging
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
pattern_id: Unique pattern identifier
|
|
425
|
+
user_id: User retrieving the pattern
|
|
426
|
+
check_permissions: Enforce access control
|
|
427
|
+
session_id: Session identifier for audit
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Dictionary with:
|
|
431
|
+
- content: Pattern content (decrypted if needed)
|
|
432
|
+
- metadata: Pattern metadata
|
|
433
|
+
|
|
434
|
+
Raises:
|
|
435
|
+
PermissionError: If access denied
|
|
436
|
+
ValueError: If pattern_id/user_id empty, pattern not found, or retention expired
|
|
437
|
+
SecurityError: If decryption fails
|
|
438
|
+
|
|
439
|
+
Example:
|
|
440
|
+
>>> pattern = integration.retrieve_pattern(
|
|
441
|
+
... pattern_id="pat_abc123",
|
|
442
|
+
... user_id="user@company.com"
|
|
443
|
+
... )
|
|
444
|
+
>>> print(pattern["content"])
|
|
445
|
+
|
|
446
|
+
"""
|
|
447
|
+
# Pattern 1: String ID validation
|
|
448
|
+
if not pattern_id or not pattern_id.strip():
|
|
449
|
+
raise ValueError(f"pattern_id cannot be empty. Got: {pattern_id!r}")
|
|
450
|
+
if not user_id or not user_id.strip():
|
|
451
|
+
raise ValueError("user_id cannot be empty")
|
|
452
|
+
|
|
453
|
+
logger.info(
|
|
454
|
+
"retrieve_pattern_started",
|
|
455
|
+
pattern_id=pattern_id,
|
|
456
|
+
user_id=user_id,
|
|
457
|
+
check_permissions=check_permissions,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
try:
|
|
461
|
+
# Step 1: Retrieve from MemDocs
|
|
462
|
+
pattern_data = self.storage.retrieve(pattern_id)
|
|
463
|
+
|
|
464
|
+
if not pattern_data:
|
|
465
|
+
logger.warning("pattern_not_found", pattern_id=pattern_id)
|
|
466
|
+
raise ValueError(f"Pattern {pattern_id} not found")
|
|
467
|
+
|
|
468
|
+
content = pattern_data["content"]
|
|
469
|
+
metadata = pattern_data["metadata"]
|
|
470
|
+
classification = Classification[metadata["classification"]]
|
|
471
|
+
|
|
472
|
+
# Step 2: Check access permissions
|
|
473
|
+
access_granted = True
|
|
474
|
+
if check_permissions:
|
|
475
|
+
access_granted = self._check_access(
|
|
476
|
+
user_id=user_id,
|
|
477
|
+
classification=classification,
|
|
478
|
+
metadata=metadata,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
if not access_granted:
|
|
482
|
+
logger.warning(
|
|
483
|
+
"access_denied",
|
|
484
|
+
pattern_id=pattern_id,
|
|
485
|
+
user_id=user_id,
|
|
486
|
+
classification=classification.value,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Log access denial
|
|
490
|
+
self.audit_logger.log_pattern_retrieve(
|
|
491
|
+
user_id=user_id,
|
|
492
|
+
pattern_id=pattern_id,
|
|
493
|
+
classification=classification.value,
|
|
494
|
+
access_granted=False,
|
|
495
|
+
session_id=session_id,
|
|
496
|
+
status="blocked",
|
|
497
|
+
error="Access denied",
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
raise PermissionError(
|
|
501
|
+
f"User {user_id} does not have access to {classification.value} pattern",
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Step 3: Decrypt if needed
|
|
505
|
+
if metadata.get("encrypted", False):
|
|
506
|
+
if not self.encryption_enabled:
|
|
507
|
+
logger.error("decryption_required_but_unavailable", pattern_id=pattern_id)
|
|
508
|
+
raise SecurityError("Encryption not available for decryption")
|
|
509
|
+
|
|
510
|
+
if self.encryption_manager:
|
|
511
|
+
content = self.encryption_manager.decrypt(content)
|
|
512
|
+
logger.debug("pattern_decrypted", pattern_id=pattern_id)
|
|
513
|
+
|
|
514
|
+
# Step 4: Check retention policy
|
|
515
|
+
created_at = datetime.fromisoformat(metadata["created_at"].rstrip("Z"))
|
|
516
|
+
retention_days = metadata["retention_days"]
|
|
517
|
+
expiration_date = created_at + timedelta(days=retention_days)
|
|
518
|
+
|
|
519
|
+
if datetime.utcnow() > expiration_date:
|
|
520
|
+
logger.warning(
|
|
521
|
+
"pattern_retention_expired",
|
|
522
|
+
pattern_id=pattern_id,
|
|
523
|
+
created_at=metadata["created_at"],
|
|
524
|
+
retention_days=retention_days,
|
|
525
|
+
)
|
|
526
|
+
raise ValueError(
|
|
527
|
+
f"Pattern {pattern_id} has expired retention period "
|
|
528
|
+
f"(created: {metadata['created_at']}, retention: {retention_days} days)",
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# Step 5: Audit logging
|
|
532
|
+
self.audit_logger.log_pattern_retrieve(
|
|
533
|
+
user_id=user_id,
|
|
534
|
+
pattern_id=pattern_id,
|
|
535
|
+
classification=classification.value,
|
|
536
|
+
access_granted=True,
|
|
537
|
+
permission_level=metadata["access_control"]["access_level"],
|
|
538
|
+
session_id=session_id,
|
|
539
|
+
status="success",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
logger.info(
|
|
543
|
+
"pattern_retrieved_successfully",
|
|
544
|
+
pattern_id=pattern_id,
|
|
545
|
+
classification=classification.value,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
return {"content": content, "metadata": metadata}
|
|
549
|
+
|
|
550
|
+
except (PermissionError, ValueError, SecurityError):
|
|
551
|
+
# Re-raise expected errors
|
|
552
|
+
raise
|
|
553
|
+
except Exception as e:
|
|
554
|
+
# Log unexpected errors
|
|
555
|
+
logger.error("pattern_retrieval_failed", pattern_id=pattern_id, error=str(e))
|
|
556
|
+
|
|
557
|
+
self.audit_logger.log_pattern_retrieve(
|
|
558
|
+
user_id=user_id,
|
|
559
|
+
pattern_id=pattern_id,
|
|
560
|
+
classification="UNKNOWN",
|
|
561
|
+
access_granted=False,
|
|
562
|
+
session_id=session_id,
|
|
563
|
+
status="failed",
|
|
564
|
+
error=str(e),
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
raise
|
|
568
|
+
|
|
569
|
+
def _classify_pattern(self, content: str, pattern_type: str) -> Classification:
|
|
570
|
+
"""Auto-classify pattern based on content and type.
|
|
571
|
+
|
|
572
|
+
Classification heuristics:
|
|
573
|
+
- SENSITIVE: Healthcare, financial, regulated data keywords
|
|
574
|
+
- INTERNAL: Proprietary, confidential, internal keywords
|
|
575
|
+
- PUBLIC: Everything else (general patterns)
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
content: Pattern content (already PII-scrubbed)
|
|
579
|
+
pattern_type: Type of pattern
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Classification level
|
|
583
|
+
|
|
584
|
+
"""
|
|
585
|
+
content_lower = content.lower()
|
|
586
|
+
|
|
587
|
+
# SENSITIVE: Healthcare keywords (HIPAA)
|
|
588
|
+
healthcare_keywords = [
|
|
589
|
+
"patient",
|
|
590
|
+
"medical",
|
|
591
|
+
"diagnosis",
|
|
592
|
+
"treatment",
|
|
593
|
+
"healthcare",
|
|
594
|
+
"clinical",
|
|
595
|
+
"hipaa",
|
|
596
|
+
"phi",
|
|
597
|
+
"medical record",
|
|
598
|
+
"prescription",
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
# SENSITIVE: Financial keywords
|
|
602
|
+
financial_keywords = [
|
|
603
|
+
"financial",
|
|
604
|
+
"payment",
|
|
605
|
+
"credit card",
|
|
606
|
+
"banking",
|
|
607
|
+
"transaction",
|
|
608
|
+
"pci dss",
|
|
609
|
+
"payment card",
|
|
610
|
+
]
|
|
611
|
+
|
|
612
|
+
# INTERNAL: Proprietary keywords
|
|
613
|
+
proprietary_keywords = [
|
|
614
|
+
"proprietary",
|
|
615
|
+
"confidential",
|
|
616
|
+
"internal",
|
|
617
|
+
"trade secret",
|
|
618
|
+
"company confidential",
|
|
619
|
+
"restricted",
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
# Check for SENSITIVE indicators
|
|
623
|
+
if any(keyword in content_lower for keyword in healthcare_keywords):
|
|
624
|
+
return Classification.SENSITIVE
|
|
625
|
+
|
|
626
|
+
if any(keyword in content_lower for keyword in financial_keywords):
|
|
627
|
+
return Classification.SENSITIVE
|
|
628
|
+
|
|
629
|
+
# Pattern type based classification
|
|
630
|
+
if pattern_type in [
|
|
631
|
+
"clinical_protocol",
|
|
632
|
+
"medical_guideline",
|
|
633
|
+
"patient_workflow",
|
|
634
|
+
"financial_procedure",
|
|
635
|
+
]:
|
|
636
|
+
return Classification.SENSITIVE
|
|
637
|
+
|
|
638
|
+
# Check for INTERNAL indicators
|
|
639
|
+
if any(keyword in content_lower for keyword in proprietary_keywords):
|
|
640
|
+
return Classification.INTERNAL
|
|
641
|
+
|
|
642
|
+
if pattern_type in ["architecture", "business_logic", "company_process"]:
|
|
643
|
+
return Classification.INTERNAL
|
|
644
|
+
|
|
645
|
+
# Default to PUBLIC for general patterns
|
|
646
|
+
return Classification.PUBLIC
|
|
647
|
+
|
|
648
|
+
def _check_access(
|
|
649
|
+
self,
|
|
650
|
+
user_id: str,
|
|
651
|
+
classification: Classification,
|
|
652
|
+
metadata: dict[str, Any],
|
|
653
|
+
) -> bool:
|
|
654
|
+
"""Check if user has access to pattern based on classification.
|
|
655
|
+
|
|
656
|
+
Access rules:
|
|
657
|
+
- PUBLIC: All users
|
|
658
|
+
- INTERNAL: Users on project team (simplified: always granted for demo)
|
|
659
|
+
- SENSITIVE: Explicit permission required (simplified: creator only)
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
user_id: User requesting access
|
|
663
|
+
classification: Pattern classification
|
|
664
|
+
metadata: Pattern metadata
|
|
665
|
+
|
|
666
|
+
Returns:
|
|
667
|
+
True if access granted, False otherwise
|
|
668
|
+
|
|
669
|
+
"""
|
|
670
|
+
# PUBLIC: Everyone has access
|
|
671
|
+
if classification == Classification.PUBLIC:
|
|
672
|
+
return True
|
|
673
|
+
|
|
674
|
+
# INTERNAL: Check project team membership
|
|
675
|
+
# Simplified: Grant access (production would check team membership)
|
|
676
|
+
if classification == Classification.INTERNAL:
|
|
677
|
+
logger.debug("internal_access_check", user_id=user_id, granted=True)
|
|
678
|
+
return True
|
|
679
|
+
|
|
680
|
+
# SENSITIVE: Require explicit permission
|
|
681
|
+
# Simplified: Only pattern creator has access
|
|
682
|
+
if classification == Classification.SENSITIVE:
|
|
683
|
+
created_by = str(metadata.get("created_by", ""))
|
|
684
|
+
granted = user_id == created_by
|
|
685
|
+
|
|
686
|
+
logger.debug(
|
|
687
|
+
"sensitive_access_check",
|
|
688
|
+
user_id=user_id,
|
|
689
|
+
created_by=created_by,
|
|
690
|
+
granted=granted,
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
return bool(granted)
|
|
694
|
+
|
|
695
|
+
# Default deny
|
|
696
|
+
return False
|
|
697
|
+
|
|
698
|
+
def _generate_pattern_id(self, user_id: str, pattern_type: str) -> str:
|
|
699
|
+
"""Generate unique pattern ID.
|
|
700
|
+
|
|
701
|
+
Format: pat_{timestamp}_{hash}
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
user_id: User creating the pattern
|
|
705
|
+
pattern_type: Type of pattern
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
Unique pattern identifier
|
|
709
|
+
|
|
710
|
+
"""
|
|
711
|
+
timestamp = datetime.utcnow().strftime("%Y%m%d%H%M%S")
|
|
712
|
+
|
|
713
|
+
# Create hash from user_id, pattern_type, and random component
|
|
714
|
+
hash_input = f"{user_id}:{pattern_type}:{timestamp}:{os.urandom(8).hex()}"
|
|
715
|
+
hash_digest = hashlib.sha256(hash_input.encode()).hexdigest()[:12]
|
|
716
|
+
|
|
717
|
+
return f"pat_{timestamp}_{hash_digest}"
|
|
718
|
+
|
|
719
|
+
def list_patterns(
|
|
720
|
+
self,
|
|
721
|
+
user_id: str,
|
|
722
|
+
classification: Classification | None = None,
|
|
723
|
+
pattern_type: str | None = None,
|
|
724
|
+
) -> list[dict[str, Any]]:
|
|
725
|
+
"""List patterns accessible to user.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
user_id: User listing patterns
|
|
729
|
+
classification: Filter by classification
|
|
730
|
+
pattern_type: Filter by pattern type
|
|
731
|
+
|
|
732
|
+
Returns:
|
|
733
|
+
List of pattern summaries
|
|
734
|
+
|
|
735
|
+
"""
|
|
736
|
+
all_pattern_ids = self.storage.list_patterns()
|
|
737
|
+
accessible_patterns = []
|
|
738
|
+
|
|
739
|
+
for pattern_id in all_pattern_ids:
|
|
740
|
+
try:
|
|
741
|
+
pattern_data = self.storage.retrieve(pattern_id)
|
|
742
|
+
if not pattern_data:
|
|
743
|
+
continue
|
|
744
|
+
|
|
745
|
+
metadata = pattern_data["metadata"]
|
|
746
|
+
pat_classification = Classification[metadata["classification"]]
|
|
747
|
+
|
|
748
|
+
# Apply filters
|
|
749
|
+
if classification and pat_classification != classification:
|
|
750
|
+
continue
|
|
751
|
+
|
|
752
|
+
if pattern_type and metadata.get("pattern_type") != pattern_type:
|
|
753
|
+
continue
|
|
754
|
+
|
|
755
|
+
# Check access
|
|
756
|
+
if self._check_access(user_id, pat_classification, metadata):
|
|
757
|
+
accessible_patterns.append(
|
|
758
|
+
{
|
|
759
|
+
"pattern_id": pattern_id,
|
|
760
|
+
"pattern_type": metadata.get("pattern_type"),
|
|
761
|
+
"classification": metadata["classification"],
|
|
762
|
+
"created_by": metadata.get("created_by"),
|
|
763
|
+
"created_at": metadata.get("created_at"),
|
|
764
|
+
"encrypted": metadata.get("encrypted", False),
|
|
765
|
+
},
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
except Exception as e:
|
|
769
|
+
logger.warning(
|
|
770
|
+
"failed_to_load_pattern_metadata",
|
|
771
|
+
pattern_id=pattern_id,
|
|
772
|
+
error=str(e),
|
|
773
|
+
)
|
|
774
|
+
continue
|
|
775
|
+
|
|
776
|
+
return accessible_patterns
|
|
777
|
+
|
|
778
|
+
def delete_pattern(self, pattern_id: str, user_id: str, session_id: str = "") -> bool:
|
|
779
|
+
"""Delete a pattern (with access control).
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
pattern_id: Pattern to delete
|
|
783
|
+
user_id: User requesting deletion
|
|
784
|
+
session_id: Session identifier
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
True if deleted successfully
|
|
788
|
+
|
|
789
|
+
Raises:
|
|
790
|
+
PermissionError: If user doesn't have permission to delete
|
|
791
|
+
ValueError: If pattern_id or user_id is empty
|
|
792
|
+
|
|
793
|
+
"""
|
|
794
|
+
# Pattern 1: String ID validation
|
|
795
|
+
if not pattern_id or not pattern_id.strip():
|
|
796
|
+
raise ValueError(f"pattern_id cannot be empty. Got: {pattern_id!r}")
|
|
797
|
+
if not user_id or not user_id.strip():
|
|
798
|
+
raise ValueError("user_id cannot be empty")
|
|
799
|
+
|
|
800
|
+
# Retrieve pattern to check permissions
|
|
801
|
+
pattern_data = self.storage.retrieve(pattern_id)
|
|
802
|
+
|
|
803
|
+
if not pattern_data:
|
|
804
|
+
logger.warning("pattern_not_found_for_deletion", pattern_id=pattern_id)
|
|
805
|
+
return False
|
|
806
|
+
|
|
807
|
+
metadata = pattern_data["metadata"]
|
|
808
|
+
|
|
809
|
+
# Only creator can delete (simplified access control)
|
|
810
|
+
if metadata.get("created_by") != user_id:
|
|
811
|
+
logger.warning(
|
|
812
|
+
"delete_permission_denied",
|
|
813
|
+
pattern_id=pattern_id,
|
|
814
|
+
user_id=user_id,
|
|
815
|
+
created_by=metadata.get("created_by"),
|
|
816
|
+
)
|
|
817
|
+
raise PermissionError(f"User {user_id} cannot delete pattern {pattern_id}")
|
|
818
|
+
|
|
819
|
+
# Delete pattern
|
|
820
|
+
deleted = self.storage.delete(pattern_id)
|
|
821
|
+
|
|
822
|
+
if deleted:
|
|
823
|
+
# Log deletion
|
|
824
|
+
self.audit_logger._write_event(
|
|
825
|
+
AuditEvent(
|
|
826
|
+
event_type="delete_pattern",
|
|
827
|
+
user_id=user_id,
|
|
828
|
+
session_id=session_id,
|
|
829
|
+
status="success",
|
|
830
|
+
data={
|
|
831
|
+
"pattern_id": pattern_id,
|
|
832
|
+
"classification": metadata["classification"],
|
|
833
|
+
},
|
|
834
|
+
),
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
logger.info("pattern_deleted", pattern_id=pattern_id, user_id=user_id)
|
|
838
|
+
|
|
839
|
+
return deleted
|
|
840
|
+
|
|
841
|
+
def get_statistics(self) -> dict[str, Any]:
|
|
842
|
+
"""Get statistics about stored patterns.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
Dictionary with pattern statistics
|
|
846
|
+
|
|
847
|
+
"""
|
|
848
|
+
all_patterns = self.storage.list_patterns()
|
|
849
|
+
|
|
850
|
+
stats: dict[str, Any] = {
|
|
851
|
+
"total_patterns": len(all_patterns),
|
|
852
|
+
"by_classification": {
|
|
853
|
+
"PUBLIC": 0,
|
|
854
|
+
"INTERNAL": 0,
|
|
855
|
+
"SENSITIVE": 0,
|
|
856
|
+
},
|
|
857
|
+
"encrypted_count": 0,
|
|
858
|
+
"with_pii_scrubbed": 0,
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
for pattern_id in all_patterns:
|
|
862
|
+
try:
|
|
863
|
+
pattern_data = self.storage.retrieve(pattern_id)
|
|
864
|
+
if not pattern_data:
|
|
865
|
+
continue
|
|
866
|
+
|
|
867
|
+
metadata = pattern_data["metadata"]
|
|
868
|
+
classification = metadata.get("classification", "INTERNAL")
|
|
869
|
+
|
|
870
|
+
stats["by_classification"][classification] += 1
|
|
871
|
+
|
|
872
|
+
if metadata.get("encrypted", False):
|
|
873
|
+
stats["encrypted_count"] += 1
|
|
874
|
+
|
|
875
|
+
if metadata.get("pii_removed", 0) > 0:
|
|
876
|
+
stats["with_pii_scrubbed"] += 1
|
|
877
|
+
|
|
878
|
+
except Exception:
|
|
879
|
+
continue
|
|
880
|
+
|
|
881
|
+
return stats
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
# ============================================================================
|
|
885
|
+
# Simplified Long-Term Memory Interface
|
|
886
|
+
# ============================================================================
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
# ============================================================================
|
|
892
|
+
# Backward Compatibility Exports
|
|
893
|
+
# ============================================================================
|
|
894
|
+
|
|
895
|
+
__all__ = [
|
|
896
|
+
# Types (from long_term_types.py)
|
|
897
|
+
"Classification",
|
|
898
|
+
"ClassificationRules",
|
|
899
|
+
"DEFAULT_CLASSIFICATION_RULES",
|
|
900
|
+
"PatternMetadata",
|
|
901
|
+
"SecurePattern",
|
|
902
|
+
"SecurityError",
|
|
903
|
+
"PermissionError",
|
|
904
|
+
# Encryption (from encryption.py)
|
|
905
|
+
"EncryptionManager",
|
|
906
|
+
"HAS_ENCRYPTION",
|
|
907
|
+
# Storage (from storage_backend.py)
|
|
908
|
+
"MemDocsStorage",
|
|
909
|
+
# Simple storage (from simple_storage.py)
|
|
910
|
+
"LongTermMemory",
|
|
911
|
+
# Main integration (defined in this file)
|
|
912
|
+
"SecureMemDocsIntegration",
|
|
913
|
+
]
|