superlocalmemory 2.8.6 → 3.0.1
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/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +62 -48
- 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/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/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 -1808
- 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 -286
- /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
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Compliance retention manager — regulatory retention enforcement.
|
|
4
|
-
|
|
5
|
-
Unlike the lifecycle ``retention_policy.py`` (which manages lifecycle-level
|
|
6
|
-
policies stored alongside memory.db), this compliance module is the
|
|
7
|
-
*regulatory* layer that:
|
|
8
|
-
|
|
9
|
-
- Links retention rules to regulatory frameworks (GDPR, EU AI Act, HIPAA).
|
|
10
|
-
- Enforces GDPR right-to-erasure (tombstone memory + preserve audit trail).
|
|
11
|
-
- Enforces EU AI Act audit retention (10-year minimum for audit records).
|
|
12
|
-
- Records every retention action in audit.db for tamper-evident compliance.
|
|
13
|
-
|
|
14
|
-
Rules are stored in audit.db (``compliance_retention_rules`` table) so that
|
|
15
|
-
the audit database remains the single source of truth for all compliance
|
|
16
|
-
configuration and evidence.
|
|
17
|
-
"""
|
|
18
|
-
import hashlib
|
|
19
|
-
import json
|
|
20
|
-
import logging
|
|
21
|
-
import sqlite3
|
|
22
|
-
from datetime import datetime, timezone
|
|
23
|
-
from typing import Any, Dict, List, Optional
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
_RULES_TABLE_SQL = """
|
|
28
|
-
CREATE TABLE IF NOT EXISTS compliance_retention_rules (
|
|
29
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30
|
-
name TEXT NOT NULL,
|
|
31
|
-
framework TEXT NOT NULL,
|
|
32
|
-
retention_days INTEGER NOT NULL,
|
|
33
|
-
action TEXT NOT NULL,
|
|
34
|
-
applies_to TEXT NOT NULL,
|
|
35
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
36
|
-
)
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
_AUDIT_EVENTS_TABLE_SQL = """
|
|
40
|
-
CREATE TABLE IF NOT EXISTS audit_events (
|
|
41
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
-
event_type TEXT NOT NULL,
|
|
43
|
-
actor TEXT NOT NULL,
|
|
44
|
-
resource_id INTEGER,
|
|
45
|
-
details TEXT DEFAULT '{}',
|
|
46
|
-
prev_hash TEXT NOT NULL DEFAULT 'genesis',
|
|
47
|
-
entry_hash TEXT NOT NULL DEFAULT '',
|
|
48
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
49
|
-
)
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _compute_hash(event_type: str, actor: str, resource_id: Any,
|
|
54
|
-
details: str, prev_hash: str, ts: str) -> str:
|
|
55
|
-
"""Compute a SHA-256 hash for a single audit event."""
|
|
56
|
-
payload = f"{event_type}|{actor}|{resource_id}|{details}|{prev_hash}|{ts}"
|
|
57
|
-
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class ComplianceRetentionManager:
|
|
61
|
-
"""Enforces regulatory retention policies across memory and audit DBs.
|
|
62
|
-
|
|
63
|
-
Connects to *both* databases:
|
|
64
|
-
- ``memory_db_path``: where memories live (tombstoning happens here).
|
|
65
|
-
- ``audit_db_path``: where rules and audit events are stored.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
def __init__(self, memory_db_path: str, audit_db_path: str):
|
|
69
|
-
self._memory_db_path = memory_db_path
|
|
70
|
-
self._audit_db_path = audit_db_path
|
|
71
|
-
self._ensure_tables()
|
|
72
|
-
|
|
73
|
-
# ------------------------------------------------------------------
|
|
74
|
-
# Internal helpers
|
|
75
|
-
# ------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
def _connect_audit(self) -> sqlite3.Connection:
|
|
78
|
-
conn = sqlite3.connect(self._audit_db_path)
|
|
79
|
-
conn.row_factory = sqlite3.Row
|
|
80
|
-
return conn
|
|
81
|
-
|
|
82
|
-
def _connect_memory(self) -> sqlite3.Connection:
|
|
83
|
-
conn = sqlite3.connect(self._memory_db_path)
|
|
84
|
-
conn.row_factory = sqlite3.Row
|
|
85
|
-
return conn
|
|
86
|
-
|
|
87
|
-
def _ensure_tables(self) -> None:
|
|
88
|
-
conn = self._connect_audit()
|
|
89
|
-
try:
|
|
90
|
-
conn.execute(_RULES_TABLE_SQL)
|
|
91
|
-
conn.execute(_AUDIT_EVENTS_TABLE_SQL)
|
|
92
|
-
conn.commit()
|
|
93
|
-
finally:
|
|
94
|
-
conn.close()
|
|
95
|
-
|
|
96
|
-
def _log_audit_event(self, event_type: str, actor: str,
|
|
97
|
-
resource_id: Optional[int],
|
|
98
|
-
details: Dict[str, Any]) -> None:
|
|
99
|
-
"""Append a tamper-evident audit event to audit.db."""
|
|
100
|
-
conn = self._connect_audit()
|
|
101
|
-
try:
|
|
102
|
-
last = conn.execute(
|
|
103
|
-
"SELECT entry_hash FROM audit_events ORDER BY id DESC LIMIT 1"
|
|
104
|
-
).fetchone()
|
|
105
|
-
prev_hash = last["entry_hash"] if last else "genesis"
|
|
106
|
-
ts = datetime.now(timezone.utc).isoformat()
|
|
107
|
-
details_json = json.dumps(details, default=str)
|
|
108
|
-
entry_hash = _compute_hash(
|
|
109
|
-
event_type, actor, resource_id, details_json, prev_hash, ts,
|
|
110
|
-
)
|
|
111
|
-
conn.execute(
|
|
112
|
-
"INSERT INTO audit_events "
|
|
113
|
-
"(event_type, actor, resource_id, details, prev_hash, entry_hash, created_at) "
|
|
114
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
115
|
-
(event_type, actor, resource_id, details_json, prev_hash,
|
|
116
|
-
entry_hash, ts),
|
|
117
|
-
)
|
|
118
|
-
conn.commit()
|
|
119
|
-
finally:
|
|
120
|
-
conn.close()
|
|
121
|
-
|
|
122
|
-
@staticmethod
|
|
123
|
-
def _parse_json(value: Any) -> Any:
|
|
124
|
-
if isinstance(value, str):
|
|
125
|
-
try:
|
|
126
|
-
return json.loads(value)
|
|
127
|
-
except (json.JSONDecodeError, TypeError):
|
|
128
|
-
return value
|
|
129
|
-
return value if value is not None else []
|
|
130
|
-
|
|
131
|
-
@staticmethod
|
|
132
|
-
def _matches(criteria: Any, mem_tags: Any, mem_project: Optional[str]) -> bool:
|
|
133
|
-
"""Return True when a rule's ``applies_to`` matches the memory."""
|
|
134
|
-
if not isinstance(criteria, dict) or not criteria:
|
|
135
|
-
return False
|
|
136
|
-
ok = True
|
|
137
|
-
if "tags" in criteria:
|
|
138
|
-
rule_tags = set(criteria["tags"]) if criteria["tags"] else set()
|
|
139
|
-
m_tags = set(mem_tags) if isinstance(mem_tags, list) else set()
|
|
140
|
-
if not rule_tags & m_tags:
|
|
141
|
-
ok = False
|
|
142
|
-
if "project_name" in criteria:
|
|
143
|
-
if mem_project != criteria["project_name"]:
|
|
144
|
-
ok = False
|
|
145
|
-
return ok
|
|
146
|
-
|
|
147
|
-
# ------------------------------------------------------------------
|
|
148
|
-
# Public API
|
|
149
|
-
# ------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
def create_retention_rule(self, name: str, framework: str,
|
|
152
|
-
retention_days: int, action: str,
|
|
153
|
-
applies_to: Dict[str, Any]) -> int:
|
|
154
|
-
"""Create a compliance retention rule in audit.db.
|
|
155
|
-
|
|
156
|
-
Returns the auto-generated rule ID.
|
|
157
|
-
"""
|
|
158
|
-
conn = self._connect_audit()
|
|
159
|
-
try:
|
|
160
|
-
cur = conn.execute(
|
|
161
|
-
"INSERT INTO compliance_retention_rules "
|
|
162
|
-
"(name, framework, retention_days, action, applies_to) "
|
|
163
|
-
"VALUES (?, ?, ?, ?, ?)",
|
|
164
|
-
(name, framework, retention_days, action,
|
|
165
|
-
json.dumps(applies_to)),
|
|
166
|
-
)
|
|
167
|
-
conn.commit()
|
|
168
|
-
rule_id = cur.lastrowid
|
|
169
|
-
finally:
|
|
170
|
-
conn.close()
|
|
171
|
-
|
|
172
|
-
self._log_audit_event(
|
|
173
|
-
"retention.rule_created", "system", rule_id,
|
|
174
|
-
{"name": name, "framework": framework},
|
|
175
|
-
)
|
|
176
|
-
return rule_id
|
|
177
|
-
|
|
178
|
-
def list_rules(self) -> List[Dict[str, Any]]:
|
|
179
|
-
"""Return all compliance retention rules."""
|
|
180
|
-
conn = self._connect_audit()
|
|
181
|
-
try:
|
|
182
|
-
rows = conn.execute(
|
|
183
|
-
"SELECT * FROM compliance_retention_rules ORDER BY id"
|
|
184
|
-
).fetchall()
|
|
185
|
-
result = []
|
|
186
|
-
for r in rows:
|
|
187
|
-
d = dict(r)
|
|
188
|
-
if isinstance(d.get("applies_to"), str):
|
|
189
|
-
d["applies_to"] = self._parse_json(d["applies_to"])
|
|
190
|
-
result.append(d)
|
|
191
|
-
return result
|
|
192
|
-
finally:
|
|
193
|
-
conn.close()
|
|
194
|
-
|
|
195
|
-
def evaluate_memory(self, memory_id: int) -> Optional[Dict[str, Any]]:
|
|
196
|
-
"""Check which compliance rule applies to a memory.
|
|
197
|
-
|
|
198
|
-
Reads the memory's tags/project from memory.db, then evaluates
|
|
199
|
-
all rules from audit.db. The first matching rule (ordered by id)
|
|
200
|
-
is returned.
|
|
201
|
-
|
|
202
|
-
Returns a dict with ``rule_name``, ``action``, ``retention_days``,
|
|
203
|
-
``framework``; or ``None`` if no rule matches.
|
|
204
|
-
"""
|
|
205
|
-
mem_conn = self._connect_memory()
|
|
206
|
-
try:
|
|
207
|
-
mem = mem_conn.execute(
|
|
208
|
-
"SELECT tags, project_name FROM memories WHERE id = ?",
|
|
209
|
-
(memory_id,),
|
|
210
|
-
).fetchone()
|
|
211
|
-
if mem is None:
|
|
212
|
-
return None
|
|
213
|
-
mem_tags = self._parse_json(mem["tags"])
|
|
214
|
-
mem_project = mem["project_name"]
|
|
215
|
-
finally:
|
|
216
|
-
mem_conn.close()
|
|
217
|
-
|
|
218
|
-
audit_conn = self._connect_audit()
|
|
219
|
-
try:
|
|
220
|
-
rules = audit_conn.execute(
|
|
221
|
-
"SELECT * FROM compliance_retention_rules ORDER BY id"
|
|
222
|
-
).fetchall()
|
|
223
|
-
for rule in rules:
|
|
224
|
-
criteria = self._parse_json(rule["applies_to"])
|
|
225
|
-
if self._matches(criteria, mem_tags, mem_project):
|
|
226
|
-
return {
|
|
227
|
-
"rule_name": rule["name"],
|
|
228
|
-
"action": rule["action"],
|
|
229
|
-
"retention_days": rule["retention_days"],
|
|
230
|
-
"framework": rule["framework"],
|
|
231
|
-
}
|
|
232
|
-
return None
|
|
233
|
-
finally:
|
|
234
|
-
audit_conn.close()
|
|
235
|
-
|
|
236
|
-
def execute_erasure_request(self, memory_id: int, framework: str,
|
|
237
|
-
requested_by: str) -> Dict[str, Any]:
|
|
238
|
-
"""Execute a GDPR (or other framework) right-to-erasure request.
|
|
239
|
-
|
|
240
|
-
1. Tombstones the memory in memory.db.
|
|
241
|
-
2. Logs the erasure event in audit.db (preserving the audit trail).
|
|
242
|
-
|
|
243
|
-
Returns a result dict with ``success``, ``action``, and ``memory_id``.
|
|
244
|
-
"""
|
|
245
|
-
mem_conn = self._connect_memory()
|
|
246
|
-
try:
|
|
247
|
-
row = mem_conn.execute(
|
|
248
|
-
"SELECT id FROM memories WHERE id = ?", (memory_id,),
|
|
249
|
-
).fetchone()
|
|
250
|
-
if row is None:
|
|
251
|
-
return {"success": False, "error": "memory_not_found",
|
|
252
|
-
"memory_id": memory_id}
|
|
253
|
-
|
|
254
|
-
ts = datetime.now(timezone.utc).isoformat()
|
|
255
|
-
mem_conn.execute(
|
|
256
|
-
"UPDATE memories SET lifecycle_state = 'tombstoned', "
|
|
257
|
-
"lifecycle_updated_at = ? WHERE id = ?",
|
|
258
|
-
(ts, memory_id),
|
|
259
|
-
)
|
|
260
|
-
mem_conn.commit()
|
|
261
|
-
finally:
|
|
262
|
-
mem_conn.close()
|
|
263
|
-
|
|
264
|
-
self._log_audit_event(
|
|
265
|
-
"retention.erasure", requested_by, memory_id,
|
|
266
|
-
{"framework": framework, "action": "tombstoned"},
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
return {"success": True, "action": "tombstoned",
|
|
270
|
-
"memory_id": memory_id}
|
|
271
|
-
|
|
272
|
-
def get_compliance_status(self) -> Dict[str, Any]:
|
|
273
|
-
"""Return a summary of current compliance retention state."""
|
|
274
|
-
conn = self._connect_audit()
|
|
275
|
-
try:
|
|
276
|
-
rules = conn.execute(
|
|
277
|
-
"SELECT * FROM compliance_retention_rules"
|
|
278
|
-
).fetchall()
|
|
279
|
-
frameworks = list({r["framework"] for r in rules})
|
|
280
|
-
events_count = conn.execute(
|
|
281
|
-
"SELECT COUNT(*) AS cnt FROM audit_events"
|
|
282
|
-
).fetchone()["cnt"]
|
|
283
|
-
return {
|
|
284
|
-
"rules_count": len(rules),
|
|
285
|
-
"frameworks": sorted(frameworks),
|
|
286
|
-
"audit_events_count": events_count,
|
|
287
|
-
}
|
|
288
|
-
finally:
|
|
289
|
-
conn.close()
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Background scheduler for periodic retention policy enforcement.
|
|
4
|
-
|
|
5
|
-
Runs on a configurable interval (default: 24 hours) to:
|
|
6
|
-
1. Load compliance retention rules from audit.db
|
|
7
|
-
2. Scan all memories in memory.db against those rules
|
|
8
|
-
3. Tombstone expired memories (age exceeds retention_days)
|
|
9
|
-
4. Log every action to audit.db for tamper-evident compliance
|
|
10
|
-
|
|
11
|
-
Uses daemon threading -- does not prevent process exit.
|
|
12
|
-
"""
|
|
13
|
-
import json
|
|
14
|
-
import logging
|
|
15
|
-
import sqlite3
|
|
16
|
-
import threading
|
|
17
|
-
from datetime import datetime, timezone
|
|
18
|
-
from typing import Any, Dict, List, Optional
|
|
19
|
-
|
|
20
|
-
from .retention_manager import ComplianceRetentionManager
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
# Default interval: 24 hours
|
|
25
|
-
DEFAULT_INTERVAL_SECONDS = 86400
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class RetentionScheduler:
|
|
29
|
-
"""Background scheduler for periodic retention policy enforcement.
|
|
30
|
-
|
|
31
|
-
Orchestrates ComplianceRetentionManager on a configurable timer
|
|
32
|
-
interval to automatically enforce retention rules.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(
|
|
36
|
-
self,
|
|
37
|
-
memory_db_path: str,
|
|
38
|
-
audit_db_path: str,
|
|
39
|
-
interval_seconds: int = DEFAULT_INTERVAL_SECONDS,
|
|
40
|
-
):
|
|
41
|
-
self._memory_db_path = memory_db_path
|
|
42
|
-
self._audit_db_path = audit_db_path
|
|
43
|
-
self.interval_seconds = interval_seconds
|
|
44
|
-
|
|
45
|
-
self._manager = ComplianceRetentionManager(
|
|
46
|
-
memory_db_path, audit_db_path,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
self._timer: Optional[threading.Timer] = None
|
|
50
|
-
self._running = False
|
|
51
|
-
self._lock = threading.Lock()
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def is_running(self) -> bool:
|
|
55
|
-
"""Whether the scheduler is currently running."""
|
|
56
|
-
return self._running
|
|
57
|
-
|
|
58
|
-
def start(self) -> None:
|
|
59
|
-
"""Start the background scheduler."""
|
|
60
|
-
with self._lock:
|
|
61
|
-
if self._running:
|
|
62
|
-
return
|
|
63
|
-
self._running = True
|
|
64
|
-
self._schedule_next()
|
|
65
|
-
|
|
66
|
-
def stop(self) -> None:
|
|
67
|
-
"""Stop the background scheduler."""
|
|
68
|
-
with self._lock:
|
|
69
|
-
self._running = False
|
|
70
|
-
if self._timer is not None:
|
|
71
|
-
self._timer.cancel()
|
|
72
|
-
self._timer = None
|
|
73
|
-
|
|
74
|
-
def run_now(self) -> Dict[str, Any]:
|
|
75
|
-
"""Execute a retention enforcement cycle immediately.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Dict with timestamp, actions taken, and rules evaluated.
|
|
79
|
-
"""
|
|
80
|
-
return self._execute_cycle()
|
|
81
|
-
|
|
82
|
-
def _schedule_next(self) -> None:
|
|
83
|
-
"""Schedule the next enforcement cycle."""
|
|
84
|
-
self._timer = threading.Timer(self.interval_seconds, self._run_cycle)
|
|
85
|
-
self._timer.daemon = True
|
|
86
|
-
self._timer.start()
|
|
87
|
-
|
|
88
|
-
def _run_cycle(self) -> None:
|
|
89
|
-
"""Run one enforcement cycle, then schedule the next."""
|
|
90
|
-
try:
|
|
91
|
-
self._execute_cycle()
|
|
92
|
-
except Exception:
|
|
93
|
-
pass # Scheduler must not crash
|
|
94
|
-
finally:
|
|
95
|
-
with self._lock:
|
|
96
|
-
if self._running:
|
|
97
|
-
self._schedule_next()
|
|
98
|
-
|
|
99
|
-
def _execute_cycle(self) -> Dict[str, Any]:
|
|
100
|
-
"""Core retention enforcement logic.
|
|
101
|
-
|
|
102
|
-
1. Load all retention rules from audit.db
|
|
103
|
-
2. Scan every memory against each rule
|
|
104
|
-
3. Tombstone memories that exceed retention_days
|
|
105
|
-
4. Log actions to audit.db
|
|
106
|
-
"""
|
|
107
|
-
rules = self._manager.list_rules()
|
|
108
|
-
actions: List[Dict[str, Any]] = []
|
|
109
|
-
|
|
110
|
-
# Scan all memories
|
|
111
|
-
memory_ids = self._get_all_memory_ids()
|
|
112
|
-
|
|
113
|
-
for mem_id in memory_ids:
|
|
114
|
-
mem = self._get_memory(mem_id)
|
|
115
|
-
if mem is None:
|
|
116
|
-
continue
|
|
117
|
-
|
|
118
|
-
# Already tombstoned -- skip
|
|
119
|
-
if mem.get("lifecycle_state") == "tombstoned":
|
|
120
|
-
continue
|
|
121
|
-
|
|
122
|
-
match = self._manager.evaluate_memory(mem_id)
|
|
123
|
-
if match is None:
|
|
124
|
-
continue
|
|
125
|
-
|
|
126
|
-
# Check if memory age exceeds the rule's retention_days
|
|
127
|
-
created_at = mem.get("created_at")
|
|
128
|
-
if created_at is None:
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
age_days = self._age_in_days(created_at)
|
|
132
|
-
if age_days > match["retention_days"]:
|
|
133
|
-
action = match["action"]
|
|
134
|
-
if action == "tombstone":
|
|
135
|
-
result = self._manager.execute_erasure_request(
|
|
136
|
-
mem_id, match["framework"], "retention_scheduler",
|
|
137
|
-
)
|
|
138
|
-
actions.append({
|
|
139
|
-
"memory_id": mem_id,
|
|
140
|
-
"action": action,
|
|
141
|
-
"rule_name": match["rule_name"],
|
|
142
|
-
"framework": match["framework"],
|
|
143
|
-
"age_days": age_days,
|
|
144
|
-
"success": result.get("success", False),
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
149
|
-
"actions": actions,
|
|
150
|
-
"rules_evaluated": len(rules),
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
# ------------------------------------------------------------------
|
|
154
|
-
# Internal helpers
|
|
155
|
-
# ------------------------------------------------------------------
|
|
156
|
-
|
|
157
|
-
def _get_all_memory_ids(self) -> List[int]:
|
|
158
|
-
"""Return all memory IDs from memory.db."""
|
|
159
|
-
conn = sqlite3.connect(self._memory_db_path)
|
|
160
|
-
try:
|
|
161
|
-
rows = conn.execute("SELECT id FROM memories").fetchall()
|
|
162
|
-
return [r[0] for r in rows]
|
|
163
|
-
finally:
|
|
164
|
-
conn.close()
|
|
165
|
-
|
|
166
|
-
def _get_memory(self, memory_id: int) -> Optional[Dict[str, Any]]:
|
|
167
|
-
"""Fetch a single memory row as a dict."""
|
|
168
|
-
conn = sqlite3.connect(self._memory_db_path)
|
|
169
|
-
conn.row_factory = sqlite3.Row
|
|
170
|
-
try:
|
|
171
|
-
row = conn.execute(
|
|
172
|
-
"SELECT * FROM memories WHERE id = ?", (memory_id,),
|
|
173
|
-
).fetchone()
|
|
174
|
-
return dict(row) if row else None
|
|
175
|
-
finally:
|
|
176
|
-
conn.close()
|
|
177
|
-
|
|
178
|
-
@staticmethod
|
|
179
|
-
def _age_in_days(created_at_str: str) -> float:
|
|
180
|
-
"""Calculate age of a memory in days from its created_at."""
|
|
181
|
-
try:
|
|
182
|
-
created = datetime.fromisoformat(created_at_str)
|
|
183
|
-
now = datetime.now(created.tzinfo)
|
|
184
|
-
return (now - created).total_seconds() / 86400
|
|
185
|
-
except (ValueError, TypeError):
|
|
186
|
-
return 0.0
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for ABAC enforcement in memory operations.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
import shutil
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestABACEnforcement:
|
|
17
|
-
def setup_method(self):
|
|
18
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
19
|
-
self.db_path = os.path.join(self.tmp_dir, "memory.db")
|
|
20
|
-
# Create store with test data
|
|
21
|
-
from memory_store_v2 import MemoryStoreV2
|
|
22
|
-
self.store = MemoryStoreV2(self.db_path)
|
|
23
|
-
self.store.add_memory(
|
|
24
|
-
content="public memory about Python",
|
|
25
|
-
tags=["python"],
|
|
26
|
-
importance=5,
|
|
27
|
-
)
|
|
28
|
-
self.store.add_memory(
|
|
29
|
-
content="private memory about secrets",
|
|
30
|
-
tags=["secrets"],
|
|
31
|
-
importance=8,
|
|
32
|
-
)
|
|
33
|
-
# Set access_level for private memory
|
|
34
|
-
conn = sqlite3.connect(self.db_path)
|
|
35
|
-
conn.execute("UPDATE memories SET access_level='private' WHERE id=2")
|
|
36
|
-
conn.commit()
|
|
37
|
-
conn.close()
|
|
38
|
-
self.store._rebuild_vectors()
|
|
39
|
-
|
|
40
|
-
def teardown_method(self):
|
|
41
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
42
|
-
|
|
43
|
-
def test_search_without_abac_works(self):
|
|
44
|
-
"""Search without ABAC context works (backward compat)."""
|
|
45
|
-
results = self.store.search("Python", limit=5)
|
|
46
|
-
assert len(results) >= 1
|
|
47
|
-
|
|
48
|
-
def test_search_with_agent_context(self):
|
|
49
|
-
"""Search with agent_context parameter works."""
|
|
50
|
-
results = self.store.search(
|
|
51
|
-
"memory", limit=10, agent_context={"agent_id": "user"}
|
|
52
|
-
)
|
|
53
|
-
assert isinstance(results, list)
|
|
54
|
-
|
|
55
|
-
def test_create_without_abac_works(self):
|
|
56
|
-
"""Create without ABAC works (backward compat)."""
|
|
57
|
-
mem_id = self.store.add_memory(content="new memory", tags=["test"])
|
|
58
|
-
assert mem_id is not None
|
|
59
|
-
|
|
60
|
-
def test_abac_check_method_exists(self):
|
|
61
|
-
"""MemoryStoreV2 has _check_abac method."""
|
|
62
|
-
assert hasattr(self.store, "_check_abac")
|
|
63
|
-
|
|
64
|
-
def test_check_abac_default_allows(self):
|
|
65
|
-
"""Default ABAC check (no policy file) allows everything."""
|
|
66
|
-
result = self.store._check_abac(
|
|
67
|
-
subject={"agent_id": "user"},
|
|
68
|
-
resource={"access_level": "public"},
|
|
69
|
-
action="read",
|
|
70
|
-
)
|
|
71
|
-
assert result["allowed"] is True
|
|
72
|
-
|
|
73
|
-
def test_check_abac_with_policy(self):
|
|
74
|
-
"""ABAC check with policy file respects deny rules."""
|
|
75
|
-
policy_path = os.path.join(self.tmp_dir, "abac_policies.json")
|
|
76
|
-
with open(policy_path, "w") as f:
|
|
77
|
-
json.dump(
|
|
78
|
-
[
|
|
79
|
-
{
|
|
80
|
-
"name": "deny-private",
|
|
81
|
-
"effect": "deny",
|
|
82
|
-
"subjects": {"agent_id": "*"},
|
|
83
|
-
"resources": {"access_level": "private"},
|
|
84
|
-
"actions": ["read"],
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
f,
|
|
88
|
-
)
|
|
89
|
-
result = self.store._check_abac(
|
|
90
|
-
subject={"agent_id": "bot"},
|
|
91
|
-
resource={"access_level": "private"},
|
|
92
|
-
action="read",
|
|
93
|
-
policy_path=policy_path,
|
|
94
|
-
)
|
|
95
|
-
assert result["allowed"] is False
|