mindforge-memory 6.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. MindForge.py +20 -0
  2. mindforge/__init__.py +111 -0
  3. mindforge/adapters/__init__.py +18 -0
  4. mindforge/adapters/claude_adapter.py +84 -0
  5. mindforge/adapters/crewai_memory.py +76 -0
  6. mindforge/adapters/generic_api.py +183 -0
  7. mindforge/adapters/langgraph_store.py +463 -0
  8. mindforge/adapters/openclaw_adapter.py +91 -0
  9. mindforge/api/__init__.py +1 -0
  10. mindforge/api/server.py +1567 -0
  11. mindforge/cli/__init__.py +1 -0
  12. mindforge/cli/main.py +13607 -0
  13. mindforge/core/__init__.py +37 -0
  14. mindforge/core/embedding.py +847 -0
  15. mindforge/core/encryption.py +597 -0
  16. mindforge/core/indexer.py +363 -0
  17. mindforge/core/mindforge.py +6298 -0
  18. mindforge/core/paths.py +43 -0
  19. mindforge/core/query.py +501 -0
  20. mindforge/core/storage.py +18656 -0
  21. mindforge/core/types.py +314 -0
  22. mindforge/core/version.py +56 -0
  23. mindforge/daemon/__init__.py +0 -0
  24. mindforge/daemon/server.py +277 -0
  25. mindforge/mcp/__init__.py +4 -0
  26. mindforge/mcp/server.py +2559 -0
  27. mindforge/modules/__init__.py +138 -0
  28. mindforge/modules/categorizer.py +151 -0
  29. mindforge/modules/conflict_detector.py +356 -0
  30. mindforge/modules/connectors.py +331 -0
  31. mindforge/modules/context_window.py +337 -0
  32. mindforge/modules/event_bus.py +572 -0
  33. mindforge/modules/evolution.py +210 -0
  34. mindforge/modules/experience.py +402 -0
  35. mindforge/modules/federated.py +722 -0
  36. mindforge/modules/federated_acl.py +393 -0
  37. mindforge/modules/follow_up.py +315 -0
  38. mindforge/modules/hybrid_search.py +565 -0
  39. mindforge/modules/importer.py +374 -0
  40. mindforge/modules/integrator.py +196 -0
  41. mindforge/modules/intent_router.py +359 -0
  42. mindforge/modules/knowledge_graph.py +395 -0
  43. mindforge/modules/memory_compressor.py +592 -0
  44. mindforge/modules/memory_decay.py +474 -0
  45. mindforge/modules/memory_passport.py +250 -0
  46. mindforge/modules/multi_agent.py +534 -0
  47. mindforge/modules/multimodal.py +304 -0
  48. mindforge/modules/outcome_learning.py +186 -0
  49. mindforge/modules/personality.py +275 -0
  50. mindforge/modules/principled_forgetting.py +258 -0
  51. mindforge/modules/privacy.py +736 -0
  52. mindforge/modules/recall.py +263 -0
  53. mindforge/modules/session_focus.py +305 -0
  54. mindforge/modules/share_conflict.py +521 -0
  55. mindforge/modules/skill_extractor.py +340 -0
  56. mindforge/modules/skill_pack.py +205 -0
  57. mindforge/modules/timeline_viz.py +206 -0
  58. mindforge/modules/v6.py +931 -0
  59. mindforge_memory-6.4.1.dist-info/METADATA +281 -0
  60. mindforge_memory-6.4.1.dist-info/RECORD +64 -0
  61. mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
  62. mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
  63. mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
  64. mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,393 @@
1
+ """
2
+ MindForge 联邦记忆细粒度 ACL
3
+
4
+ 为联邦记忆共享提供细粒度访问控制(Access Control List):
5
+ 按「主体(peer)× 资源(记忆/分类/标签/全部)× 操作(read/write/reshare)」
6
+ 配置 allow / deny 规则,支持优先级、信任阈值与过期时间。
7
+
8
+ 评估语义(参考 AWS IAM / RBAC 混合模型):
9
+ - 默认拒绝(default-deny):没有任何规则匹配时拒绝访问
10
+ - 规则按 priority 从高到低评估,同优先级下 deny 优先于 allow
11
+ - 规则可限定 peer 信任阈值(trust_min)与过期时间(expires_at)
12
+ - 所有拒绝决策写入审计日志(acl_deny)
13
+
14
+ 与 federated.py(跨节点对等共享)配套:FederatedMemory 可注入本管理器,
15
+ 在 share_memory / federated_search 时按 ACL 过滤目标节点。
16
+ """
17
+
18
+ import time
19
+ import uuid
20
+ from typing import Any, Dict, List, Optional
21
+
22
+
23
+ # 合法操作集合(* 表示全部操作)
24
+ ACL_OPERATIONS = ("read", "write", "reshare", "*")
25
+
26
+ # 合法资源类型
27
+ ACL_RESOURCE_TYPES = ("memory", "category", "tag", "all")
28
+
29
+ # 合法效果
30
+ ACL_EFFECTS = ("allow", "deny")
31
+
32
+
33
+ class FederatedACLManager:
34
+ """联邦记忆细粒度 ACL 管理器
35
+
36
+ 通过 SQLite 持久化 ACL 规则,随主库一起备份。
37
+ 拒绝决策写入审计日志(复用 storage._add_audit)。
38
+ """
39
+
40
+ def __init__(self, storage):
41
+ self.storage = storage
42
+ self._init_tables()
43
+
44
+ # ===== 内部基础设施 =====
45
+
46
+ def _conn(self):
47
+ return self.storage._get_conn()
48
+
49
+ def _init_tables(self):
50
+ conn = self._conn()
51
+ conn.executescript("""
52
+ CREATE TABLE IF NOT EXISTS federated_acl_rules (
53
+ id TEXT PRIMARY KEY,
54
+ principal TEXT NOT NULL,
55
+ resource_type TEXT NOT NULL DEFAULT 'all',
56
+ resource_value TEXT DEFAULT '',
57
+ operations TEXT NOT NULL DEFAULT 'read',
58
+ effect TEXT NOT NULL DEFAULT 'deny',
59
+ priority INTEGER DEFAULT 100,
60
+ trust_min REAL DEFAULT 0.0,
61
+ expires_at REAL DEFAULT 0.0,
62
+ note TEXT DEFAULT '',
63
+ created_by TEXT DEFAULT '',
64
+ created_at REAL
65
+ );
66
+ CREATE INDEX IF NOT EXISTS idx_acl_principal
67
+ ON federated_acl_rules(principal);
68
+ CREATE INDEX IF NOT EXISTS idx_acl_resource
69
+ ON federated_acl_rules(resource_type, resource_value);
70
+ CREATE INDEX IF NOT EXISTS idx_acl_effect
71
+ ON federated_acl_rules(effect);
72
+ """)
73
+ conn.commit()
74
+
75
+ @staticmethod
76
+ def _parse_resource(resource: str):
77
+ """解析资源表达式。
78
+
79
+ 支持:
80
+ - "all" 或 "*" → (all, "")
81
+ - "memory:<memory_id>" → (memory, <memory_id>)
82
+ - "category:<name>" → (category, <name>)
83
+ - "tag:<name>" → (tag, <name>)
84
+ """
85
+ r = (resource or "").strip()
86
+ if not r or r in ("all", "*"):
87
+ return "all", ""
88
+ if ":" not in r:
89
+ raise ValueError(f"无效资源表达式: {resource}(应为 all / memory:<id> / category:<名> / tag:<名>)")
90
+ rtype, rvalue = r.split(":", 1)
91
+ rtype = rtype.strip().lower()
92
+ rvalue = rvalue.strip()
93
+ if rtype not in ACL_RESOURCE_TYPES:
94
+ raise ValueError(f"无效资源类型: {rtype}(可选: {'/'.join(ACL_RESOURCE_TYPES)})")
95
+ if rtype != "all" and not rvalue:
96
+ raise ValueError(f"资源类型 {rtype} 需要指定值,如 {rtype}:example")
97
+ return rtype, rvalue
98
+
99
+ @staticmethod
100
+ def _normalize_operations(operations) -> str:
101
+ """规范化操作列表为逗号分隔字符串"""
102
+ if isinstance(operations, str):
103
+ ops = [o.strip().lower() for o in operations.split(",")]
104
+ else:
105
+ ops = [str(o).strip().lower() for o in (operations or [])]
106
+ valid = []
107
+ for op in ops:
108
+ if not op:
109
+ continue
110
+ if op not in ACL_OPERATIONS:
111
+ raise ValueError(f"无效操作: {op}(可选: {'/'.join(ACL_OPERATIONS)})")
112
+ valid.append(op)
113
+ if not valid:
114
+ raise ValueError("至少需要指定一个操作(read/write/reshare/*)")
115
+ if "*" in valid:
116
+ return "*"
117
+ # 去重保序
118
+ seen = set()
119
+ uniq = []
120
+ for op in valid:
121
+ if op not in seen:
122
+ seen.add(op)
123
+ uniq.append(op)
124
+ return ",".join(uniq)
125
+
126
+ def _row_to_rule(self, row) -> Dict[str, Any]:
127
+ return {
128
+ "rule_id": row["id"],
129
+ "principal": row["principal"],
130
+ "resource_type": row["resource_type"],
131
+ "resource_value": row["resource_value"],
132
+ "operations": row["operations"],
133
+ "effect": row["effect"],
134
+ "priority": row["priority"],
135
+ "trust_min": row["trust_min"],
136
+ "expires_at": row["expires_at"],
137
+ "note": row["note"],
138
+ "created_by": row["created_by"],
139
+ "created_at": row["created_at"],
140
+ }
141
+
142
+ # ===== 规则管理 =====
143
+
144
+ def add_rule(self, principal: str, resource: str,
145
+ operations: Any = "read", effect: str = "allow",
146
+ priority: int = 100, trust_min: float = 0.0,
147
+ expires_hours: Optional[float] = None,
148
+ note: str = "", created_by: str = "") -> Dict[str, Any]:
149
+ """添加一条 ACL 规则。
150
+
151
+ Args:
152
+ principal: 主体(peer_id),"*" 表示任意节点
153
+ resource: 资源表达式 all / memory:<id> / category:<名> / tag:<名>
154
+ operations: 操作 read/write/reshare/*(字符串逗号分隔或列表)
155
+ effect: allow / deny
156
+ priority: 优先级(越大越先评估,默认 100)
157
+ trust_min: peer 信任阈值(peer 信任度需 >= 该值,规则才生效)
158
+ expires_hours: 规则有效时长(小时),None 表示永久
159
+ note: 备注
160
+ created_by: 创建者
161
+ """
162
+ principal = (principal or "").strip()
163
+ if not principal:
164
+ return {"success": False, "error": "principal 不能为空"}
165
+ if len(principal) > 128:
166
+ return {"success": False, "error": "principal 过长(>128)"}
167
+ try:
168
+ rtype, rvalue = self._parse_resource(resource)
169
+ ops_str = self._normalize_operations(operations)
170
+ except ValueError as e:
171
+ return {"success": False, "error": str(e)}
172
+ effect = (effect or "").strip().lower()
173
+ if effect not in ACL_EFFECTS:
174
+ return {"success": False,
175
+ "error": f"无效 effect: {effect}(可选: {'/'.join(ACL_EFFECTS)})"}
176
+ try:
177
+ priority = max(0, min(10000, int(priority)))
178
+ trust_min = max(0.0, min(1.0, float(trust_min)))
179
+ except (TypeError, ValueError):
180
+ return {"success": False, "error": "priority / trust_min 参数非法"}
181
+
182
+ now = time.time()
183
+ expires_at = now + float(expires_hours) * 3600 if expires_hours else 0.0
184
+ rule_id = f"acl_{uuid.uuid4().hex[:12]}"
185
+
186
+ conn = self._conn()
187
+ conn.execute(
188
+ "INSERT INTO federated_acl_rules (id, principal, resource_type,"
189
+ " resource_value, operations, effect, priority, trust_min,"
190
+ " expires_at, note, created_by, created_at)"
191
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
192
+ (rule_id, principal, rtype, rvalue, ops_str, effect, priority,
193
+ trust_min, expires_at, (note or "").strip()[:500],
194
+ (created_by or "").strip()[:128], now))
195
+ conn.commit()
196
+ self.storage._add_audit("acl_add_rule", rule_id, created_by or principal,
197
+ "", "INTERNAL",
198
+ {"principal": principal, "resource": f"{rtype}:{rvalue}",
199
+ "operations": ops_str, "effect": effect})
200
+ return {"success": True, "rule_id": rule_id, "principal": principal,
201
+ "resource": resource if rtype != "all" else "all",
202
+ "operations": ops_str, "effect": effect, "priority": priority}
203
+
204
+ def remove_rule(self, rule_id: str, actor: str = "") -> Dict[str, Any]:
205
+ """删除一条 ACL 规则"""
206
+ rule_id = (rule_id or "").strip()
207
+ if not rule_id:
208
+ return {"success": False, "error": "rule_id 不能为空"}
209
+ conn = self._conn()
210
+ cur = conn.execute(
211
+ "DELETE FROM federated_acl_rules WHERE id = ?", (rule_id,))
212
+ conn.commit()
213
+ if cur.rowcount == 0:
214
+ return {"success": False, "error": "规则不存在"}
215
+ self.storage._add_audit("acl_remove_rule", rule_id, actor, "",
216
+ "INTERNAL", {})
217
+ return {"success": True, "rule_id": rule_id}
218
+
219
+ def list_rules(self, principal: Optional[str] = None,
220
+ effect: Optional[str] = None,
221
+ limit: int = 200) -> List[Dict[str, Any]]:
222
+ """列出 ACL 规则"""
223
+ limit = max(1, min(1000, int(limit)))
224
+ conn = self._conn()
225
+ sql = "SELECT * FROM federated_acl_rules WHERE 1=1"
226
+ params: List[Any] = []
227
+ if principal:
228
+ sql += " AND (principal = ? OR principal = '*')"
229
+ params.append(principal)
230
+ if effect:
231
+ sql += " AND effect = ?"
232
+ params.append(effect.strip().lower())
233
+ sql += " ORDER BY priority DESC, created_at ASC LIMIT ?"
234
+ params.append(limit)
235
+ rows = conn.execute(sql, params).fetchall()
236
+ return [self._row_to_rule(r) for r in rows]
237
+
238
+ # ===== 访问评估 =====
239
+
240
+ def check_access(self, peer_id: str, memory_id: str,
241
+ operation: str = "read",
242
+ peer_trust: Optional[float] = None,
243
+ memory_category: Optional[str] = None,
244
+ memory_tags: Optional[List[str]] = None,
245
+ is_owner: bool = False) -> Dict[str, Any]:
246
+ """评估某个 peer 对某条记忆的访问权限。
247
+
248
+ Returns:
249
+ {"allowed": bool, "effect": "allow"|"deny",
250
+ "reason": str, "matched_rule": rule_id|None}
251
+ """
252
+ peer_id = (peer_id or "").strip()
253
+ operation = (operation or "read").strip().lower()
254
+ if not peer_id:
255
+ return {"allowed": False, "effect": "deny",
256
+ "reason": "peer_id 为空", "matched_rule": None}
257
+ if is_owner:
258
+ return {"allowed": True, "effect": "allow",
259
+ "reason": "owner 始终允许", "matched_rule": None}
260
+ if operation == "*":
261
+ # v5.4.4 修复 #7:不再静默转为 read,而是返回明确拒绝
262
+ return {"allowed": False, "effect": "deny",
263
+ "reason": "operation='*' 不允许用于查询,请指定具体操作(read/write/reshare)",
264
+ "matched_rule": None}
265
+ if operation not in ACL_OPERATIONS:
266
+ return {"allowed": False, "effect": "deny",
267
+ "reason": f"无效操作: {operation}(可选: read/write/reshare)",
268
+ "matched_rule": None}
269
+
270
+ tags_lower = {str(t).strip().lower() for t in (memory_tags or []) if str(t).strip()}
271
+ now = time.time()
272
+ conn = self._conn()
273
+ # 候选规则:主体匹配(精确或通配)
274
+ # v5.4.2:同优先级下 deny 先于 allow 评估(deny-overrides)
275
+ rows = conn.execute(
276
+ "SELECT * FROM federated_acl_rules"
277
+ " WHERE (principal = ? OR principal = '*')"
278
+ " ORDER BY priority DESC,"
279
+ " CASE effect WHEN 'deny' THEN 0 ELSE 1 END,"
280
+ " created_at ASC",
281
+ (peer_id,)).fetchall()
282
+
283
+ for row in rows:
284
+ # 过期规则跳过
285
+ if row["expires_at"] and row["expires_at"] > 0 and row["expires_at"] < now:
286
+ continue
287
+ # 信任阈值:仅当提供了 peer_trust 时校验
288
+ if row["trust_min"] and row["trust_min"] > 0:
289
+ if peer_trust is None or peer_trust < row["trust_min"]:
290
+ continue
291
+ # 操作匹配
292
+ ops = row["operations"]
293
+ if ops != "*" and operation not in {o.strip() for o in ops.split(",")}:
294
+ continue
295
+ # 资源匹配
296
+ rtype = row["resource_type"]
297
+ rvalue = row["resource_value"]
298
+ matched = False
299
+ if rtype == "all":
300
+ matched = True
301
+ elif rtype == "memory":
302
+ matched = (memory_id and rvalue == memory_id)
303
+ elif rtype == "category":
304
+ matched = (memory_category is not None
305
+ and str(memory_category).strip() == rvalue)
306
+ elif rtype == "tag":
307
+ matched = rvalue.lower() in tags_lower
308
+ if not matched:
309
+ continue
310
+
311
+ # 命中规则
312
+ allowed = (row["effect"] == "allow")
313
+ result = {
314
+ "allowed": allowed,
315
+ "effect": row["effect"],
316
+ "reason": f"命中规则 {row['id']}({row['effect']}, "
317
+ f"{rtype}:{rvalue or '*'}, priority={row['priority']})",
318
+ "matched_rule": row["id"],
319
+ }
320
+ if not allowed:
321
+ self._audit_deny(peer_id, memory_id, operation, row["id"])
322
+ return result
323
+
324
+ # 默认拒绝
325
+ self._audit_deny(peer_id, memory_id, operation, None)
326
+ return {"allowed": False, "effect": "deny",
327
+ "reason": "默认拒绝:无任何匹配规则(default-deny)",
328
+ "matched_rule": None}
329
+
330
+ def filter_peers(self, memory_id: str, peer_ids: List[str],
331
+ operation: str = "read",
332
+ trust_map: Optional[Dict[str, float]] = None,
333
+ memory_category: Optional[str] = None,
334
+ memory_tags: Optional[List[str]] = None) -> Dict[str, Any]:
335
+ """对一组 peer 做访问过滤,返回允许/拒绝明细。
336
+
337
+ 供 FederatedMemory.share_memory / federated_search 批量过滤使用。
338
+ """
339
+ trust_map = trust_map or {}
340
+ allowed_list: List[str] = []
341
+ denied_map: Dict[str, str] = {}
342
+ for pid in (peer_ids or []):
343
+ decision = self.check_access(
344
+ peer_id=pid, memory_id=memory_id, operation=operation,
345
+ peer_trust=trust_map.get(pid),
346
+ memory_category=memory_category, memory_tags=memory_tags)
347
+ if decision["allowed"]:
348
+ allowed_list.append(pid)
349
+ else:
350
+ denied_map[pid] = decision["reason"]
351
+ return {"memory_id": memory_id, "operation": operation,
352
+ "allowed": allowed_list, "denied": denied_map,
353
+ "allowed_count": len(allowed_list),
354
+ "denied_count": len(denied_map)}
355
+
356
+ def _audit_deny(self, peer_id: str, memory_id: str,
357
+ operation: str, rule_id: Optional[str]):
358
+ """拒绝决策写入审计日志"""
359
+ try:
360
+ self.storage._add_audit(
361
+ "acl_deny", memory_id or "", peer_id, "", "INTERNAL",
362
+ {"operation": operation, "matched_rule": rule_id})
363
+ except Exception:
364
+ # 审计失败不影响访问决策
365
+ pass
366
+
367
+ # ===== 统计 =====
368
+
369
+ def acl_stats(self) -> Dict[str, Any]:
370
+ """ACL 统计:规则分布 + 拒绝审计计数"""
371
+ conn = self._conn()
372
+ total = conn.execute(
373
+ "SELECT COUNT(*) AS c FROM federated_acl_rules").fetchone()["c"]
374
+ by_effect = conn.execute(
375
+ "SELECT effect, COUNT(*) AS c FROM federated_acl_rules"
376
+ " GROUP BY effect").fetchall()
377
+ by_resource = conn.execute(
378
+ "SELECT resource_type, COUNT(*) AS c FROM federated_acl_rules"
379
+ " GROUP BY resource_type").fetchall()
380
+ now = time.time()
381
+ expired = conn.execute(
382
+ "SELECT COUNT(*) AS c FROM federated_acl_rules"
383
+ " WHERE expires_at > 0 AND expires_at < ?", (now,)).fetchone()["c"]
384
+ deny_events = conn.execute(
385
+ "SELECT COUNT(*) AS c FROM audit_log WHERE action = 'acl_deny'"
386
+ ).fetchone()["c"]
387
+ return {
388
+ "total_rules": total,
389
+ "by_effect": {r["effect"]: r["c"] for r in by_effect},
390
+ "by_resource_type": {r["resource_type"]: r["c"] for r in by_resource},
391
+ "expired_rules": expired,
392
+ "deny_audit_events": deny_events,
393
+ }