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.
Files changed (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -1,518 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """SuperLocalMemory V2 - Cache Manager
5
-
6
- Solution Architect & Original Creator
7
-
8
- (see LICENSE file)
9
-
10
- ATTRIBUTION REQUIRED: This notice must be preserved in all copies.
11
- """
12
- """
13
- Cache Manager - LRU Cache for Search Results
14
-
15
- Implements Least Recently Used (LRU) cache for search query results to reduce
16
- redundant computation and improve response times.
17
-
18
- Key Features:
19
- 1. LRU Eviction Policy: Automatically removes least recently used entries
20
- 2. TTL Support: Optional time-to-live for cache entries
21
- 3. Size-Based Eviction: Maximum cache size in number of entries
22
- 4. Memory-Efficient: Uses OrderedDict for O(1) access and updates
23
- 5. Thread-Safe: Optional thread safety for concurrent access
24
-
25
- Performance Impact:
26
- - Cache hit: ~0.1ms (negligible overhead)
27
- - Cache miss: Standard search time
28
- - Target cache hit rate: 30-50% for typical usage
29
-
30
- Usage:
31
- cache = CacheManager(max_size=100, ttl_seconds=300)
32
-
33
- # Try cache first
34
- result = cache.get("python web")
35
- if result is None:
36
- # Cache miss - perform search
37
- result = search_engine.search("python web")
38
- cache.put("python web", result)
39
- """
40
-
41
- import time
42
- import hashlib
43
- import json
44
- from collections import OrderedDict
45
- from typing import Any, Optional, Dict, Tuple
46
- from threading import RLock
47
-
48
-
49
- class CacheEntry:
50
- """
51
- Single cache entry with metadata.
52
-
53
- Stores:
54
- - value: Cached result
55
- - timestamp: Creation time for TTL validation
56
- - access_count: Number of times accessed (for analytics)
57
- - size_estimate: Memory size estimate in bytes
58
- """
59
-
60
- __slots__ = ['value', 'timestamp', 'access_count', 'size_estimate']
61
-
62
- def __init__(self, value: Any, size_estimate: int = 0):
63
- """
64
- Create cache entry.
65
-
66
- Args:
67
- value: Value to cache
68
- size_estimate: Estimated size in bytes
69
- """
70
- self.value = value
71
- self.timestamp = time.time()
72
- self.access_count = 0
73
- self.size_estimate = size_estimate
74
-
75
- def is_expired(self, ttl_seconds: Optional[float]) -> bool:
76
- """
77
- Check if entry has exceeded TTL.
78
-
79
- Args:
80
- ttl_seconds: Time-to-live in seconds (None = no expiry)
81
-
82
- Returns:
83
- True if expired, False otherwise
84
- """
85
- if ttl_seconds is None:
86
- return False
87
-
88
- age = time.time() - self.timestamp
89
- return age > ttl_seconds
90
-
91
- def mark_accessed(self) -> None:
92
- """Mark entry as accessed (increment counter)."""
93
- self.access_count += 1
94
-
95
-
96
- class CacheManager:
97
- """
98
- LRU cache manager for search results with TTL support.
99
-
100
- Uses OrderedDict to maintain insertion/access order efficiently.
101
- When cache is full, least recently used entry is evicted.
102
-
103
- Thread-safe when thread_safe=True.
104
- """
105
-
106
- def __init__(
107
- self,
108
- max_size: int = 100,
109
- ttl_seconds: Optional[float] = 300,
110
- thread_safe: bool = False
111
- ):
112
- """
113
- Initialize cache manager.
114
-
115
- Args:
116
- max_size: Maximum number of cache entries
117
- ttl_seconds: Time-to-live for entries (None = no expiry)
118
- thread_safe: Enable thread-safe operations
119
- """
120
- self.max_size = max_size
121
- self.ttl_seconds = ttl_seconds
122
- self.thread_safe = thread_safe
123
-
124
- # LRU cache storage
125
- self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
126
-
127
- # Thread safety lock
128
- self._lock = RLock() if thread_safe else None
129
-
130
- # Statistics
131
- self._hits = 0
132
- self._misses = 0
133
- self._evictions = 0
134
- self._total_size_estimate = 0
135
-
136
- def _hash_key(self, query: str, **kwargs) -> str:
137
- """
138
- Generate cache key from query and parameters.
139
-
140
- Args:
141
- query: Search query
142
- **kwargs: Additional parameters to include in key
143
-
144
- Returns:
145
- Hash string for cache key
146
- """
147
- # Create deterministic key from query + parameters
148
- key_data = {
149
- 'query': query,
150
- **kwargs
151
- }
152
-
153
- # Sort keys for deterministic hashing
154
- key_str = json.dumps(key_data, sort_keys=True)
155
-
156
- # Hash for compact key
157
- return hashlib.sha256(key_str.encode()).hexdigest()[:16]
158
-
159
- def _estimate_size(self, value: Any) -> int:
160
- """
161
- Estimate memory size of cached value.
162
-
163
- Rough estimate for monitoring memory usage.
164
-
165
- Args:
166
- value: Value to estimate
167
-
168
- Returns:
169
- Estimated size in bytes
170
- """
171
- try:
172
- # For lists of tuples (typical search results)
173
- if isinstance(value, list):
174
- # Rough estimate: 100 bytes per result
175
- return len(value) * 100
176
-
177
- # For other types, try JSON serialization size
178
- return len(json.dumps(value, default=str))
179
- except Exception:
180
- # Fallback: assume moderate size
181
- return 1000
182
-
183
- def get(
184
- self,
185
- query: str,
186
- **kwargs
187
- ) -> Optional[Any]:
188
- """
189
- Get cached result for query.
190
-
191
- Args:
192
- query: Search query
193
- **kwargs: Additional parameters used in cache key
194
-
195
- Returns:
196
- Cached result if found and valid, None otherwise
197
- """
198
- key = self._hash_key(query, **kwargs)
199
-
200
- # Thread-safe access
201
- if self._lock:
202
- self._lock.acquire()
203
-
204
- try:
205
- # Check if key exists
206
- if key not in self._cache:
207
- self._misses += 1
208
- return None
209
-
210
- entry = self._cache[key]
211
-
212
- # Check TTL expiry
213
- if entry.is_expired(self.ttl_seconds):
214
- # Remove expired entry
215
- del self._cache[key]
216
- self._total_size_estimate -= entry.size_estimate
217
- self._misses += 1
218
- return None
219
-
220
- # Move to end (mark as recently used)
221
- self._cache.move_to_end(key)
222
- entry.mark_accessed()
223
-
224
- self._hits += 1
225
- return entry.value
226
-
227
- finally:
228
- if self._lock:
229
- self._lock.release()
230
-
231
- def put(
232
- self,
233
- query: str,
234
- value: Any,
235
- **kwargs
236
- ) -> None:
237
- """
238
- Store result in cache.
239
-
240
- Args:
241
- query: Search query
242
- value: Result to cache
243
- **kwargs: Additional parameters used in cache key
244
- """
245
- key = self._hash_key(query, **kwargs)
246
- size_estimate = self._estimate_size(value)
247
-
248
- # Thread-safe access
249
- if self._lock:
250
- self._lock.acquire()
251
-
252
- try:
253
- # Check if key already exists (update)
254
- if key in self._cache:
255
- old_entry = self._cache[key]
256
- self._total_size_estimate -= old_entry.size_estimate
257
- del self._cache[key]
258
-
259
- # Check if cache is full
260
- if len(self._cache) >= self.max_size:
261
- # Evict least recently used (first item)
262
- evicted_key, evicted_entry = self._cache.popitem(last=False)
263
- self._total_size_estimate -= evicted_entry.size_estimate
264
- self._evictions += 1
265
-
266
- # Add new entry (at end = most recently used)
267
- entry = CacheEntry(value, size_estimate)
268
- self._cache[key] = entry
269
- self._total_size_estimate += size_estimate
270
-
271
- finally:
272
- if self._lock:
273
- self._lock.release()
274
-
275
- def invalidate(self, query: str, **kwargs) -> bool:
276
- """
277
- Remove specific entry from cache.
278
-
279
- Args:
280
- query: Search query
281
- **kwargs: Additional parameters
282
-
283
- Returns:
284
- True if entry was removed, False if not found
285
- """
286
- key = self._hash_key(query, **kwargs)
287
-
288
- if self._lock:
289
- self._lock.acquire()
290
-
291
- try:
292
- if key in self._cache:
293
- entry = self._cache[key]
294
- del self._cache[key]
295
- self._total_size_estimate -= entry.size_estimate
296
- return True
297
- return False
298
-
299
- finally:
300
- if self._lock:
301
- self._lock.release()
302
-
303
- def clear(self) -> None:
304
- """Clear entire cache."""
305
- if self._lock:
306
- self._lock.acquire()
307
-
308
- try:
309
- self._cache.clear()
310
- self._total_size_estimate = 0
311
-
312
- finally:
313
- if self._lock:
314
- self._lock.release()
315
-
316
- def evict_expired(self) -> int:
317
- """
318
- Manually evict all expired entries.
319
-
320
- Returns:
321
- Number of entries evicted
322
- """
323
- if self.ttl_seconds is None:
324
- return 0
325
-
326
- if self._lock:
327
- self._lock.acquire()
328
-
329
- try:
330
- expired_keys = [
331
- key for key, entry in self._cache.items()
332
- if entry.is_expired(self.ttl_seconds)
333
- ]
334
-
335
- for key in expired_keys:
336
- entry = self._cache[key]
337
- del self._cache[key]
338
- self._total_size_estimate -= entry.size_estimate
339
-
340
- return len(expired_keys)
341
-
342
- finally:
343
- if self._lock:
344
- self._lock.release()
345
-
346
- def get_stats(self) -> Dict[str, Any]:
347
- """
348
- Get cache statistics.
349
-
350
- Returns:
351
- Dictionary with cache statistics
352
- """
353
- total_requests = self._hits + self._misses
354
- hit_rate = self._hits / total_requests if total_requests > 0 else 0.0
355
-
356
- # Average access count
357
- avg_access_count = 0.0
358
- if self._cache:
359
- avg_access_count = sum(
360
- entry.access_count for entry in self._cache.values()
361
- ) / len(self._cache)
362
-
363
- return {
364
- 'max_size': self.max_size,
365
- 'current_size': len(self._cache),
366
- 'ttl_seconds': self.ttl_seconds,
367
- 'hits': self._hits,
368
- 'misses': self._misses,
369
- 'hit_rate': hit_rate,
370
- 'evictions': self._evictions,
371
- 'total_size_estimate_kb': self._total_size_estimate / 1024,
372
- 'avg_access_count': avg_access_count,
373
- 'thread_safe': self.thread_safe
374
- }
375
-
376
- def get_top_queries(self, limit: int = 10) -> list:
377
- """
378
- Get most frequently accessed queries.
379
-
380
- Args:
381
- limit: Maximum number of queries to return
382
-
383
- Returns:
384
- List of (query_hash, access_count) tuples
385
- """
386
- if self._lock:
387
- self._lock.acquire()
388
-
389
- try:
390
- queries = [
391
- (key, entry.access_count)
392
- for key, entry in self._cache.items()
393
- ]
394
-
395
- queries.sort(key=lambda x: x[1], reverse=True)
396
- return queries[:limit]
397
-
398
- finally:
399
- if self._lock:
400
- self._lock.release()
401
-
402
-
403
- # CLI interface for testing
404
- if __name__ == "__main__":
405
- import random
406
-
407
- print("Cache Manager - Demo")
408
- print("=" * 60)
409
-
410
- # Initialize cache
411
- cache = CacheManager(max_size=5, ttl_seconds=10)
412
-
413
- print("\nCache Configuration:")
414
- stats = cache.get_stats()
415
- print(f" Max Size: {stats['max_size']}")
416
- print(f" TTL: {stats['ttl_seconds']}s")
417
-
418
- # Simulate search queries
419
- queries = [
420
- "python programming",
421
- "javascript web",
422
- "machine learning",
423
- "database sql",
424
- "api rest"
425
- ]
426
-
427
- # Mock search results
428
- def mock_search(query: str) -> None:
429
- """Simulate search result."""
430
- return [
431
- (f"doc_{i}", random.random())
432
- for i in range(3)
433
- ]
434
-
435
- print("\n" + "=" * 60)
436
- print("Simulating Search Operations:")
437
- print("=" * 60)
438
-
439
- # First pass - all cache misses
440
- print("\nPass 1 (Cold Cache):")
441
- for query in queries:
442
- result = cache.get(query)
443
- if result is None:
444
- print(f" MISS: '{query}' - performing search")
445
- result = mock_search(query)
446
- cache.put(query, result)
447
- else:
448
- print(f" HIT: '{query}'")
449
-
450
- # Second pass - all cache hits
451
- print("\nPass 2 (Warm Cache):")
452
- for query in queries:
453
- result = cache.get(query)
454
- if result is None:
455
- print(f" MISS: '{query}' - performing search")
456
- result = mock_search(query)
457
- cache.put(query, result)
458
- else:
459
- print(f" HIT: '{query}'")
460
-
461
- # Third pass - add more queries to trigger eviction
462
- print("\nPass 3 (Cache Overflow - LRU Eviction):")
463
- extra_queries = [
464
- "neural networks",
465
- "cloud computing",
466
- "devops kubernetes"
467
- ]
468
-
469
- for query in extra_queries:
470
- result = cache.get(query)
471
- if result is None:
472
- print(f" MISS: '{query}' - performing search")
473
- result = mock_search(query)
474
- cache.put(query, result)
475
-
476
- # Check if old queries were evicted
477
- print("\nPass 4 (Check Evictions):")
478
- for query in queries[:3]:
479
- result = cache.get(query)
480
- if result is None:
481
- print(f" EVICTED: '{query}'")
482
- else:
483
- print(f" RETAINED: '{query}'")
484
-
485
- # Display statistics
486
- print("\n" + "=" * 60)
487
- print("Cache Statistics:")
488
- print("=" * 60)
489
-
490
- stats = cache.get_stats()
491
- for key, value in stats.items():
492
- if isinstance(value, float):
493
- print(f" {key}: {value:.2f}")
494
- else:
495
- print(f" {key}: {value}")
496
-
497
- # Test TTL expiry
498
- print("\n" + "=" * 60)
499
- print("Testing TTL Expiry:")
500
- print("=" * 60)
501
-
502
- cache_ttl = CacheManager(max_size=10, ttl_seconds=2)
503
- cache_ttl.put("test query", mock_search("test"))
504
-
505
- print("\n Immediately after cache:")
506
- result = cache_ttl.get("test query")
507
- print(f" Result: {'HIT' if result else 'MISS'}")
508
-
509
- print("\n After 3 seconds (exceeds TTL):")
510
- time.sleep(3)
511
- result = cache_ttl.get("test query")
512
- print(f" Result: {'HIT' if result else 'MISS (expired)'}")
513
-
514
- print("\n" + "=" * 60)
515
- print("Performance Impact:")
516
- print(" Cache hit: ~0.1ms overhead")
517
- print(" Cache miss: Standard search time + 0.1ms")
518
- print(" Target hit rate: 30-50% for typical usage")
@@ -1,48 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """SLM v2.8 Compliance Engine — ABAC + Audit Trail + Retention.
4
-
5
- Enterprise-grade access control, tamper-evident audit trail,
6
- and retention policy management for GDPR/EU AI Act/HIPAA.
7
-
8
- Graceful degradation: if this module fails to import,
9
- all agents have full access (v2.7 behavior).
10
- """
11
- import threading
12
- from pathlib import Path
13
- from typing import Optional, Dict, Any
14
-
15
- COMPLIANCE_AVAILABLE = False
16
- _init_error = None
17
-
18
- try:
19
- from .abac_engine import ABACEngine
20
- from .audit_db import AuditDB
21
- COMPLIANCE_AVAILABLE = True
22
- except ImportError as e:
23
- _init_error = str(e)
24
-
25
- _abac_engine: Optional["ABACEngine"] = None
26
- _abac_lock = threading.Lock()
27
-
28
-
29
- def get_abac_engine(config_path: Optional[Path] = None) -> Optional["ABACEngine"]:
30
- """Get or create the ABAC engine singleton."""
31
- global _abac_engine
32
- if not COMPLIANCE_AVAILABLE:
33
- return None
34
- with _abac_lock:
35
- if _abac_engine is None:
36
- try:
37
- _abac_engine = ABACEngine(config_path)
38
- except Exception:
39
- return None
40
- return _abac_engine
41
-
42
-
43
- def get_status() -> Dict[str, Any]:
44
- return {
45
- "compliance_available": COMPLIANCE_AVAILABLE,
46
- "init_error": _init_error,
47
- "abac_active": _abac_engine is not None,
48
- }
@@ -1,149 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Attribute-Based Access Control policy evaluation.
4
-
5
- Evaluates access requests against JSON-defined policies using
6
- subject, resource, and action attributes. Deny-first semantics
7
- ensure any matching deny policy blocks access regardless of
8
- allow policies. When no policies exist, all access is permitted
9
- (backward compatible with v2.7 default-allow behavior).
10
-
11
- Policy format:
12
- {
13
- "name": str, # Human-readable policy name
14
- "effect": str, # "allow" or "deny"
15
- "subjects": dict, # Attribute constraints on the requester
16
- "resources": dict, # Attribute constraints on the resource
17
- "actions": list[str] # Actions this policy applies to
18
- }
19
-
20
- Matching rules:
21
- - "*" matches any value for that attribute
22
- - Specific values require exact match
23
- - All attributes in the policy must match for the policy to apply
24
- """
25
- import json
26
- import logging
27
- from pathlib import Path
28
- from typing import Any, Dict, List, Optional
29
-
30
- logger = logging.getLogger(__name__)
31
-
32
-
33
- class ABACEngine:
34
- """Evaluates ABAC policies for memory access control.
35
-
36
- Deny-first evaluation: if ANY deny policy matches the request,
37
- access is denied. If no deny matches, access is allowed
38
- (default-allow preserves v2.7 backward compatibility).
39
- """
40
-
41
- def __init__(self, config_path: Optional[str] = None) -> None:
42
- self._config_path = config_path
43
- self.policies: List[Dict[str, Any]] = []
44
- if config_path:
45
- self._load_policies(config_path)
46
-
47
- def _load_policies(self, path: str) -> None:
48
- """Load policies from a JSON file. Graceful on missing/invalid."""
49
- try:
50
- raw = Path(path).read_text(encoding="utf-8")
51
- data = json.loads(raw)
52
- if isinstance(data, list):
53
- self.policies = data
54
- logger.info("Loaded %d ABAC policies from %s", len(data), path)
55
- else:
56
- logger.warning("ABAC policy file is not a list: %s", path)
57
- except FileNotFoundError:
58
- logger.debug("No ABAC policy file at %s — default allow", path)
59
- except (json.JSONDecodeError, OSError) as exc:
60
- logger.warning("Failed to parse ABAC policies: %s", exc)
61
-
62
- def evaluate(
63
- self,
64
- subject: Dict[str, Any],
65
- resource: Dict[str, Any],
66
- action: str,
67
- ) -> Dict[str, Any]:
68
- """Evaluate an access request against loaded policies.
69
-
70
- Args:
71
- subject: Attributes of the requester (e.g. agent_id).
72
- resource: Attributes of the target resource.
73
- action: The action being requested (read/write/delete).
74
-
75
- Returns:
76
- Dict with keys: allowed (bool), reason (str),
77
- and policy_name (str) when a specific policy decided.
78
- """
79
- if not self.policies:
80
- return {"allowed": True, "reason": "no_policies_loaded"}
81
-
82
- # Phase 1: check all deny policies first
83
- for policy in self.policies:
84
- if policy.get("effect") != "deny":
85
- continue
86
- if self._matches(policy, subject, resource, action):
87
- return {
88
- "allowed": False,
89
- "reason": "denied_by_policy",
90
- "policy_name": policy.get("name", "unnamed"),
91
- }
92
-
93
- # Phase 2: check allow policies
94
- for policy in self.policies:
95
- if policy.get("effect") != "allow":
96
- continue
97
- if self._matches(policy, subject, resource, action):
98
- return {
99
- "allowed": True,
100
- "reason": "allowed_by_policy",
101
- "policy_name": policy.get("name", "unnamed"),
102
- }
103
-
104
- # Phase 3: no matching policy — default allow (backward compat)
105
- return {"allowed": True, "reason": "no_matching_policy"}
106
-
107
- # ------------------------------------------------------------------
108
- # Internal matching helpers
109
- # ------------------------------------------------------------------
110
-
111
- def _matches(
112
- self,
113
- policy: Dict[str, Any],
114
- subject: Dict[str, Any],
115
- resource: Dict[str, Any],
116
- action: str,
117
- ) -> bool:
118
- """Return True if policy matches the request."""
119
- if not self._action_matches(policy.get("actions", []), action):
120
- return False
121
- if not self._attrs_match(policy.get("subjects", {}), subject):
122
- return False
123
- if not self._attrs_match(policy.get("resources", {}), resource):
124
- return False
125
- return True
126
-
127
- @staticmethod
128
- def _action_matches(policy_actions: List[str], action: str) -> bool:
129
- """Check if the requested action is in the policy's action list."""
130
- if "*" in policy_actions:
131
- return True
132
- return action in policy_actions
133
-
134
- @staticmethod
135
- def _attrs_match(
136
- policy_attrs: Dict[str, Any],
137
- request_attrs: Dict[str, Any],
138
- ) -> bool:
139
- """Check if all policy attribute constraints are satisfied.
140
-
141
- Every key in policy_attrs must either be "*" (match anything)
142
- or exactly equal the corresponding value in request_attrs.
143
- """
144
- for key, expected in policy_attrs.items():
145
- if expected == "*":
146
- continue
147
- if request_attrs.get(key) != expected:
148
- return False
149
- return True