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,932 @@
|
|
|
1
|
+
"""Audit Logging Framework for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Comprehensive audit logging for SOC2, HIPAA, and GDPR compliance.
|
|
4
|
+
Implements tamper-evident, append-only logging with structured JSON format.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- JSON Lines format (one event per line)
|
|
8
|
+
- ISO-8601 timestamps (UTC)
|
|
9
|
+
- Unique event IDs (UUID)
|
|
10
|
+
- Tamper-evident (append-only)
|
|
11
|
+
- Query/search capability
|
|
12
|
+
- Log rotation support
|
|
13
|
+
|
|
14
|
+
Reference:
|
|
15
|
+
- SECURE_MEMORY_ARCHITECTURE.md: Audit Trail Implementation
|
|
16
|
+
- SOC2 CC7.2: System Monitoring
|
|
17
|
+
- HIPAA 164.312(b): Audit Controls
|
|
18
|
+
- GDPR Article 30: Records of Processing
|
|
19
|
+
|
|
20
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
21
|
+
Licensed under Fair Source 0.9
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import uuid
|
|
28
|
+
from dataclasses import asdict, dataclass, field
|
|
29
|
+
from datetime import datetime, timedelta
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AuditEvent:
|
|
38
|
+
"""Represents a single audit event.
|
|
39
|
+
|
|
40
|
+
All audit events share these core fields for compliance tracking.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Core identification
|
|
44
|
+
event_id: str = field(default_factory=lambda: f"evt_{uuid.uuid4().hex[:12]}")
|
|
45
|
+
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
|
|
46
|
+
version: str = "1.0"
|
|
47
|
+
|
|
48
|
+
# Event classification
|
|
49
|
+
event_type: str = "" # llm_request, store_pattern, retrieve_pattern, security_violation
|
|
50
|
+
user_id: str = ""
|
|
51
|
+
session_id: str = ""
|
|
52
|
+
|
|
53
|
+
# Status tracking
|
|
54
|
+
status: str = "success" # success, failed, blocked
|
|
55
|
+
error: str = ""
|
|
56
|
+
|
|
57
|
+
# Custom fields (populated by specific event types)
|
|
58
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> dict[str, Any]:
|
|
61
|
+
"""Convert to dictionary for JSON serialization"""
|
|
62
|
+
result = asdict(self)
|
|
63
|
+
# Flatten data dict into top level for easier querying
|
|
64
|
+
data = result.pop("data", {})
|
|
65
|
+
result.update(data)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class SecurityViolation:
|
|
71
|
+
"""Represents a security policy violation.
|
|
72
|
+
|
|
73
|
+
Used for tracking and alerting on security issues.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
violation_type: str # secrets_detected, pii_in_storage, classification_error, etc.
|
|
77
|
+
severity: str # LOW, MEDIUM, HIGH, CRITICAL
|
|
78
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
79
|
+
user_notified: bool = False
|
|
80
|
+
manager_notified: bool = False
|
|
81
|
+
security_team_notified: bool = False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AuditLogger:
|
|
85
|
+
"""Comprehensive audit logging for Empathy Framework.
|
|
86
|
+
|
|
87
|
+
Implements SOC2, HIPAA, and GDPR compliant audit trails with:
|
|
88
|
+
- Tamper-evident append-only logging
|
|
89
|
+
- Structured JSON Lines format
|
|
90
|
+
- Comprehensive event tracking
|
|
91
|
+
- Query and search capabilities
|
|
92
|
+
- Log rotation support
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> logger = AuditLogger() # Uses platform-appropriate default
|
|
96
|
+
>>> logger.log_llm_request(
|
|
97
|
+
... user_id="user@company.com",
|
|
98
|
+
... empathy_level=3,
|
|
99
|
+
... provider="anthropic",
|
|
100
|
+
... model="claude-sonnet-4",
|
|
101
|
+
... memory_sources=["enterprise", "user", "project"],
|
|
102
|
+
... pii_count=0,
|
|
103
|
+
... secrets_count=0
|
|
104
|
+
... )
|
|
105
|
+
|
|
106
|
+
Log Format:
|
|
107
|
+
Each line is a complete JSON object representing one event.
|
|
108
|
+
Format: JSON Lines (.jsonl) - one event per line, append-only.
|
|
109
|
+
|
|
110
|
+
Compliance:
|
|
111
|
+
- SOC2 CC7.2: System Monitoring and Logging
|
|
112
|
+
- HIPAA 164.312(b): Audit Controls
|
|
113
|
+
- GDPR Article 30: Records of Processing Activities
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
log_dir: str | None = None, # Uses platform-appropriate default if None
|
|
120
|
+
log_filename: str = "audit.jsonl",
|
|
121
|
+
max_file_size_mb: int = 100,
|
|
122
|
+
retention_days: int = 365,
|
|
123
|
+
enable_rotation: bool = True,
|
|
124
|
+
enable_console_logging: bool = False,
|
|
125
|
+
):
|
|
126
|
+
"""Initialize the audit logger.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
log_dir: Directory for audit logs
|
|
130
|
+
log_filename: Name of the audit log file
|
|
131
|
+
max_file_size_mb: Maximum file size before rotation (if enabled)
|
|
132
|
+
retention_days: Number of days to retain audit logs
|
|
133
|
+
enable_rotation: Whether to enable automatic log rotation
|
|
134
|
+
enable_console_logging: Whether to also log to console (for development)
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
# Use platform-appropriate default if log_dir not specified
|
|
138
|
+
if log_dir is None:
|
|
139
|
+
from attune.platform_utils import get_default_log_dir
|
|
140
|
+
|
|
141
|
+
self.log_dir = get_default_log_dir()
|
|
142
|
+
else:
|
|
143
|
+
self.log_dir = Path(log_dir)
|
|
144
|
+
self.log_filename = log_filename
|
|
145
|
+
self.log_path = self.log_dir / log_filename
|
|
146
|
+
self.max_file_size_bytes = max_file_size_mb * 1024 * 1024
|
|
147
|
+
self.retention_days = retention_days
|
|
148
|
+
self.enable_rotation = enable_rotation
|
|
149
|
+
self.enable_console_logging = enable_console_logging
|
|
150
|
+
|
|
151
|
+
# Track security violations for alerting
|
|
152
|
+
self._violation_counts: dict[str, int] = {}
|
|
153
|
+
|
|
154
|
+
# Initialize log directory
|
|
155
|
+
self._initialize_log_directory()
|
|
156
|
+
|
|
157
|
+
def _initialize_log_directory(self):
|
|
158
|
+
"""Create log directory if it doesn't exist"""
|
|
159
|
+
try:
|
|
160
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
# Set restrictive permissions (owner read/write only)
|
|
162
|
+
os.chmod(self.log_dir, 0o700)
|
|
163
|
+
logger.info(f"Audit log directory initialized: {self.log_dir}")
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Failed to initialize audit log directory: {e}")
|
|
166
|
+
# Fallback to local directory
|
|
167
|
+
self.log_dir = Path("./logs")
|
|
168
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
self.log_path = self.log_dir / self.log_filename
|
|
170
|
+
logger.warning(f"Using fallback log directory: {self.log_dir}")
|
|
171
|
+
|
|
172
|
+
def _write_event(self, event: AuditEvent):
|
|
173
|
+
"""Write an audit event to the log file.
|
|
174
|
+
|
|
175
|
+
Uses append-only mode for tamper-evidence.
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
# Check if rotation is needed
|
|
179
|
+
if self.enable_rotation and self.log_path.exists():
|
|
180
|
+
if self.log_path.stat().st_size > self.max_file_size_bytes:
|
|
181
|
+
self._rotate_log()
|
|
182
|
+
|
|
183
|
+
# Write event as single line JSON
|
|
184
|
+
with open(self.log_path, "a", encoding="utf-8") as f:
|
|
185
|
+
json.dump(event.to_dict(), f, ensure_ascii=False)
|
|
186
|
+
f.write("\n")
|
|
187
|
+
|
|
188
|
+
# Optional console logging for development
|
|
189
|
+
if self.enable_console_logging:
|
|
190
|
+
logger.debug(f"Audit event: {event.event_type} - {event.status}")
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.error(f"Failed to write audit event: {e}")
|
|
194
|
+
# Critical: audit logging failure should be visible
|
|
195
|
+
if self.enable_console_logging:
|
|
196
|
+
print(f"AUDIT LOG FAILURE: {e}", flush=True)
|
|
197
|
+
|
|
198
|
+
def _rotate_log(self):
|
|
199
|
+
"""Rotate the audit log file.
|
|
200
|
+
|
|
201
|
+
Renames current log with timestamp and creates new file.
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
|
205
|
+
rotated_name = f"{self.log_filename}.{timestamp}"
|
|
206
|
+
rotated_path = self.log_dir / rotated_name
|
|
207
|
+
|
|
208
|
+
self.log_path.rename(rotated_path)
|
|
209
|
+
logger.info(f"Audit log rotated: {rotated_path}")
|
|
210
|
+
|
|
211
|
+
# Clean up old logs beyond retention period
|
|
212
|
+
self._cleanup_old_logs()
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Failed to rotate audit log: {e}")
|
|
216
|
+
|
|
217
|
+
def _cleanup_old_logs(self):
|
|
218
|
+
"""Remove audit logs older than retention period"""
|
|
219
|
+
try:
|
|
220
|
+
cutoff_date = datetime.utcnow() - timedelta(days=self.retention_days)
|
|
221
|
+
|
|
222
|
+
for log_file in self.log_dir.glob(f"{self.log_filename}.*"):
|
|
223
|
+
# Extract timestamp from filename
|
|
224
|
+
try:
|
|
225
|
+
timestamp_str = log_file.suffix[1:] # Remove leading dot
|
|
226
|
+
file_date = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
|
227
|
+
|
|
228
|
+
if file_date < cutoff_date:
|
|
229
|
+
log_file.unlink()
|
|
230
|
+
logger.info(f"Removed old audit log: {log_file}")
|
|
231
|
+
except (ValueError, IndexError):
|
|
232
|
+
# Skip files that don't match expected format
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Failed to cleanup old audit logs: {e}")
|
|
237
|
+
|
|
238
|
+
def log_llm_request(
|
|
239
|
+
self,
|
|
240
|
+
user_id: str,
|
|
241
|
+
empathy_level: int,
|
|
242
|
+
provider: str,
|
|
243
|
+
model: str,
|
|
244
|
+
memory_sources: list[str],
|
|
245
|
+
pii_count: int = 0,
|
|
246
|
+
secrets_count: int = 0,
|
|
247
|
+
request_size_bytes: int = 0,
|
|
248
|
+
response_size_bytes: int = 0,
|
|
249
|
+
duration_ms: int = 0,
|
|
250
|
+
memdocs_patterns_used: list[str] | None = None,
|
|
251
|
+
sanitization_applied: bool = True,
|
|
252
|
+
classification_verified: bool = True,
|
|
253
|
+
session_id: str = "",
|
|
254
|
+
ip_address: str = "",
|
|
255
|
+
temperature: float = 0.7,
|
|
256
|
+
status: str = "success",
|
|
257
|
+
error: str = "",
|
|
258
|
+
**kwargs,
|
|
259
|
+
):
|
|
260
|
+
"""Log an LLM API request.
|
|
261
|
+
|
|
262
|
+
Tracks all LLM interactions for compliance and monitoring.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
user_id: User or service account making the request
|
|
266
|
+
empathy_level: Empathy level (1-5) used for this request
|
|
267
|
+
provider: LLM provider (anthropic, openai, local)
|
|
268
|
+
model: Specific model used
|
|
269
|
+
memory_sources: Which memory sources were loaded (enterprise, user, project)
|
|
270
|
+
pii_count: Number of PII items detected (not the items themselves)
|
|
271
|
+
secrets_count: Number of secrets detected
|
|
272
|
+
request_size_bytes: Size of the request payload
|
|
273
|
+
response_size_bytes: Size of the response payload
|
|
274
|
+
duration_ms: Request duration in milliseconds
|
|
275
|
+
memdocs_patterns_used: List of MemDocs pattern IDs used
|
|
276
|
+
sanitization_applied: Whether PII sanitization was applied
|
|
277
|
+
classification_verified: Whether data classification was verified
|
|
278
|
+
session_id: Session identifier
|
|
279
|
+
ip_address: Anonymized IP address (e.g., first 3 octets only)
|
|
280
|
+
temperature: LLM temperature setting
|
|
281
|
+
status: success, failed, or blocked
|
|
282
|
+
error: Error message if failed
|
|
283
|
+
**kwargs: Additional custom fields
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> logger.log_llm_request(
|
|
287
|
+
... user_id="user@company.com",
|
|
288
|
+
... empathy_level=3,
|
|
289
|
+
... provider="anthropic",
|
|
290
|
+
... model="claude-sonnet-4",
|
|
291
|
+
... memory_sources=["enterprise", "user"],
|
|
292
|
+
... pii_count=0,
|
|
293
|
+
... secrets_count=0
|
|
294
|
+
... )
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
event = AuditEvent(
|
|
298
|
+
event_type="llm_request",
|
|
299
|
+
user_id=user_id,
|
|
300
|
+
session_id=session_id,
|
|
301
|
+
status=status,
|
|
302
|
+
error=error,
|
|
303
|
+
data={
|
|
304
|
+
"llm": {
|
|
305
|
+
"provider": provider,
|
|
306
|
+
"model": model,
|
|
307
|
+
"empathy_level": empathy_level,
|
|
308
|
+
"temperature": temperature,
|
|
309
|
+
},
|
|
310
|
+
"memory": {
|
|
311
|
+
"sources": memory_sources,
|
|
312
|
+
"total_sources": len(memory_sources),
|
|
313
|
+
"security_policies_applied": "enterprise" in memory_sources,
|
|
314
|
+
},
|
|
315
|
+
"memdocs": {
|
|
316
|
+
"patterns_used": memdocs_patterns_used or [],
|
|
317
|
+
"pattern_count": len(memdocs_patterns_used or []),
|
|
318
|
+
},
|
|
319
|
+
"security": {
|
|
320
|
+
"pii_detected": pii_count,
|
|
321
|
+
"secrets_detected": secrets_count,
|
|
322
|
+
"sanitization_applied": sanitization_applied,
|
|
323
|
+
"classification_verified": classification_verified,
|
|
324
|
+
},
|
|
325
|
+
"request": {
|
|
326
|
+
"size_bytes": request_size_bytes,
|
|
327
|
+
"duration_ms": duration_ms,
|
|
328
|
+
"ip_address": ip_address,
|
|
329
|
+
},
|
|
330
|
+
"response": {
|
|
331
|
+
"size_bytes": response_size_bytes,
|
|
332
|
+
},
|
|
333
|
+
"compliance": {
|
|
334
|
+
"gdpr_compliant": pii_count == 0 or sanitization_applied,
|
|
335
|
+
"hipaa_compliant": secrets_count == 0 and sanitization_applied,
|
|
336
|
+
"soc2_compliant": True,
|
|
337
|
+
},
|
|
338
|
+
**kwargs,
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
self._write_event(event)
|
|
343
|
+
|
|
344
|
+
# Check for security violations
|
|
345
|
+
if secrets_count > 0:
|
|
346
|
+
self._handle_security_violation(
|
|
347
|
+
user_id=user_id,
|
|
348
|
+
violation_type="secrets_detected",
|
|
349
|
+
severity="HIGH",
|
|
350
|
+
details={"secrets_count": secrets_count, "event_type": "llm_request"},
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def log_pattern_store(
|
|
354
|
+
self,
|
|
355
|
+
user_id: str,
|
|
356
|
+
pattern_id: str,
|
|
357
|
+
pattern_type: str,
|
|
358
|
+
classification: str,
|
|
359
|
+
pii_scrubbed: int = 0,
|
|
360
|
+
secrets_detected: int = 0,
|
|
361
|
+
retention_days: int = 180,
|
|
362
|
+
encrypted: bool = False,
|
|
363
|
+
session_id: str = "",
|
|
364
|
+
status: str = "success",
|
|
365
|
+
error: str = "",
|
|
366
|
+
**kwargs,
|
|
367
|
+
):
|
|
368
|
+
"""Log MemDocs pattern storage.
|
|
369
|
+
|
|
370
|
+
Tracks pattern creation for compliance and data governance.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
user_id: User storing the pattern
|
|
374
|
+
pattern_id: Unique identifier for the pattern
|
|
375
|
+
pattern_type: Type of pattern (code, architecture, workflow, etc.)
|
|
376
|
+
classification: PUBLIC, INTERNAL, or SENSITIVE
|
|
377
|
+
pii_scrubbed: Number of PII items scrubbed before storage
|
|
378
|
+
secrets_detected: Number of secrets found (should be 0 for storage)
|
|
379
|
+
retention_days: Retention period in days
|
|
380
|
+
encrypted: Whether pattern is encrypted at rest
|
|
381
|
+
session_id: Session identifier
|
|
382
|
+
status: success, failed, or blocked
|
|
383
|
+
error: Error message if failed
|
|
384
|
+
**kwargs: Additional custom fields
|
|
385
|
+
|
|
386
|
+
Example:
|
|
387
|
+
>>> logger.log_pattern_store(
|
|
388
|
+
... user_id="user@company.com",
|
|
389
|
+
... pattern_id="pattern_abc123",
|
|
390
|
+
... pattern_type="architecture",
|
|
391
|
+
... classification="INTERNAL",
|
|
392
|
+
... pii_scrubbed=2,
|
|
393
|
+
... retention_days=180
|
|
394
|
+
... )
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
event = AuditEvent(
|
|
398
|
+
event_type="store_pattern",
|
|
399
|
+
user_id=user_id,
|
|
400
|
+
session_id=session_id,
|
|
401
|
+
status=status,
|
|
402
|
+
error=error,
|
|
403
|
+
data={
|
|
404
|
+
"pattern": {
|
|
405
|
+
"pattern_id": pattern_id,
|
|
406
|
+
"pattern_type": pattern_type,
|
|
407
|
+
"classification": classification,
|
|
408
|
+
"encrypted": encrypted,
|
|
409
|
+
"retention_days": retention_days,
|
|
410
|
+
},
|
|
411
|
+
"security": {
|
|
412
|
+
"pii_scrubbed": pii_scrubbed,
|
|
413
|
+
"secrets_detected": secrets_detected,
|
|
414
|
+
"sanitization_applied": pii_scrubbed > 0,
|
|
415
|
+
},
|
|
416
|
+
"compliance": {
|
|
417
|
+
"gdpr_compliant": secrets_detected == 0,
|
|
418
|
+
"hipaa_compliant": (classification == "SENSITIVE" and encrypted)
|
|
419
|
+
or classification != "SENSITIVE",
|
|
420
|
+
"soc2_compliant": secrets_detected == 0
|
|
421
|
+
and classification in ["PUBLIC", "INTERNAL", "SENSITIVE"],
|
|
422
|
+
"classification_verified": classification
|
|
423
|
+
in ["PUBLIC", "INTERNAL", "SENSITIVE"],
|
|
424
|
+
},
|
|
425
|
+
**kwargs,
|
|
426
|
+
},
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
self._write_event(event)
|
|
430
|
+
|
|
431
|
+
# Check for security violations
|
|
432
|
+
if secrets_detected > 0:
|
|
433
|
+
self._handle_security_violation(
|
|
434
|
+
user_id=user_id,
|
|
435
|
+
violation_type="secrets_in_storage",
|
|
436
|
+
severity="CRITICAL",
|
|
437
|
+
details={
|
|
438
|
+
"secrets_detected": secrets_detected,
|
|
439
|
+
"pattern_id": pattern_id,
|
|
440
|
+
"event_type": "store_pattern",
|
|
441
|
+
},
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
if classification == "SENSITIVE" and not encrypted:
|
|
445
|
+
self._handle_security_violation(
|
|
446
|
+
user_id=user_id,
|
|
447
|
+
violation_type="sensitive_not_encrypted",
|
|
448
|
+
severity="HIGH",
|
|
449
|
+
details={
|
|
450
|
+
"pattern_id": pattern_id,
|
|
451
|
+
"classification": classification,
|
|
452
|
+
"event_type": "store_pattern",
|
|
453
|
+
},
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
def log_pattern_retrieve(
|
|
457
|
+
self,
|
|
458
|
+
user_id: str,
|
|
459
|
+
pattern_id: str,
|
|
460
|
+
classification: str,
|
|
461
|
+
access_granted: bool = True,
|
|
462
|
+
permission_level: str = "",
|
|
463
|
+
session_id: str = "",
|
|
464
|
+
status: str = "success",
|
|
465
|
+
error: str = "",
|
|
466
|
+
**kwargs,
|
|
467
|
+
):
|
|
468
|
+
"""Log MemDocs pattern retrieval.
|
|
469
|
+
|
|
470
|
+
Tracks pattern access for compliance and security monitoring.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
user_id: User retrieving the pattern
|
|
474
|
+
pattern_id: Unique identifier for the pattern
|
|
475
|
+
classification: PUBLIC, INTERNAL, or SENSITIVE
|
|
476
|
+
access_granted: Whether access was granted
|
|
477
|
+
permission_level: Permission level used for access decision
|
|
478
|
+
session_id: Session identifier
|
|
479
|
+
status: success, failed, or blocked
|
|
480
|
+
error: Error message if failed
|
|
481
|
+
**kwargs: Additional custom fields
|
|
482
|
+
|
|
483
|
+
Example:
|
|
484
|
+
>>> logger.log_pattern_retrieve(
|
|
485
|
+
... user_id="user@company.com",
|
|
486
|
+
... pattern_id="pattern_abc123",
|
|
487
|
+
... classification="SENSITIVE",
|
|
488
|
+
... access_granted=True,
|
|
489
|
+
... permission_level="explicit"
|
|
490
|
+
... )
|
|
491
|
+
|
|
492
|
+
"""
|
|
493
|
+
event = AuditEvent(
|
|
494
|
+
event_type="retrieve_pattern",
|
|
495
|
+
user_id=user_id,
|
|
496
|
+
session_id=session_id,
|
|
497
|
+
status="success" if access_granted else "blocked",
|
|
498
|
+
error=error,
|
|
499
|
+
data={
|
|
500
|
+
"pattern": {
|
|
501
|
+
"pattern_id": pattern_id,
|
|
502
|
+
"classification": classification,
|
|
503
|
+
},
|
|
504
|
+
"access": {
|
|
505
|
+
"granted": access_granted,
|
|
506
|
+
"permission_level": permission_level,
|
|
507
|
+
"audit_required": classification == "SENSITIVE",
|
|
508
|
+
},
|
|
509
|
+
"compliance": {
|
|
510
|
+
"access_logged": True,
|
|
511
|
+
"hipaa_compliant": classification == "SENSITIVE",
|
|
512
|
+
},
|
|
513
|
+
**kwargs,
|
|
514
|
+
},
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
self._write_event(event)
|
|
518
|
+
|
|
519
|
+
# Log unauthorized access attempts
|
|
520
|
+
if not access_granted:
|
|
521
|
+
self._handle_security_violation(
|
|
522
|
+
user_id=user_id,
|
|
523
|
+
violation_type="unauthorized_access",
|
|
524
|
+
severity="MEDIUM" if classification == "INTERNAL" else "HIGH",
|
|
525
|
+
details={
|
|
526
|
+
"pattern_id": pattern_id,
|
|
527
|
+
"classification": classification,
|
|
528
|
+
"event_type": "retrieve_pattern",
|
|
529
|
+
},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
def log_security_violation(
|
|
533
|
+
self,
|
|
534
|
+
user_id: str,
|
|
535
|
+
violation_type: str,
|
|
536
|
+
severity: str,
|
|
537
|
+
details: dict[str, Any],
|
|
538
|
+
session_id: str = "",
|
|
539
|
+
blocked: bool = True,
|
|
540
|
+
**kwargs,
|
|
541
|
+
):
|
|
542
|
+
"""Log a security policy violation.
|
|
543
|
+
|
|
544
|
+
Tracks security incidents for monitoring and response.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
user_id: User who triggered the violation
|
|
548
|
+
violation_type: Type of violation (secrets_detected, pii_in_storage, etc.)
|
|
549
|
+
severity: LOW, MEDIUM, HIGH, or CRITICAL
|
|
550
|
+
details: Additional details about the violation
|
|
551
|
+
session_id: Session identifier
|
|
552
|
+
blocked: Whether the action was blocked
|
|
553
|
+
**kwargs: Additional custom fields
|
|
554
|
+
|
|
555
|
+
Example:
|
|
556
|
+
>>> logger.log_security_violation(
|
|
557
|
+
... user_id="user@company.com",
|
|
558
|
+
... violation_type="secrets_detected",
|
|
559
|
+
... severity="HIGH",
|
|
560
|
+
... details={"secret_type": "api_key", "action": "llm_request"}, # pragma: allowlist secret
|
|
561
|
+
... blocked=True
|
|
562
|
+
... )
|
|
563
|
+
|
|
564
|
+
"""
|
|
565
|
+
violation = SecurityViolation(
|
|
566
|
+
violation_type=violation_type,
|
|
567
|
+
severity=severity,
|
|
568
|
+
details=details,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
event = AuditEvent(
|
|
572
|
+
event_type="security_violation",
|
|
573
|
+
user_id=user_id,
|
|
574
|
+
session_id=session_id,
|
|
575
|
+
status="blocked" if blocked else "logged",
|
|
576
|
+
data={
|
|
577
|
+
"violation": {
|
|
578
|
+
"type": violation_type,
|
|
579
|
+
"severity": severity,
|
|
580
|
+
"details": details,
|
|
581
|
+
"blocked": blocked,
|
|
582
|
+
},
|
|
583
|
+
"response": {
|
|
584
|
+
"user_notified": violation.user_notified,
|
|
585
|
+
"manager_notified": violation.manager_notified,
|
|
586
|
+
"security_team_notified": violation.security_team_notified,
|
|
587
|
+
},
|
|
588
|
+
"compliance": {
|
|
589
|
+
"gdpr_compliant": blocked,
|
|
590
|
+
"hipaa_compliant": blocked,
|
|
591
|
+
"soc2_compliant": blocked,
|
|
592
|
+
},
|
|
593
|
+
**kwargs,
|
|
594
|
+
},
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
self._write_event(event)
|
|
598
|
+
|
|
599
|
+
def _handle_security_violation(
|
|
600
|
+
self,
|
|
601
|
+
user_id: str,
|
|
602
|
+
violation_type: str,
|
|
603
|
+
severity: str,
|
|
604
|
+
details: dict[str, Any],
|
|
605
|
+
):
|
|
606
|
+
"""Internal handler for security violations.
|
|
607
|
+
|
|
608
|
+
Tracks violation counts and triggers alerts.
|
|
609
|
+
"""
|
|
610
|
+
# Track violations per user
|
|
611
|
+
key = f"{user_id}:{violation_type}"
|
|
612
|
+
self._violation_counts[key] = self._violation_counts.get(key, 0) + 1
|
|
613
|
+
|
|
614
|
+
# Log the violation
|
|
615
|
+
self.log_security_violation(
|
|
616
|
+
user_id=user_id,
|
|
617
|
+
violation_type=violation_type,
|
|
618
|
+
severity=severity,
|
|
619
|
+
details=details,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
# Alert logic
|
|
623
|
+
count = self._violation_counts[key]
|
|
624
|
+
if severity == "CRITICAL" or count >= 3:
|
|
625
|
+
logger.warning(
|
|
626
|
+
f"Security violation threshold reached: {user_id} - "
|
|
627
|
+
f"{violation_type} (count: {count}, severity: {severity})",
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
def query(
|
|
631
|
+
self,
|
|
632
|
+
event_type: str | None = None,
|
|
633
|
+
user_id: str | None = None,
|
|
634
|
+
status: str | None = None,
|
|
635
|
+
start_date: datetime | None = None,
|
|
636
|
+
end_date: datetime | None = None,
|
|
637
|
+
limit: int = 1000,
|
|
638
|
+
**filters,
|
|
639
|
+
) -> list[dict]:
|
|
640
|
+
"""Query audit logs with filters.
|
|
641
|
+
|
|
642
|
+
Provides search and analysis capabilities for audit data.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
event_type: Filter by event type (llm_request, store_pattern, etc.)
|
|
646
|
+
user_id: Filter by user ID
|
|
647
|
+
status: Filter by status (success, failed, blocked)
|
|
648
|
+
start_date: Filter events after this date
|
|
649
|
+
end_date: Filter events before this date
|
|
650
|
+
limit: Maximum number of events to return
|
|
651
|
+
**filters: Additional key-value filters (supports nested keys with __)
|
|
652
|
+
|
|
653
|
+
Returns:
|
|
654
|
+
List of matching audit events as dictionaries
|
|
655
|
+
|
|
656
|
+
Example:
|
|
657
|
+
>>> # Find all failed LLM requests
|
|
658
|
+
>>> events = logger.query(event_type="llm_request", status="failed")
|
|
659
|
+
>>>
|
|
660
|
+
>>> # Find security violations in last 24 hours
|
|
661
|
+
>>> from datetime import datetime, timedelta
|
|
662
|
+
>>> events = logger.query(
|
|
663
|
+
... event_type="security_violation",
|
|
664
|
+
... start_date=datetime.utcnow() - timedelta(days=1)
|
|
665
|
+
... )
|
|
666
|
+
>>>
|
|
667
|
+
>>> # Find patterns with high PII counts (nested filter)
|
|
668
|
+
>>> events = logger.query(security__pii_detected__gt=5)
|
|
669
|
+
|
|
670
|
+
"""
|
|
671
|
+
results: list[dict[str, object]] = []
|
|
672
|
+
|
|
673
|
+
try:
|
|
674
|
+
if not self.log_path.exists():
|
|
675
|
+
return results
|
|
676
|
+
|
|
677
|
+
with open(self.log_path, encoding="utf-8") as f:
|
|
678
|
+
for line in f:
|
|
679
|
+
if len(results) >= limit:
|
|
680
|
+
break
|
|
681
|
+
|
|
682
|
+
try:
|
|
683
|
+
event = json.loads(line.strip())
|
|
684
|
+
|
|
685
|
+
# Apply filters
|
|
686
|
+
if event_type and event.get("event_type") != event_type:
|
|
687
|
+
continue
|
|
688
|
+
if user_id and event.get("user_id") != user_id:
|
|
689
|
+
continue
|
|
690
|
+
if status and event.get("status") != status:
|
|
691
|
+
continue
|
|
692
|
+
|
|
693
|
+
# Date range filtering
|
|
694
|
+
if start_date or end_date:
|
|
695
|
+
event_time = datetime.fromisoformat(
|
|
696
|
+
event.get("timestamp", "").rstrip("Z"),
|
|
697
|
+
)
|
|
698
|
+
if start_date and event_time < start_date:
|
|
699
|
+
continue
|
|
700
|
+
if end_date and event_time > end_date:
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
# Custom filters (supports nested keys with __)
|
|
704
|
+
if filters and not self._apply_custom_filters(event, filters):
|
|
705
|
+
continue
|
|
706
|
+
|
|
707
|
+
results.append(event)
|
|
708
|
+
|
|
709
|
+
except json.JSONDecodeError:
|
|
710
|
+
logger.warning("Skipping malformed audit log line")
|
|
711
|
+
continue
|
|
712
|
+
|
|
713
|
+
except Exception as e:
|
|
714
|
+
logger.error(f"Failed to query audit logs: {e}")
|
|
715
|
+
|
|
716
|
+
return results
|
|
717
|
+
|
|
718
|
+
def _apply_custom_filters(self, event: dict, filters: dict) -> bool:
|
|
719
|
+
"""Apply custom filters to an event.
|
|
720
|
+
|
|
721
|
+
Supports nested key access with __ separator and comparison operators.
|
|
722
|
+
"""
|
|
723
|
+
for key, value in filters.items():
|
|
724
|
+
# Handle comparison operators (e.g., security__pii_detected__gt=5)
|
|
725
|
+
parts = key.split("__")
|
|
726
|
+
operator = None
|
|
727
|
+
|
|
728
|
+
# Optimization: Use set for O(1) membership testing (vs O(n) with list)
|
|
729
|
+
valid_operators = {"gt", "gte", "lt", "lte", "ne"}
|
|
730
|
+
if len(parts) > 1 and parts[-1] in valid_operators:
|
|
731
|
+
operator = parts[-1]
|
|
732
|
+
parts = parts[:-1]
|
|
733
|
+
|
|
734
|
+
# Navigate nested dictionary
|
|
735
|
+
current = event
|
|
736
|
+
for part in parts:
|
|
737
|
+
if isinstance(current, dict) and part in current:
|
|
738
|
+
current = current[part]
|
|
739
|
+
else:
|
|
740
|
+
return False
|
|
741
|
+
|
|
742
|
+
# Apply comparison
|
|
743
|
+
if (
|
|
744
|
+
operator == "gt" and not (isinstance(current, int | float) and current > value)
|
|
745
|
+
) or (
|
|
746
|
+
operator == "gte" and not (isinstance(current, int | float) and current >= value)
|
|
747
|
+
):
|
|
748
|
+
return False
|
|
749
|
+
if (
|
|
750
|
+
(operator == "lt" and not (isinstance(current, int | float) and current < value))
|
|
751
|
+
or (
|
|
752
|
+
operator == "lte"
|
|
753
|
+
and not (isinstance(current, int | float) and current <= value)
|
|
754
|
+
)
|
|
755
|
+
or (operator == "ne" and current == value)
|
|
756
|
+
or (operator is None and current != value)
|
|
757
|
+
):
|
|
758
|
+
return False
|
|
759
|
+
|
|
760
|
+
return True
|
|
761
|
+
|
|
762
|
+
def get_violation_summary(self, user_id: str | None = None) -> dict[str, Any]:
|
|
763
|
+
"""Get summary of security violations.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
user_id: Optional user ID to filter by
|
|
767
|
+
|
|
768
|
+
Returns:
|
|
769
|
+
Dictionary with violation statistics
|
|
770
|
+
|
|
771
|
+
Example:
|
|
772
|
+
>>> summary = logger.get_violation_summary(user_id="user@company.com")
|
|
773
|
+
>>> print(f"Total violations: {summary['total_violations']}")
|
|
774
|
+
|
|
775
|
+
"""
|
|
776
|
+
violations = self.query(event_type="security_violation", user_id=user_id)
|
|
777
|
+
|
|
778
|
+
by_type: dict[str, int] = {}
|
|
779
|
+
by_severity: dict[str, int] = {}
|
|
780
|
+
by_user: dict[str, int] = {}
|
|
781
|
+
|
|
782
|
+
for violation in violations:
|
|
783
|
+
vtype = str(violation.get("violation", {}).get("type", "unknown"))
|
|
784
|
+
severity = str(violation.get("violation", {}).get("severity", "unknown"))
|
|
785
|
+
vid = str(violation.get("user_id", "unknown"))
|
|
786
|
+
|
|
787
|
+
by_type[vtype] = by_type.get(vtype, 0) + 1
|
|
788
|
+
by_severity[severity] = by_severity.get(severity, 0) + 1
|
|
789
|
+
by_user[vid] = by_user.get(vid, 0) + 1
|
|
790
|
+
|
|
791
|
+
summary: dict[str, int | dict[str, int]] = {
|
|
792
|
+
"total_violations": len(violations),
|
|
793
|
+
"by_type": by_type,
|
|
794
|
+
"by_severity": by_severity,
|
|
795
|
+
"by_user": by_user,
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return summary
|
|
799
|
+
|
|
800
|
+
def get_compliance_report(
|
|
801
|
+
self,
|
|
802
|
+
start_date: datetime | None = None,
|
|
803
|
+
end_date: datetime | None = None,
|
|
804
|
+
) -> dict[str, Any]:
|
|
805
|
+
"""Generate compliance report for audit period.
|
|
806
|
+
|
|
807
|
+
Provides statistics for compliance audits (SOC2, HIPAA, GDPR).
|
|
808
|
+
|
|
809
|
+
Args:
|
|
810
|
+
start_date: Start of audit period
|
|
811
|
+
end_date: End of audit period
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
Dictionary with compliance statistics
|
|
815
|
+
|
|
816
|
+
Example:
|
|
817
|
+
>>> from datetime import datetime, timedelta
|
|
818
|
+
>>> report = logger.get_compliance_report(
|
|
819
|
+
... start_date=datetime.utcnow() - timedelta(days=30)
|
|
820
|
+
... )
|
|
821
|
+
>>> print(f"Total LLM requests: {report['llm_requests']['total']}")
|
|
822
|
+
|
|
823
|
+
"""
|
|
824
|
+
# Query all events in period
|
|
825
|
+
all_events = self.query(start_date=start_date, end_date=end_date, limit=100000)
|
|
826
|
+
|
|
827
|
+
report: dict[str, Any] = {
|
|
828
|
+
"period": {
|
|
829
|
+
"start": start_date.isoformat() if start_date else "all_time",
|
|
830
|
+
"end": end_date.isoformat() if end_date else "now",
|
|
831
|
+
},
|
|
832
|
+
"llm_requests": {
|
|
833
|
+
"total": 0,
|
|
834
|
+
"with_pii_detected": 0,
|
|
835
|
+
"with_secrets_detected": 0,
|
|
836
|
+
"sanitization_applied": 0,
|
|
837
|
+
},
|
|
838
|
+
"pattern_storage": {
|
|
839
|
+
"total": 0,
|
|
840
|
+
"by_classification": {"PUBLIC": 0, "INTERNAL": 0, "SENSITIVE": 0},
|
|
841
|
+
"with_pii_scrubbed": 0,
|
|
842
|
+
"encrypted": 0,
|
|
843
|
+
},
|
|
844
|
+
"pattern_retrieval": {
|
|
845
|
+
"total": 0,
|
|
846
|
+
"by_classification": {"PUBLIC": 0, "INTERNAL": 0, "SENSITIVE": 0},
|
|
847
|
+
"access_denied": 0,
|
|
848
|
+
},
|
|
849
|
+
"security_violations": {"total": 0, "by_severity": {}, "by_type": {}},
|
|
850
|
+
"compliance_metrics": {
|
|
851
|
+
"gdpr_compliant_rate": 0.0,
|
|
852
|
+
"hipaa_compliant_rate": 0.0,
|
|
853
|
+
"soc2_compliant_rate": 0.0,
|
|
854
|
+
},
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
total_compliance_checks = 0
|
|
858
|
+
gdpr_compliant = 0
|
|
859
|
+
hipaa_compliant = 0
|
|
860
|
+
soc2_compliant = 0
|
|
861
|
+
|
|
862
|
+
for event in all_events:
|
|
863
|
+
event_type = event.get("event_type")
|
|
864
|
+
|
|
865
|
+
if event_type == "llm_request":
|
|
866
|
+
report["llm_requests"]["total"] += 1
|
|
867
|
+
security = event.get("security", {})
|
|
868
|
+
if security.get("pii_detected", 0) > 0:
|
|
869
|
+
report["llm_requests"]["with_pii_detected"] += 1
|
|
870
|
+
if security.get("secrets_detected", 0) > 0:
|
|
871
|
+
report["llm_requests"]["with_secrets_detected"] += 1
|
|
872
|
+
if security.get("sanitization_applied"):
|
|
873
|
+
report["llm_requests"]["sanitization_applied"] += 1
|
|
874
|
+
|
|
875
|
+
elif event_type == "store_pattern":
|
|
876
|
+
report["pattern_storage"]["total"] += 1
|
|
877
|
+
pattern = event.get("pattern", {})
|
|
878
|
+
classification = pattern.get("classification", "INTERNAL")
|
|
879
|
+
report["pattern_storage"]["by_classification"][classification] = (
|
|
880
|
+
report["pattern_storage"]["by_classification"].get(classification, 0) + 1
|
|
881
|
+
)
|
|
882
|
+
if event.get("security", {}).get("pii_scrubbed", 0) > 0:
|
|
883
|
+
report["pattern_storage"]["with_pii_scrubbed"] += 1
|
|
884
|
+
if pattern.get("encrypted"):
|
|
885
|
+
report["pattern_storage"]["encrypted"] += 1
|
|
886
|
+
|
|
887
|
+
elif event_type == "retrieve_pattern":
|
|
888
|
+
report["pattern_retrieval"]["total"] += 1
|
|
889
|
+
pattern = event.get("pattern", {})
|
|
890
|
+
classification = pattern.get("classification", "INTERNAL")
|
|
891
|
+
report["pattern_retrieval"]["by_classification"][classification] = (
|
|
892
|
+
report["pattern_retrieval"]["by_classification"].get(classification, 0) + 1
|
|
893
|
+
)
|
|
894
|
+
if not event.get("access", {}).get("granted", True):
|
|
895
|
+
report["pattern_retrieval"]["access_denied"] += 1
|
|
896
|
+
|
|
897
|
+
elif event_type == "security_violation":
|
|
898
|
+
report["security_violations"]["total"] += 1
|
|
899
|
+
violation = event.get("violation", {})
|
|
900
|
+
vtype = violation.get("type", "unknown")
|
|
901
|
+
severity = violation.get("severity", "unknown")
|
|
902
|
+
report["security_violations"]["by_type"][vtype] = (
|
|
903
|
+
report["security_violations"]["by_type"].get(vtype, 0) + 1
|
|
904
|
+
)
|
|
905
|
+
report["security_violations"]["by_severity"][severity] = (
|
|
906
|
+
report["security_violations"]["by_severity"].get(severity, 0) + 1
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
# Track compliance rates
|
|
910
|
+
compliance = event.get("compliance", {})
|
|
911
|
+
if compliance:
|
|
912
|
+
total_compliance_checks += 1
|
|
913
|
+
if compliance.get("gdpr_compliant"):
|
|
914
|
+
gdpr_compliant += 1
|
|
915
|
+
if compliance.get("hipaa_compliant"):
|
|
916
|
+
hipaa_compliant += 1
|
|
917
|
+
if compliance.get("soc2_compliant"):
|
|
918
|
+
soc2_compliant += 1
|
|
919
|
+
|
|
920
|
+
# Calculate compliance rates
|
|
921
|
+
if total_compliance_checks > 0:
|
|
922
|
+
report["compliance_metrics"]["gdpr_compliant_rate"] = (
|
|
923
|
+
gdpr_compliant / total_compliance_checks
|
|
924
|
+
)
|
|
925
|
+
report["compliance_metrics"]["hipaa_compliant_rate"] = (
|
|
926
|
+
hipaa_compliant / total_compliance_checks
|
|
927
|
+
)
|
|
928
|
+
report["compliance_metrics"]["soc2_compliant_rate"] = (
|
|
929
|
+
soc2_compliant / total_compliance_checks
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
return report
|