superlocalmemory 2.8.5 → 3.0.0
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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +2 -2
- package/bin/slm.bat +4 -2
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/{install.ps1 → scripts/install.ps1} +36 -4
- package/{install.sh → scripts/install.sh} +14 -13
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1800
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -266
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for ABAC policy engine.
|
|
4
|
-
"""
|
|
5
|
-
import tempfile
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import json
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TestABACEngine:
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
17
|
-
self.policy_path = os.path.join(self.tmp_dir, "abac_policies.json")
|
|
18
|
-
|
|
19
|
-
def teardown_method(self):
|
|
20
|
-
import shutil
|
|
21
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
22
|
-
|
|
23
|
-
def _write_policies(self, policies):
|
|
24
|
-
with open(self.policy_path, "w") as f:
|
|
25
|
-
json.dump(policies, f)
|
|
26
|
-
|
|
27
|
-
def test_creation_no_policy_file(self):
|
|
28
|
-
"""Engine works with no policy file — allow all."""
|
|
29
|
-
from compliance.abac_engine import ABACEngine
|
|
30
|
-
engine = ABACEngine(config_path="/nonexistent/path.json")
|
|
31
|
-
assert engine is not None
|
|
32
|
-
|
|
33
|
-
def test_missing_policy_allows_all(self):
|
|
34
|
-
"""No policy file → all access allowed (backward compat)."""
|
|
35
|
-
from compliance.abac_engine import ABACEngine
|
|
36
|
-
engine = ABACEngine(config_path="/nonexistent/path.json")
|
|
37
|
-
result = engine.evaluate(subject={"agent_id": "user"}, resource={"access_level": "public"}, action="read")
|
|
38
|
-
assert result["allowed"] is True
|
|
39
|
-
|
|
40
|
-
def test_load_policies_from_json(self):
|
|
41
|
-
"""Can load policies from JSON file."""
|
|
42
|
-
from compliance.abac_engine import ABACEngine
|
|
43
|
-
self._write_policies([
|
|
44
|
-
{"name": "deny-private", "effect": "deny", "subjects": {"agent_id": "*"}, "resources": {"access_level": "private"}, "actions": ["read"]}
|
|
45
|
-
])
|
|
46
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
47
|
-
assert len(engine.policies) == 1
|
|
48
|
-
|
|
49
|
-
def test_deny_policy_blocks_access(self):
|
|
50
|
-
"""Deny policy prevents access to matching resources."""
|
|
51
|
-
from compliance.abac_engine import ABACEngine
|
|
52
|
-
self._write_policies([
|
|
53
|
-
{"name": "deny-private", "effect": "deny", "subjects": {"agent_id": "*"}, "resources": {"access_level": "private"}, "actions": ["read"]}
|
|
54
|
-
])
|
|
55
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
56
|
-
result = engine.evaluate(subject={"agent_id": "agent_a"}, resource={"access_level": "private"}, action="read")
|
|
57
|
-
assert result["allowed"] is False
|
|
58
|
-
assert result["policy_name"] == "deny-private"
|
|
59
|
-
|
|
60
|
-
def test_allow_policy_grants_access(self):
|
|
61
|
-
"""Allow policy explicitly permits access."""
|
|
62
|
-
from compliance.abac_engine import ABACEngine
|
|
63
|
-
self._write_policies([
|
|
64
|
-
{"name": "allow-admin", "effect": "allow", "subjects": {"agent_id": "admin"}, "resources": {"access_level": "*"}, "actions": ["read", "write", "delete"]}
|
|
65
|
-
])
|
|
66
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
67
|
-
result = engine.evaluate(subject={"agent_id": "admin"}, resource={"access_level": "private"}, action="write")
|
|
68
|
-
assert result["allowed"] is True
|
|
69
|
-
|
|
70
|
-
def test_subject_matching_specific_agent(self):
|
|
71
|
-
"""Policy matches specific agent_id."""
|
|
72
|
-
from compliance.abac_engine import ABACEngine
|
|
73
|
-
self._write_policies([
|
|
74
|
-
{"name": "deny-untrusted", "effect": "deny", "subjects": {"agent_id": "untrusted_bot"}, "resources": {"access_level": "*"}, "actions": ["read"]}
|
|
75
|
-
])
|
|
76
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
77
|
-
# untrusted_bot denied
|
|
78
|
-
r1 = engine.evaluate(subject={"agent_id": "untrusted_bot"}, resource={"access_level": "public"}, action="read")
|
|
79
|
-
assert r1["allowed"] is False
|
|
80
|
-
# trusted_agent allowed (no matching deny policy)
|
|
81
|
-
r2 = engine.evaluate(subject={"agent_id": "trusted_agent"}, resource={"access_level": "public"}, action="read")
|
|
82
|
-
assert r2["allowed"] is True
|
|
83
|
-
|
|
84
|
-
def test_resource_matching_by_project(self):
|
|
85
|
-
"""Policy matches by project name."""
|
|
86
|
-
from compliance.abac_engine import ABACEngine
|
|
87
|
-
self._write_policies([
|
|
88
|
-
{"name": "deny-secret-project", "effect": "deny", "subjects": {"agent_id": "*"}, "resources": {"project": "secret_project"}, "actions": ["read"]}
|
|
89
|
-
])
|
|
90
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
91
|
-
r1 = engine.evaluate(subject={"agent_id": "user"}, resource={"project": "secret_project"}, action="read")
|
|
92
|
-
assert r1["allowed"] is False
|
|
93
|
-
r2 = engine.evaluate(subject={"agent_id": "user"}, resource={"project": "public_project"}, action="read")
|
|
94
|
-
assert r2["allowed"] is True
|
|
95
|
-
|
|
96
|
-
def test_action_matching(self):
|
|
97
|
-
"""Policy only applies to specified actions."""
|
|
98
|
-
from compliance.abac_engine import ABACEngine
|
|
99
|
-
self._write_policies([
|
|
100
|
-
{"name": "deny-delete", "effect": "deny", "subjects": {"agent_id": "*"}, "resources": {"access_level": "*"}, "actions": ["delete"]}
|
|
101
|
-
])
|
|
102
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
103
|
-
r1 = engine.evaluate(subject={"agent_id": "user"}, resource={"access_level": "public"}, action="delete")
|
|
104
|
-
assert r1["allowed"] is False
|
|
105
|
-
r2 = engine.evaluate(subject={"agent_id": "user"}, resource={"access_level": "public"}, action="read")
|
|
106
|
-
assert r2["allowed"] is True
|
|
107
|
-
|
|
108
|
-
def test_deny_takes_precedence(self):
|
|
109
|
-
"""When both allow and deny match, deny wins."""
|
|
110
|
-
from compliance.abac_engine import ABACEngine
|
|
111
|
-
self._write_policies([
|
|
112
|
-
{"name": "allow-all", "effect": "allow", "subjects": {"agent_id": "*"}, "resources": {"access_level": "*"}, "actions": ["read"]},
|
|
113
|
-
{"name": "deny-private", "effect": "deny", "subjects": {"agent_id": "*"}, "resources": {"access_level": "private"}, "actions": ["read"]}
|
|
114
|
-
])
|
|
115
|
-
engine = ABACEngine(config_path=self.policy_path)
|
|
116
|
-
result = engine.evaluate(subject={"agent_id": "user"}, resource={"access_level": "private"}, action="read")
|
|
117
|
-
assert result["allowed"] is False
|
|
118
|
-
|
|
119
|
-
def test_evaluate_returns_reason(self):
|
|
120
|
-
"""Evaluation result includes reason."""
|
|
121
|
-
from compliance.abac_engine import ABACEngine
|
|
122
|
-
engine = ABACEngine(config_path="/nonexistent/path.json")
|
|
123
|
-
result = engine.evaluate(subject={"agent_id": "user"}, resource={}, action="read")
|
|
124
|
-
assert "reason" in result
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for ABAC enforcement via MCP tool integration.
|
|
4
|
-
"""
|
|
5
|
-
import tempfile
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import json
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TestABACMCPIntegration:
|
|
15
|
-
def setup_method(self):
|
|
16
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
17
|
-
self.db_path = os.path.join(self.tmp_dir, "memory.db")
|
|
18
|
-
|
|
19
|
-
def teardown_method(self):
|
|
20
|
-
import shutil
|
|
21
|
-
|
|
22
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
23
|
-
|
|
24
|
-
def test_middleware_creation(self):
|
|
25
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
26
|
-
|
|
27
|
-
mw = ABACMiddleware(self.db_path)
|
|
28
|
-
assert mw is not None
|
|
29
|
-
|
|
30
|
-
def test_check_read_access_default_allow(self):
|
|
31
|
-
"""Default (no policies) allows all reads."""
|
|
32
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
33
|
-
|
|
34
|
-
mw = ABACMiddleware(self.db_path)
|
|
35
|
-
result = mw.check_access(
|
|
36
|
-
agent_id="any_agent",
|
|
37
|
-
action="read",
|
|
38
|
-
resource={"access_level": "public"},
|
|
39
|
-
)
|
|
40
|
-
assert result["allowed"] is True
|
|
41
|
-
|
|
42
|
-
def test_check_write_access_default_allow(self):
|
|
43
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
44
|
-
|
|
45
|
-
mw = ABACMiddleware(self.db_path)
|
|
46
|
-
result = mw.check_access(
|
|
47
|
-
agent_id="any_agent", action="write", resource={}
|
|
48
|
-
)
|
|
49
|
-
assert result["allowed"] is True
|
|
50
|
-
|
|
51
|
-
def test_check_access_with_deny_policy(self):
|
|
52
|
-
"""Deny policy blocks access when enforced."""
|
|
53
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
54
|
-
|
|
55
|
-
policy_path = os.path.join(self.tmp_dir, "abac_policies.json")
|
|
56
|
-
with open(policy_path, "w") as f:
|
|
57
|
-
json.dump(
|
|
58
|
-
[
|
|
59
|
-
{
|
|
60
|
-
"name": "deny-bots",
|
|
61
|
-
"effect": "deny",
|
|
62
|
-
"subjects": {"agent_id": "untrusted_bot"},
|
|
63
|
-
"resources": {"access_level": "*"},
|
|
64
|
-
"actions": ["read", "write"],
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
f,
|
|
68
|
-
)
|
|
69
|
-
mw = ABACMiddleware(self.db_path, policy_path=policy_path)
|
|
70
|
-
result = mw.check_access(
|
|
71
|
-
agent_id="untrusted_bot",
|
|
72
|
-
action="read",
|
|
73
|
-
resource={"access_level": "public"},
|
|
74
|
-
)
|
|
75
|
-
assert result["allowed"] is False
|
|
76
|
-
|
|
77
|
-
def test_denied_access_logged(self):
|
|
78
|
-
"""Denied access is recorded for audit trail."""
|
|
79
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
80
|
-
|
|
81
|
-
policy_path = os.path.join(self.tmp_dir, "abac_policies.json")
|
|
82
|
-
with open(policy_path, "w") as f:
|
|
83
|
-
json.dump(
|
|
84
|
-
[
|
|
85
|
-
{
|
|
86
|
-
"name": "deny-all-write",
|
|
87
|
-
"effect": "deny",
|
|
88
|
-
"subjects": {"agent_id": "*"},
|
|
89
|
-
"resources": {"access_level": "*"},
|
|
90
|
-
"actions": ["write"],
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
f,
|
|
94
|
-
)
|
|
95
|
-
mw = ABACMiddleware(self.db_path, policy_path=policy_path)
|
|
96
|
-
mw.check_access(agent_id="user", action="write", resource={})
|
|
97
|
-
assert mw.denied_count >= 1
|
|
98
|
-
|
|
99
|
-
def test_build_agent_context(self):
|
|
100
|
-
"""build_agent_context creates proper context dict for store."""
|
|
101
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
102
|
-
|
|
103
|
-
mw = ABACMiddleware(self.db_path)
|
|
104
|
-
ctx = mw.build_agent_context(agent_id="claude_agent", protocol="mcp")
|
|
105
|
-
assert ctx["agent_id"] == "claude_agent"
|
|
106
|
-
assert ctx["protocol"] == "mcp"
|
|
107
|
-
|
|
108
|
-
def test_graceful_when_compliance_unavailable(self):
|
|
109
|
-
"""Middleware works even if ABACEngine import fails."""
|
|
110
|
-
from compliance.abac_middleware import ABACMiddleware
|
|
111
|
-
|
|
112
|
-
mw = ABACMiddleware(
|
|
113
|
-
self.db_path, policy_path="/nonexistent/path.json"
|
|
114
|
-
)
|
|
115
|
-
result = mw.check_access(
|
|
116
|
-
agent_id="user", action="read", resource={}
|
|
117
|
-
)
|
|
118
|
-
assert result["allowed"] is True
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for audit database with hash chain tamper detection.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import hashlib
|
|
10
|
-
import json
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestAuditDB:
|
|
17
|
-
def setup_method(self):
|
|
18
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
19
|
-
self.db_path = os.path.join(self.tmp_dir, "audit.db")
|
|
20
|
-
|
|
21
|
-
def teardown_method(self):
|
|
22
|
-
import shutil
|
|
23
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
24
|
-
|
|
25
|
-
def test_creation(self):
|
|
26
|
-
from compliance.audit_db import AuditDB
|
|
27
|
-
db = AuditDB(self.db_path)
|
|
28
|
-
assert db is not None
|
|
29
|
-
|
|
30
|
-
def test_schema_created(self):
|
|
31
|
-
from compliance.audit_db import AuditDB
|
|
32
|
-
db = AuditDB(self.db_path)
|
|
33
|
-
conn = sqlite3.connect(self.db_path)
|
|
34
|
-
tables = {r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()}
|
|
35
|
-
conn.close()
|
|
36
|
-
assert "audit_events" in tables
|
|
37
|
-
|
|
38
|
-
def test_log_event(self):
|
|
39
|
-
from compliance.audit_db import AuditDB
|
|
40
|
-
db = AuditDB(self.db_path)
|
|
41
|
-
eid = db.log_event(event_type="memory.created", actor="user", resource_id=1, details={"action": "create"})
|
|
42
|
-
assert isinstance(eid, int)
|
|
43
|
-
assert eid > 0
|
|
44
|
-
|
|
45
|
-
def test_hash_chain_first_entry(self):
|
|
46
|
-
"""First entry's prev_hash should be a known genesis value."""
|
|
47
|
-
from compliance.audit_db import AuditDB
|
|
48
|
-
db = AuditDB(self.db_path)
|
|
49
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
50
|
-
conn = sqlite3.connect(self.db_path)
|
|
51
|
-
row = conn.execute("SELECT prev_hash, entry_hash FROM audit_events WHERE id=1").fetchone()
|
|
52
|
-
conn.close()
|
|
53
|
-
assert row[0] == "genesis"
|
|
54
|
-
assert row[1] is not None and len(row[1]) == 64 # SHA-256 hex
|
|
55
|
-
|
|
56
|
-
def test_hash_chain_links(self):
|
|
57
|
-
"""Each entry's prev_hash should equal the previous entry's entry_hash."""
|
|
58
|
-
from compliance.audit_db import AuditDB
|
|
59
|
-
db = AuditDB(self.db_path)
|
|
60
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
61
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=2)
|
|
62
|
-
db.log_event("memory.deleted", actor="user", resource_id=1)
|
|
63
|
-
conn = sqlite3.connect(self.db_path)
|
|
64
|
-
rows = conn.execute("SELECT id, prev_hash, entry_hash FROM audit_events ORDER BY id").fetchall()
|
|
65
|
-
conn.close()
|
|
66
|
-
assert rows[1][1] == rows[0][2] # Entry 2's prev = Entry 1's hash
|
|
67
|
-
assert rows[2][1] == rows[1][2] # Entry 3's prev = Entry 2's hash
|
|
68
|
-
|
|
69
|
-
def test_verify_chain_valid(self):
|
|
70
|
-
"""verify_chain returns True for untampered chain."""
|
|
71
|
-
from compliance.audit_db import AuditDB
|
|
72
|
-
db = AuditDB(self.db_path)
|
|
73
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
74
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
75
|
-
result = db.verify_chain()
|
|
76
|
-
assert result["valid"] is True
|
|
77
|
-
assert result["entries_checked"] == 2
|
|
78
|
-
|
|
79
|
-
def test_verify_chain_detects_tampering(self):
|
|
80
|
-
"""verify_chain returns False if an entry was modified."""
|
|
81
|
-
from compliance.audit_db import AuditDB
|
|
82
|
-
db = AuditDB(self.db_path)
|
|
83
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
84
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
85
|
-
# Tamper with the first entry
|
|
86
|
-
conn = sqlite3.connect(self.db_path)
|
|
87
|
-
conn.execute("UPDATE audit_events SET actor='hacker' WHERE id=1")
|
|
88
|
-
conn.commit()
|
|
89
|
-
conn.close()
|
|
90
|
-
result = db.verify_chain()
|
|
91
|
-
assert result["valid"] is False
|
|
92
|
-
|
|
93
|
-
def test_query_by_type(self):
|
|
94
|
-
from compliance.audit_db import AuditDB
|
|
95
|
-
db = AuditDB(self.db_path)
|
|
96
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
97
|
-
db.log_event("memory.recalled", actor="user", resource_id=1)
|
|
98
|
-
db.log_event("memory.created", actor="user", resource_id=2)
|
|
99
|
-
results = db.query_events(event_type="memory.created")
|
|
100
|
-
assert len(results) == 2
|
|
101
|
-
|
|
102
|
-
def test_query_by_actor(self):
|
|
103
|
-
from compliance.audit_db import AuditDB
|
|
104
|
-
db = AuditDB(self.db_path)
|
|
105
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
106
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
107
|
-
results = db.query_events(actor="agent_a")
|
|
108
|
-
assert len(results) == 1
|
|
109
|
-
|
|
110
|
-
def test_query_by_time_range(self):
|
|
111
|
-
from compliance.audit_db import AuditDB
|
|
112
|
-
db = AuditDB(self.db_path)
|
|
113
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
114
|
-
results = db.query_events(limit=10)
|
|
115
|
-
assert len(results) >= 1
|
|
116
|
-
assert "created_at" in results[0]
|
|
117
|
-
|
|
118
|
-
def test_empty_chain_is_valid(self):
|
|
119
|
-
from compliance.audit_db import AuditDB
|
|
120
|
-
db = AuditDB(self.db_path)
|
|
121
|
-
result = db.verify_chain()
|
|
122
|
-
assert result["valid"] is True
|
|
123
|
-
assert result["entries_checked"] == 0
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for audit logger EventBus listener.
|
|
4
|
-
"""
|
|
5
|
-
import tempfile, os, sys, sqlite3
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
9
|
-
|
|
10
|
-
class TestAuditLogger:
|
|
11
|
-
def setup_method(self):
|
|
12
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
13
|
-
self.audit_db_path = os.path.join(self.tmp_dir, "audit.db")
|
|
14
|
-
|
|
15
|
-
def teardown_method(self):
|
|
16
|
-
import shutil
|
|
17
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
18
|
-
|
|
19
|
-
def test_creation(self):
|
|
20
|
-
from compliance.audit_logger import AuditLogger
|
|
21
|
-
logger = AuditLogger(self.audit_db_path)
|
|
22
|
-
assert logger is not None
|
|
23
|
-
|
|
24
|
-
def test_logs_memory_created(self):
|
|
25
|
-
from compliance.audit_logger import AuditLogger
|
|
26
|
-
logger = AuditLogger(self.audit_db_path)
|
|
27
|
-
logger.handle_event({"event_type": "memory.created", "memory_id": 1, "payload": {}, "timestamp": datetime.now().isoformat(), "source_agent": "user"})
|
|
28
|
-
conn = sqlite3.connect(self.audit_db_path)
|
|
29
|
-
rows = conn.execute("SELECT * FROM audit_events WHERE event_type='memory.created'").fetchall()
|
|
30
|
-
conn.close()
|
|
31
|
-
assert len(rows) == 1
|
|
32
|
-
|
|
33
|
-
def test_logs_memory_recalled(self):
|
|
34
|
-
from compliance.audit_logger import AuditLogger
|
|
35
|
-
logger = AuditLogger(self.audit_db_path)
|
|
36
|
-
logger.handle_event({"event_type": "memory.recalled", "memory_id": 2, "payload": {"query": "test"}, "timestamp": datetime.now().isoformat(), "source_agent": "agent_a"})
|
|
37
|
-
conn = sqlite3.connect(self.audit_db_path)
|
|
38
|
-
rows = conn.execute("SELECT * FROM audit_events WHERE event_type='memory.recalled'").fetchall()
|
|
39
|
-
conn.close()
|
|
40
|
-
assert len(rows) == 1
|
|
41
|
-
|
|
42
|
-
def test_logs_memory_deleted(self):
|
|
43
|
-
from compliance.audit_logger import AuditLogger
|
|
44
|
-
logger = AuditLogger(self.audit_db_path)
|
|
45
|
-
logger.handle_event({"event_type": "memory.deleted", "memory_id": 3, "payload": {}, "timestamp": datetime.now().isoformat(), "source_agent": "user"})
|
|
46
|
-
conn = sqlite3.connect(self.audit_db_path)
|
|
47
|
-
rows = conn.execute("SELECT * FROM audit_events WHERE event_type='memory.deleted'").fetchall()
|
|
48
|
-
conn.close()
|
|
49
|
-
assert len(rows) == 1
|
|
50
|
-
|
|
51
|
-
def test_hash_chain_maintained(self):
|
|
52
|
-
"""Multiple events maintain hash chain integrity."""
|
|
53
|
-
from compliance.audit_logger import AuditLogger
|
|
54
|
-
logger = AuditLogger(self.audit_db_path)
|
|
55
|
-
for i in range(5):
|
|
56
|
-
logger.handle_event({"event_type": "memory.created", "memory_id": i, "payload": {}, "timestamp": datetime.now().isoformat(), "source_agent": "user"})
|
|
57
|
-
from compliance.audit_db import AuditDB
|
|
58
|
-
db = AuditDB(self.audit_db_path)
|
|
59
|
-
result = db.verify_chain()
|
|
60
|
-
assert result["valid"] is True
|
|
61
|
-
assert result["entries_checked"] == 5
|
|
62
|
-
|
|
63
|
-
def test_logs_lifecycle_transitions(self):
|
|
64
|
-
from compliance.audit_logger import AuditLogger
|
|
65
|
-
logger = AuditLogger(self.audit_db_path)
|
|
66
|
-
logger.handle_event({"event_type": "lifecycle.transitioned", "memory_id": 1, "payload": {"from_state": "active", "to_state": "warm"}, "timestamp": datetime.now().isoformat(), "source_agent": "scheduler"})
|
|
67
|
-
conn = sqlite3.connect(self.audit_db_path)
|
|
68
|
-
rows = conn.execute("SELECT * FROM audit_events").fetchall()
|
|
69
|
-
conn.close()
|
|
70
|
-
assert len(rows) == 1
|
|
71
|
-
|
|
72
|
-
def test_ignores_unknown_gracefully(self):
|
|
73
|
-
"""Unknown event types logged without error."""
|
|
74
|
-
from compliance.audit_logger import AuditLogger
|
|
75
|
-
logger = AuditLogger(self.audit_db_path)
|
|
76
|
-
logger.handle_event({"event_type": "unknown.event", "payload": {}, "timestamp": datetime.now().isoformat(), "source_agent": "test"})
|
|
77
|
-
assert logger.events_logged >= 1
|
|
78
|
-
|
|
79
|
-
def test_graceful_on_malformed_event(self):
|
|
80
|
-
"""Malformed events don't crash the logger."""
|
|
81
|
-
from compliance.audit_logger import AuditLogger
|
|
82
|
-
logger = AuditLogger(self.audit_db_path)
|
|
83
|
-
logger.handle_event({}) # Empty event
|
|
84
|
-
logger.handle_event({"event_type": "test"}) # Missing fields
|
|
85
|
-
# Should not crash
|
|
86
|
-
|
|
87
|
-
def test_register_with_eventbus(self):
|
|
88
|
-
from compliance.audit_logger import AuditLogger
|
|
89
|
-
logger = AuditLogger(self.audit_db_path)
|
|
90
|
-
result = logger.register_with_eventbus()
|
|
91
|
-
assert isinstance(result, bool)
|
|
92
|
-
|
|
93
|
-
def test_get_status(self):
|
|
94
|
-
from compliance.audit_logger import AuditLogger
|
|
95
|
-
logger = AuditLogger(self.audit_db_path)
|
|
96
|
-
status = logger.get_status()
|
|
97
|
-
assert "events_logged" in status
|
|
98
|
-
assert "registered" in status
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for audit_trail MCP tool handler.
|
|
4
|
-
|
|
5
|
-
Validates the MCP wrapper around AuditDB — tests empty trail, event logging,
|
|
6
|
-
event_type and actor filtering, and hash chain verification.
|
|
7
|
-
"""
|
|
8
|
-
import asyncio
|
|
9
|
-
import os
|
|
10
|
-
import shutil
|
|
11
|
-
import sys
|
|
12
|
-
import tempfile
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
import pytest
|
|
16
|
-
|
|
17
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestMCPAuditTrail:
|
|
21
|
-
"""Tests for the audit_trail tool handler."""
|
|
22
|
-
|
|
23
|
-
def setup_method(self):
|
|
24
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
25
|
-
self.db_path = os.path.join(self.tmp_dir, "audit.db")
|
|
26
|
-
|
|
27
|
-
def teardown_method(self):
|
|
28
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
29
|
-
|
|
30
|
-
def _run(self, coro):
|
|
31
|
-
return asyncio.get_event_loop().run_until_complete(coro)
|
|
32
|
-
|
|
33
|
-
def test_empty_trail(self):
|
|
34
|
-
"""Fresh audit DB should return count=0."""
|
|
35
|
-
import mcp_tools_v28 as tools
|
|
36
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
37
|
-
|
|
38
|
-
result = self._run(tools.audit_trail())
|
|
39
|
-
assert result["success"] is True
|
|
40
|
-
assert result["count"] == 0
|
|
41
|
-
assert result["events"] == []
|
|
42
|
-
|
|
43
|
-
def test_verify_empty_chain(self):
|
|
44
|
-
"""Hash chain verification on empty DB should be valid."""
|
|
45
|
-
import mcp_tools_v28 as tools
|
|
46
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
47
|
-
|
|
48
|
-
result = self._run(tools.audit_trail(verify_chain=True))
|
|
49
|
-
assert result["success"] is True
|
|
50
|
-
assert result["chain_valid"] is True
|
|
51
|
-
assert result["chain_entries"] == 0
|
|
52
|
-
|
|
53
|
-
def test_query_with_events(self):
|
|
54
|
-
"""After logging events, query should return them."""
|
|
55
|
-
from compliance.audit_db import AuditDB
|
|
56
|
-
|
|
57
|
-
db = AuditDB(self.db_path)
|
|
58
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
59
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
60
|
-
db.log_event("memory.created", actor="user", resource_id=2)
|
|
61
|
-
|
|
62
|
-
import mcp_tools_v28 as tools
|
|
63
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
64
|
-
|
|
65
|
-
result = self._run(tools.audit_trail())
|
|
66
|
-
assert result["success"] is True
|
|
67
|
-
assert result["count"] == 3
|
|
68
|
-
|
|
69
|
-
def test_filter_by_event_type(self):
|
|
70
|
-
"""Filtering by event_type should narrow results."""
|
|
71
|
-
from compliance.audit_db import AuditDB
|
|
72
|
-
|
|
73
|
-
db = AuditDB(self.db_path)
|
|
74
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
75
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
76
|
-
|
|
77
|
-
import mcp_tools_v28 as tools
|
|
78
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
79
|
-
|
|
80
|
-
result = self._run(tools.audit_trail(event_type="memory.created"))
|
|
81
|
-
assert result["count"] == 1
|
|
82
|
-
assert result["events"][0]["event_type"] == "memory.created"
|
|
83
|
-
|
|
84
|
-
def test_filter_by_actor(self):
|
|
85
|
-
"""Filtering by actor should narrow results."""
|
|
86
|
-
from compliance.audit_db import AuditDB
|
|
87
|
-
|
|
88
|
-
db = AuditDB(self.db_path)
|
|
89
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
90
|
-
db.log_event("memory.recalled", actor="agent_a", resource_id=1)
|
|
91
|
-
|
|
92
|
-
import mcp_tools_v28 as tools
|
|
93
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
94
|
-
|
|
95
|
-
result = self._run(tools.audit_trail(actor="agent_a"))
|
|
96
|
-
assert result["count"] == 1
|
|
97
|
-
assert result["events"][0]["actor"] == "agent_a"
|
|
98
|
-
|
|
99
|
-
def test_verify_chain_with_events(self):
|
|
100
|
-
"""Hash chain with events should verify successfully."""
|
|
101
|
-
from compliance.audit_db import AuditDB
|
|
102
|
-
|
|
103
|
-
db = AuditDB(self.db_path)
|
|
104
|
-
db.log_event("memory.created", actor="user", resource_id=1)
|
|
105
|
-
db.log_event("memory.recalled", actor="user", resource_id=1)
|
|
106
|
-
db.log_event("memory.updated", actor="user", resource_id=1)
|
|
107
|
-
|
|
108
|
-
import mcp_tools_v28 as tools
|
|
109
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
110
|
-
|
|
111
|
-
result = self._run(tools.audit_trail(verify_chain=True))
|
|
112
|
-
assert result["success"] is True
|
|
113
|
-
assert result["chain_valid"] is True
|
|
114
|
-
assert result["chain_entries"] == 3
|
|
115
|
-
|
|
116
|
-
def test_limit_parameter(self):
|
|
117
|
-
"""Limit parameter should cap returned events."""
|
|
118
|
-
from compliance.audit_db import AuditDB
|
|
119
|
-
|
|
120
|
-
db = AuditDB(self.db_path)
|
|
121
|
-
for i in range(10):
|
|
122
|
-
db.log_event("memory.created", actor="user", resource_id=i)
|
|
123
|
-
|
|
124
|
-
import mcp_tools_v28 as tools
|
|
125
|
-
tools.DEFAULT_AUDIT_DB = self.db_path
|
|
126
|
-
|
|
127
|
-
result = self._run(tools.audit_trail(limit=3))
|
|
128
|
-
assert result["count"] == 3
|