mindforge-memory 6.4.3__tar.gz → 6.4.6__tar.gz

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 (140) hide show
  1. {mindforge_memory-6.4.3/mindforge_memory.egg-info → mindforge_memory-6.4.6}/PKG-INFO +21 -5
  2. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/README.md +20 -4
  3. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/indexer.py +25 -6
  4. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/mindforge.py +14 -0
  5. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/storage.py +14 -0
  6. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/version.py +2 -2
  7. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/connectors.py +9 -1
  8. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6/mindforge_memory.egg-info}/PKG-INFO +21 -5
  9. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge_memory.egg-info/SOURCES.txt +3 -0
  10. mindforge_memory-6.4.6/tests/test_trash_hard_delete.py +115 -0
  11. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v568_fixes.py +1 -1
  12. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v575_release_consistency.py +2 -2
  13. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v612_fixes.py +2 -2
  14. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v643_fixes.py +5 -3
  15. mindforge_memory-6.4.6/tests/test_v645_fixes.py +108 -0
  16. mindforge_memory-6.4.6/tests/test_v646_fixes.py +92 -0
  17. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/LICENSE +0 -0
  18. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/MindForge.py +0 -0
  19. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/__init__.py +0 -0
  20. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/__init__.py +0 -0
  21. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/claude_adapter.py +0 -0
  22. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/crewai_memory.py +0 -0
  23. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/generic_api.py +0 -0
  24. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/langgraph_store.py +0 -0
  25. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/adapters/openclaw_adapter.py +0 -0
  26. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/api/__init__.py +0 -0
  27. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/api/server.py +0 -0
  28. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/cli/__init__.py +0 -0
  29. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/cli/main.py +0 -0
  30. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/__init__.py +0 -0
  31. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/embedding.py +0 -0
  32. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/encryption.py +0 -0
  33. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/paths.py +0 -0
  34. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/query.py +0 -0
  35. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/core/types.py +0 -0
  36. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/daemon/__init__.py +0 -0
  37. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/daemon/server.py +0 -0
  38. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/mcp/__init__.py +0 -0
  39. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/mcp/server.py +0 -0
  40. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/__init__.py +0 -0
  41. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/categorizer.py +0 -0
  42. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/conflict_detector.py +0 -0
  43. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/context_window.py +0 -0
  44. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/event_bus.py +0 -0
  45. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/evolution.py +0 -0
  46. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/experience.py +0 -0
  47. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/federated.py +0 -0
  48. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/federated_acl.py +0 -0
  49. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/follow_up.py +0 -0
  50. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/hybrid_search.py +0 -0
  51. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/importer.py +0 -0
  52. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/integrator.py +0 -0
  53. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/intent_router.py +0 -0
  54. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/knowledge_graph.py +0 -0
  55. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/memory_compressor.py +0 -0
  56. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/memory_decay.py +0 -0
  57. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/memory_passport.py +0 -0
  58. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/multi_agent.py +0 -0
  59. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/multimodal.py +0 -0
  60. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/outcome_learning.py +0 -0
  61. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/personality.py +0 -0
  62. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/principled_forgetting.py +0 -0
  63. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/privacy.py +0 -0
  64. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/recall.py +0 -0
  65. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/session_focus.py +0 -0
  66. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/share_conflict.py +0 -0
  67. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/skill_extractor.py +0 -0
  68. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/skill_pack.py +0 -0
  69. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/timeline_viz.py +0 -0
  70. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge/modules/v6.py +0 -0
  71. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge_memory.egg-info/dependency_links.txt +0 -0
  72. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge_memory.egg-info/entry_points.txt +0 -0
  73. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge_memory.egg-info/requires.txt +0 -0
  74. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/mindforge_memory.egg-info/top_level.txt +0 -0
  75. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/pyproject.toml +0 -0
  76. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/setup.cfg +0 -0
  77. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/setup.py +0 -0
  78. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_core.py +0 -0
  79. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_security_hardening.py +0 -0
  80. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v546_features.py +0 -0
  81. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v547_fixes.py +0 -0
  82. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v548_features.py +0 -0
  83. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v550_features.py +0 -0
  84. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v552_features.py +0 -0
  85. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v554_features.py +0 -0
  86. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v556_features.py +0 -0
  87. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v557_banner_insight_wal.py +0 -0
  88. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v557_security_fixes.py +0 -0
  89. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v557_webhook_sig_export.py +0 -0
  90. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v558_audit_fixes.py +0 -0
  91. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v558_features.py +0 -0
  92. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v560_decay_gc.py +0 -0
  93. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v560_kdf_versioning.py +0 -0
  94. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v560_module_coverage.py +0 -0
  95. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v561_security.py +0 -0
  96. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v562_p2_fixes.py +0 -0
  97. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v563_entry_coverage.py +0 -0
  98. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v563_perf_fixes.py +0 -0
  99. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v565_security.py +0 -0
  100. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v566_encrypted_search.py +0 -0
  101. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v567_security_fixes.py +0 -0
  102. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v569_fts_sync.py +0 -0
  103. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v570_coverage_expansion.py +0 -0
  104. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v570_drama_and_utils.py +0 -0
  105. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v570_encrypted_fallback.py +0 -0
  106. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v571_fixes.py +0 -0
  107. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v572_security.py +0 -0
  108. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v573_encrypted_export.py +0 -0
  109. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v573_security.py +0 -0
  110. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v576_bitemporal.py +0 -0
  111. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v576_gdpr.py +0 -0
  112. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v577_agent_governance.py +0 -0
  113. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v578_embedding.py +0 -0
  114. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v579_graph.py +0 -0
  115. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v580_experience.py +0 -0
  116. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v5812_bugfixes.py +0 -0
  117. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v583_security.py +0 -0
  118. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v584_adapters.py +0 -0
  119. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v585_benchmark.py +0 -0
  120. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v586_federated_mcp.py +0 -0
  121. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v590_features.py +0 -0
  122. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v592_features.py +0 -0
  123. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v593_features.py +0 -0
  124. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v595_features.py +0 -0
  125. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v600_forgetting_followup.py +0 -0
  126. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v600_import_timeline.py +0 -0
  127. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v600_multi_agent_formal.py +0 -0
  128. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v600_outcome_passport.py +0 -0
  129. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v600_skillpack_evolution.py +0 -0
  130. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v611_fixes.py +0 -0
  131. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v612_cascade_fixes.py +0 -0
  132. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v613_integrity.py +0 -0
  133. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v620_growth_governance.py +0 -0
  134. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v630_search_explain.py +0 -0
  135. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v631_scrypt.py +0 -0
  136. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v632_explain_interfaces.py +0 -0
  137. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v635_features.py +0 -0
  138. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v635_integration.py +0 -0
  139. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v636_fixes.py +0 -0
  140. {mindforge_memory-6.4.3 → mindforge_memory-6.4.6}/tests/test_v6_ports.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindforge-memory
3
- Version: 6.4.3
3
+ Version: 6.4.6
4
4
  Summary: AI Agent 终身记忆系统 - 四层记忆架构 · 知识图谱 · 多模态支持
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/opok-ops/MindForge
@@ -36,11 +36,11 @@ Dynamic: license-file
36
36
 
37
37
  MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
38
38
 
39
- > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
39
+ > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.5: no team, no funding, 1,243 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
40
40
 
41
41
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
42
42
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
43
- [![Version](https://img.shields.io/badge/version-6.4.3-green.svg)](https://github.com/opok-ops/MindForge/releases)
43
+ [![Version](https://img.shields.io/badge/version-6.4.6-green.svg)](https://github.com/opok-ops/MindForge/releases)
44
44
  [![Stars](https://img.shields.io/github/stars/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/stargazers)
45
45
  [![Forks](https://img.shields.io/github/forks/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/forks)
46
46
  [![Open Issues](https://img.shields.io/github/issues/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/issues)
@@ -94,10 +94,26 @@ MindForge search "what does user like"
94
94
  MindForge stats
95
95
  ```
96
96
 
97
+
98
+ ## Scale & Quality Benchmarks (v6.4.6, measured)
99
+
100
+ | Scale | add throughput | search p50 (rare token) | search p50 (realistic mixed) | db size |
101
+ |---|---|---|---|---|
102
+ | 200 entries (baseline) | — | 1.8 ms | — | <1 MB |
103
+ | 10,000 | 1,080/s | ~9.5 ms | ~16 ms | 10 MB |
104
+ | 100,000 | ~558/s | 7.4 ms | 143 ms | 98 MB |
105
+
106
+ - Retrieval quality (fixed-seed, `benchmarks/quality_bench.py`, 2k entries): needle Recall@1 = 1.0, MRR = 1.0, topic Recall@1 = 1.0, noise false-recall = 0.
107
+ - Known characteristic: all-common-word queries on synthetic template data fall back to O(N) scan (documented, not a defect); realistic queries hit the pruned inverted index.
108
+ - Full LLM-judged benchmarks (LoCoMo / LongMemEval) — interface ready, not yet run (requires external LLM API key).
109
+ - TF-IDF degraded mode process memory at 100k: ~700 MB. Index LRU cap: 100,000 docs (MINDFORGE_INDEX_MAX_DOCS).
110
+
97
111
  ## What's New
98
112
 
99
- v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
113
+ v6.1.3 – v6.4.5 shipped in ten days (2026-11-01 → 11-10; two same-day releases), guarded by **1,244 tests** (1,243 passing, 1 environment-skipped).
100
114
 
115
+ - **v6.4.6 — scale validation & retrieval benchmarks** (2026-11-10). First 100,000-entry stress test: add throughput ~600/s (linear), ~98MB db, memory peak ~700MB (TF-IDF degraded mode), search p50 7.4ms (rare token, −92% vs pre-fix) / 143ms (realistic mixed); pathological all-common query 421→218ms (−48%). Fixed a real bottleneck found by the stress test: Chinese long-query p50 was 421ms — profile showed low-IDF common words degenerating the inverted index into full-corpus scans. Fix: two-tier pruning (relative-eps + standard IR max_df stop-wording for df > 0.5×N) + heapq.nlargest; pathological all-common-word queries (synthetic-template data only) honestly documented as known O(N) characteristic, realistic queries verified unaffected (8k A/B, 30k side-by-side: 43ms mixed vs 86ms pathological). Shipped reproducible benchmarks (`benchmarks/scale_bench.py`, `benchmarks/quality_bench.py`): needle Recall@1 = 1.0 / MRR = 1.0, zero noise false-recall. Full LLM-judged benchmarks (LoCoMo/LongMemEval) deferred — needs external LLM API key. 7 new guards.
116
+ - **v6.4.5 — audit-driven fixes** (2026-11-10; v6.4.4 skipped by maintainer decision). Two inverse doc-code inconsistencies (v6.4.3 fixed "claimed but not done"; this one fixes "done but not tracked"): `connectors.py` outgoing HTTP User-Agent was hardcoded `MindForge-Connector/5.8.9` — lying about the version to every target site since the 5.8.9 era (`event_bus.py` fixed the same disease in v5.6.8; this one slipped through). Now dynamic from the single version truth. Website "core modules" count was stale (17 in prose, 15 in stat card, 31 measured) — both fixed to the measured 31; MCP tool count 87 verified exact, untouched. Audit archive (not a defect): WAL auto-checkpoint verified healthy under daemon-like load — 4 rounds × 400 mixed ops, WAL converged at ~4.1MB (default 1000-page threshold), TRUNCATE resets to zero. 5 new guards in `tests/test_v645_fixes.py`.
101
117
  - **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
102
118
  - **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
103
119
  - **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
@@ -167,7 +183,7 @@ v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228
167
183
 
168
184
  ```
169
185
  ┌──────────────────────────────────────────────────────┐
170
- │ MindForge v6.4.3 │
186
+ │ MindForge v6.4.6 │
171
187
  │ Cognitive Layer Personality · KnowledgeGraph │
172
188
  │ MemoryEvolution · FederatedMemory │
173
189
  ├──────────────────────────────────────────────────────┤
@@ -6,11 +6,11 @@
6
6
 
7
7
  MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
8
8
 
9
- > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
9
+ > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.5: no team, no funding, 1,243 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
10
10
 
11
11
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
13
- [![Version](https://img.shields.io/badge/version-6.4.3-green.svg)](https://github.com/opok-ops/MindForge/releases)
13
+ [![Version](https://img.shields.io/badge/version-6.4.6-green.svg)](https://github.com/opok-ops/MindForge/releases)
14
14
  [![Stars](https://img.shields.io/github/stars/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/stargazers)
15
15
  [![Forks](https://img.shields.io/github/forks/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/forks)
16
16
  [![Open Issues](https://img.shields.io/github/issues/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/issues)
@@ -64,10 +64,26 @@ MindForge search "what does user like"
64
64
  MindForge stats
65
65
  ```
66
66
 
67
+
68
+ ## Scale & Quality Benchmarks (v6.4.6, measured)
69
+
70
+ | Scale | add throughput | search p50 (rare token) | search p50 (realistic mixed) | db size |
71
+ |---|---|---|---|---|
72
+ | 200 entries (baseline) | — | 1.8 ms | — | <1 MB |
73
+ | 10,000 | 1,080/s | ~9.5 ms | ~16 ms | 10 MB |
74
+ | 100,000 | ~558/s | 7.4 ms | 143 ms | 98 MB |
75
+
76
+ - Retrieval quality (fixed-seed, `benchmarks/quality_bench.py`, 2k entries): needle Recall@1 = 1.0, MRR = 1.0, topic Recall@1 = 1.0, noise false-recall = 0.
77
+ - Known characteristic: all-common-word queries on synthetic template data fall back to O(N) scan (documented, not a defect); realistic queries hit the pruned inverted index.
78
+ - Full LLM-judged benchmarks (LoCoMo / LongMemEval) — interface ready, not yet run (requires external LLM API key).
79
+ - TF-IDF degraded mode process memory at 100k: ~700 MB. Index LRU cap: 100,000 docs (MINDFORGE_INDEX_MAX_DOCS).
80
+
67
81
  ## What's New
68
82
 
69
- v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
83
+ v6.1.3 – v6.4.5 shipped in ten days (2026-11-01 → 11-10; two same-day releases), guarded by **1,244 tests** (1,243 passing, 1 environment-skipped).
70
84
 
85
+ - **v6.4.6 — scale validation & retrieval benchmarks** (2026-11-10). First 100,000-entry stress test: add throughput ~600/s (linear), ~98MB db, memory peak ~700MB (TF-IDF degraded mode), search p50 7.4ms (rare token, −92% vs pre-fix) / 143ms (realistic mixed); pathological all-common query 421→218ms (−48%). Fixed a real bottleneck found by the stress test: Chinese long-query p50 was 421ms — profile showed low-IDF common words degenerating the inverted index into full-corpus scans. Fix: two-tier pruning (relative-eps + standard IR max_df stop-wording for df > 0.5×N) + heapq.nlargest; pathological all-common-word queries (synthetic-template data only) honestly documented as known O(N) characteristic, realistic queries verified unaffected (8k A/B, 30k side-by-side: 43ms mixed vs 86ms pathological). Shipped reproducible benchmarks (`benchmarks/scale_bench.py`, `benchmarks/quality_bench.py`): needle Recall@1 = 1.0 / MRR = 1.0, zero noise false-recall. Full LLM-judged benchmarks (LoCoMo/LongMemEval) deferred — needs external LLM API key. 7 new guards.
86
+ - **v6.4.5 — audit-driven fixes** (2026-11-10; v6.4.4 skipped by maintainer decision). Two inverse doc-code inconsistencies (v6.4.3 fixed "claimed but not done"; this one fixes "done but not tracked"): `connectors.py` outgoing HTTP User-Agent was hardcoded `MindForge-Connector/5.8.9` — lying about the version to every target site since the 5.8.9 era (`event_bus.py` fixed the same disease in v5.6.8; this one slipped through). Now dynamic from the single version truth. Website "core modules" count was stale (17 in prose, 15 in stat card, 31 measured) — both fixed to the measured 31; MCP tool count 87 verified exact, untouched. Audit archive (not a defect): WAL auto-checkpoint verified healthy under daemon-like load — 4 rounds × 400 mixed ops, WAL converged at ~4.1MB (default 1000-page threshold), TRUNCATE resets to zero. 5 new guards in `tests/test_v645_fixes.py`.
71
87
  - **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
72
88
  - **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
73
89
  - **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
@@ -137,7 +153,7 @@ v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228
137
153
 
138
154
  ```
139
155
  ┌──────────────────────────────────────────────────────┐
140
- │ MindForge v6.4.3 │
156
+ │ MindForge v6.4.6 │
141
157
  │ Cognitive Layer Personality · KnowledgeGraph │
142
158
  │ MemoryEvolution · FederatedMemory │
143
159
  ├──────────────────────────────────────────────────────┤
@@ -3,6 +3,7 @@ MindForge v5.0 索引引擎
3
3
  支持 TF-IDF、向量索引、FTS5 全文检索
4
4
  """
5
5
 
6
+ import heapq
6
7
  import math
7
8
  import os
8
9
  import re
@@ -151,17 +152,35 @@ class VectorIndex:
151
152
  if query_norm == 0:
152
153
  return [(doc_id, 0.0) for doc_id in list(self.vectors.keys())[:top_k]]
153
154
 
155
+ # v6.4.6 perf: 低区分度查询维剪枝。100k 规模压测发现中文长查询
156
+ # p50 421ms,profile 定位到常见词(如「条目/陈述性/记忆」)的倒排
157
+ # 链全量遍历。两层剪枝:eps 相对阈值 + max_df 停用词(df > 0.5×N
158
+ # 的维是平滑 IDF 下 qval≈tf×1 的全库词——遍历整条链却无区分度,
159
+ # 所有文档点积同加近似常数,top-K 排序不变;30k profile 实证
160
+ # eps 剪不掉全库词,必须按链长剪)。
161
+ _EPS_REL = 0.02
162
+ _half = 0.5 * len(self.vectors)
163
+ active = [(idx, qval) for idx, qval in q.items()
164
+ if qval > _EPS_REL * query_norm
165
+ and len(self._postings.get(idx, ())) <= _half]
166
+ if not active:
167
+ # 全部维度被剪(极端退化查询):退回原语义,保证不空召回
168
+ active = list(q.items())
169
+
154
170
  # 倒排链累加点积:只访问与查询共享非零维度的候选文档
155
171
  dots: Dict[str, float] = {}
156
- for idx, qval in q.items():
172
+ for idx, qval in active:
157
173
  for doc_id, dval in self._postings.get(idx, {}).items():
158
174
  dots[doc_id] = dots.get(doc_id, 0.0) + qval * dval
159
175
 
160
- scores = [(doc_id, dot / (query_norm * self._norms[doc_id]))
161
- for doc_id, dot in dots.items()
162
- if self._norms.get(doc_id, 0.0) > 0]
163
- scores.sort(key=lambda x: x[1], reverse=True)
164
- return scores[:top_k]
176
+ # v6.4.6 perf: heapq.nlargest 替代全量 sort——候选集大时
177
+ # O(n·log k) 优于 O(n·log n),且免建全量列表排序的中间开销。
178
+ return heapq.nlargest(
179
+ top_k,
180
+ ((doc_id, dot / (query_norm * self._norms[doc_id]))
181
+ for doc_id, dot in dots.items()
182
+ if self._norms.get(doc_id, 0.0) > 0),
183
+ key=lambda x: x[1])
165
184
 
166
185
  def _cosine(self, v1, v2) -> float:
167
186
  """保留原始接口(向后兼容),稀疏/稠密输入均支持"""
@@ -1230,6 +1230,20 @@ class MindForge:
1230
1230
  # v5.6.2 安全修复:隐私访问控制
1231
1231
  entry = self._storage.get_memory(memory_id, actor, session_id)
1232
1232
  if entry is None:
1233
+ # v6.4.6 fix: 硬删已软删(trash)条目的数据主权路径。get_memory
1234
+ # 过滤 trash 条目,此前 delete(hard_delete=True) 对回收站内容
1235
+ # 静默返回 False(CLI 显示「记忆不存在」,误导用户以为已彻底
1236
+ # 清除,物理数据实际仍在库中)。GDPR erase 语义(storage 权利
1237
+ # 清单 2122 行)要求:能软删活条目,就必须能物理清除其 trash
1238
+ # 态。隐私读检查不适用此分支——内容已在 trash,仅物理清除;
1239
+ # storage.delete_memory 自带审计写入(v5.6.1 同事务)。
1240
+ if hard_delete:
1241
+ if self._storage.is_in_trash(memory_id):
1242
+ success = self._storage.delete_memory(
1243
+ memory_id, actor, session_id, hard_delete=True)
1244
+ if success:
1245
+ self._index.remove_memory(memory_id)
1246
+ return success
1233
1247
  return False
1234
1248
  ok, _reason = self.privacy_engine.check_access(entry, actor, session_id)
1235
1249
  if not ok:
@@ -2959,6 +2959,20 @@ class StorageEngine:
2959
2959
  return len(ids)
2960
2960
 
2961
2961
  @_with_rollback
2962
+ def is_in_trash(self, entry_id: str) -> bool:
2963
+ """v6.4.6: 判定条目是否处于回收站(软删态)。
2964
+
2965
+ 服务 delete(hard_delete=True) 的数据主权路径:get_memory 过滤
2966
+ trash 条目(WHERE category != 'trash'),上层 None 分支需要此
2967
+ 轻量查询放行物理清除。幂等,只读,不产生任何写副作用。
2968
+ """
2969
+ conn = self._get_conn()
2970
+ row = conn.execute(
2971
+ "SELECT 1 FROM memories WHERE id = ? AND category = 'trash'",
2972
+ (entry_id,),
2973
+ ).fetchone()
2974
+ return row is not None
2975
+
2962
2976
  def delete_memory(self, entry_id: str,
2963
2977
  actor: str = "", session_id: str = "",
2964
2978
  hard_delete: bool = False) -> bool:
@@ -20,10 +20,10 @@ core/mindforge.py、cli/main.py、mcp/server.py),每处都带一份硬编码
20
20
  from typing import Tuple
21
21
 
22
22
  #: 当前发布版本(唯一真值)
23
- __version__: str = "6.4.3"
23
+ __version__: str = "6.4.6"
24
24
 
25
25
  #: 结构化版本元组,便于比较(如 `VERSION_INFO >= (6, 1, 0)`)
26
- VERSION_INFO: Tuple[int, int, int] = (6, 4, 3)
26
+ VERSION_INFO: Tuple[int, int, int] = (6, 4, 6)
27
27
 
28
28
  #: 下一个大版本的目标版本号(供 v6 端口预留引用)
29
29
  NEXT_MAJOR_TARGET: str = "7.0.0"
@@ -18,6 +18,10 @@ import ipaddress
18
18
  import ssl
19
19
  import urllib.parse
20
20
  import urllib.request
21
+
22
+ # v6.4.5: User-Agent 版本动态化(唯一真值 mindforge/core/version.py)。
23
+ # version.py 仅依赖 typing,无循环导入风险。
24
+ from mindforge.core.version import __version__
21
25
  from abc import ABC, abstractmethod
22
26
  from typing import Any, Dict, List, Optional, Type
23
27
 
@@ -308,7 +312,11 @@ class UrlConnector(BaseConnector):
308
312
  _FixedIPHTTPSHandler(fixed_ip),
309
313
  )
310
314
  req = urllib.request.Request(
311
- source, headers={"User-Agent": "MindForge-Connector/5.8.9"})
315
+ # v6.4.5 fix: 此前硬编码 "MindForge-Connector/5.8.9"(5.8.9 时代
316
+ # 写死后再未跟上),对外请求谎报版本。改为动态引用唯一真值
317
+ # mindforge/core/version.py(与 event_bus.py / cli URL Importer
318
+ # 同款模式,v5.6.8 起已验证)。
319
+ source, headers={"User-Agent": f"MindForge-Connector/{__version__}"})
312
320
  with opener.open(req, timeout=kwargs.get("timeout", 15)) as resp:
313
321
  content_type = resp.headers.get("Content-Type", "")
314
322
  data = _read_limited(resp)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindforge-memory
3
- Version: 6.4.3
3
+ Version: 6.4.6
4
4
  Summary: AI Agent 终身记忆系统 - 四层记忆架构 · 知识图谱 · 多模态支持
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/opok-ops/MindForge
@@ -36,11 +36,11 @@ Dynamic: license-file
36
36
 
37
37
  MindForge gives AI agents a persistent, structured long-term memory: a four-tier lifecycle model, knowledge graphs, hybrid retrieval, end-to-end encryption, and MCP / CLI / REST interfaces. All data stays on your machine.
38
38
 
39
- > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.3: no team, no funding, 1,227 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
39
+ > **The story** — one developer, seven months, from v1.0 (March 2026) to v6.4.5: no team, no funding, 1,243 passing tests. [Read the journey →](https://opok-ops.github.io/MindForge/#developer)
40
40
 
41
41
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
42
42
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
43
- [![Version](https://img.shields.io/badge/version-6.4.3-green.svg)](https://github.com/opok-ops/MindForge/releases)
43
+ [![Version](https://img.shields.io/badge/version-6.4.6-green.svg)](https://github.com/opok-ops/MindForge/releases)
44
44
  [![Stars](https://img.shields.io/github/stars/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/stargazers)
45
45
  [![Forks](https://img.shields.io/github/forks/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/forks)
46
46
  [![Open Issues](https://img.shields.io/github/issues/opok-ops/MindForge.svg)](https://github.com/opok-ops/MindForge/issues)
@@ -94,10 +94,26 @@ MindForge search "what does user like"
94
94
  MindForge stats
95
95
  ```
96
96
 
97
+
98
+ ## Scale & Quality Benchmarks (v6.4.6, measured)
99
+
100
+ | Scale | add throughput | search p50 (rare token) | search p50 (realistic mixed) | db size |
101
+ |---|---|---|---|---|
102
+ | 200 entries (baseline) | — | 1.8 ms | — | <1 MB |
103
+ | 10,000 | 1,080/s | ~9.5 ms | ~16 ms | 10 MB |
104
+ | 100,000 | ~558/s | 7.4 ms | 143 ms | 98 MB |
105
+
106
+ - Retrieval quality (fixed-seed, `benchmarks/quality_bench.py`, 2k entries): needle Recall@1 = 1.0, MRR = 1.0, topic Recall@1 = 1.0, noise false-recall = 0.
107
+ - Known characteristic: all-common-word queries on synthetic template data fall back to O(N) scan (documented, not a defect); realistic queries hit the pruned inverted index.
108
+ - Full LLM-judged benchmarks (LoCoMo / LongMemEval) — interface ready, not yet run (requires external LLM API key).
109
+ - TF-IDF degraded mode process memory at 100k: ~700 MB. Index LRU cap: 100,000 docs (MINDFORGE_INDEX_MAX_DOCS).
110
+
97
111
  ## What's New
98
112
 
99
- v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228 tests** (1,227 passing, 1 environment-skipped).
113
+ v6.1.3 – v6.4.5 shipped in ten days (2026-11-01 → 11-10; two same-day releases), guarded by **1,244 tests** (1,243 passing, 1 environment-skipped).
100
114
 
115
+ - **v6.4.6 — scale validation & retrieval benchmarks** (2026-11-10). First 100,000-entry stress test: add throughput ~600/s (linear), ~98MB db, memory peak ~700MB (TF-IDF degraded mode), search p50 7.4ms (rare token, −92% vs pre-fix) / 143ms (realistic mixed); pathological all-common query 421→218ms (−48%). Fixed a real bottleneck found by the stress test: Chinese long-query p50 was 421ms — profile showed low-IDF common words degenerating the inverted index into full-corpus scans. Fix: two-tier pruning (relative-eps + standard IR max_df stop-wording for df > 0.5×N) + heapq.nlargest; pathological all-common-word queries (synthetic-template data only) honestly documented as known O(N) characteristic, realistic queries verified unaffected (8k A/B, 30k side-by-side: 43ms mixed vs 86ms pathological). Shipped reproducible benchmarks (`benchmarks/scale_bench.py`, `benchmarks/quality_bench.py`): needle Recall@1 = 1.0 / MRR = 1.0, zero noise false-recall. Full LLM-judged benchmarks (LoCoMo/LongMemEval) deferred — needs external LLM API key. 7 new guards.
116
+ - **v6.4.5 — audit-driven fixes** (2026-11-10; v6.4.4 skipped by maintainer decision). Two inverse doc-code inconsistencies (v6.4.3 fixed "claimed but not done"; this one fixes "done but not tracked"): `connectors.py` outgoing HTTP User-Agent was hardcoded `MindForge-Connector/5.8.9` — lying about the version to every target site since the 5.8.9 era (`event_bus.py` fixed the same disease in v5.6.8; this one slipped through). Now dynamic from the single version truth. Website "core modules" count was stale (17 in prose, 15 in stat card, 31 measured) — both fixed to the measured 31; MCP tool count 87 verified exact, untouched. Audit archive (not a defect): WAL auto-checkpoint verified healthy under daemon-like load — 4 rounds × 400 mixed ops, WAL converged at ~4.1MB (default 1000-page threshold), TRUNCATE resets to zero. 5 new guards in `tests/test_v645_fixes.py`.
101
117
  - **v6.4.3 — documentation-code consistency fix** (2026-11-10). Pre-release audit found that v6.4.2's claimed first cut — WAL + `synchronous=NORMAL` — **never landed in code**: `_get_conn()` set only `journal_mode=WAL`, leaving `synchronous` at SQLite's default FULL. The 2.1× search gain was real, but carried entirely by the other four cuts — real numbers, misattributed cause. This release adds `PRAGMA synchronous=NORMAL` on the WAL branch (per-connection, cross-thread; network-FS DELETE fallback stays FULL by design). Same-machine A/B on SSD shows no statistically significant latency delta (search p50 1.8 → 1.8ms — fsync is ~0.1ms-scale here); the benefit targets slow disks and IO-saturated containers, and more importantly makes the documented power-loss semantics (un-checkpointed access counts / search audits may be lost; compliance audits still commit immediately) actually hold. 4 new guard tests in `tests/test_v643_fixes.py`.
102
118
  - **v6.4.2 — tens-of-thousands-chars latency: another 2×** (2026-11-09). Same-machine A/B benchmark (400 memories / ~47k chars, 3 runs each): **search p50 6.3→3.0ms (2.1×), p95 11.7→4.2ms (2.8×)**; 24k-char library p50 2.5ms; fast machine state 1.8ms. Five surgical cuts: WAL + `synchronous=NORMAL` (commits skip per-write fsync), enum-downgrade memo, `row.keys()` set reuse, first-visit access-count batching merged into one `flush_access()` transaction per search, and search-audit INSERT folded into the same commit. The v5.6.3 throttle contract and v5.5.4 `recently_accessed` semantics are pinned by 23 contract tests — all green. Compliance-grade audits still commit immediately. *(Note: the `synchronous=NORMAL` cut landed in v6.4.3 — see above.)*
103
119
  - **v6.4.1 — million-character millisecond search** (2026-11-09). Scale benchmark (8,000 memories / ~990k chars) exposed a fuzzy-fallback long tail: search p95 881ms. Root causes: SequenceMatcher wasted on mathematically-impossible matches (ratio = 2M/(m+n), M ≤ m, so n ≥ 4m can never pass 0.4) + a ~50μs/entry Python full-scan tax. Fixed with a math-capped prune + SQLite-level candidate pre-filtering: **search p95 881→30.7ms (28×)**, add p50 2.6ms flat at 10k-scale. New `scripts/bench_scale.py` + CI scale guard (3000 memories, millisecond contract) + 4 behavior-level regression tests pin the semantics.
@@ -167,7 +183,7 @@ v6.1.3 – v6.4.3 shipped in ten days (2026-11-01 → 11-10), guarded by **1,228
167
183
 
168
184
  ```
169
185
  ┌──────────────────────────────────────────────────────┐
170
- │ MindForge v6.4.3 │
186
+ │ MindForge v6.4.6 │
171
187
  │ Cognitive Layer Personality · KnowledgeGraph │
172
188
  │ MemoryEvolution · FederatedMemory │
173
189
  ├──────────────────────────────────────────────────────┤
@@ -68,6 +68,7 @@ mindforge_memory.egg-info/requires.txt
68
68
  mindforge_memory.egg-info/top_level.txt
69
69
  tests/test_core.py
70
70
  tests/test_security_hardening.py
71
+ tests/test_trash_hard_delete.py
71
72
  tests/test_v546_features.py
72
73
  tests/test_v547_fixes.py
73
74
  tests/test_v548_features.py
@@ -132,4 +133,6 @@ tests/test_v635_features.py
132
133
  tests/test_v635_integration.py
133
134
  tests/test_v636_fixes.py
134
135
  tests/test_v643_fixes.py
136
+ tests/test_v645_fixes.py
137
+ tests/test_v646_fixes.py
135
138
  tests/test_v6_ports.py
@@ -0,0 +1,115 @@
1
+ """
2
+ 回收站硬删数据主权守卫(2026-11-10 数据/安全专项审计发现)
3
+
4
+ 缺陷形态:MindForge.delete(id, hard_delete=True) 对已软删(trash 态)
5
+ 条目静默返回 False——get_memory 过滤 trash(WHERE category != 'trash'),
6
+ 上层 None 分支直接拒绝,CLI 对用户显示「记忆不存在」。物理数据仍在
7
+ 库中,误导用户以为已彻底清除。GDPR erase 语义(storage 权利清单)
8
+ 要求:能软删活条目,就必须能物理清除其 trash 态。
9
+
10
+ 修复:delete() None 分支 + hard_delete 时查 is_in_trash 放行物理清除
11
+ (隐私读检查不适用:内容已在 trash,仅物理清除;审计仍同事务写入)。
12
+
13
+ 对照组语义(不得回归):
14
+ - 活条目正常软删/硬删路径不变;
15
+ - 对 trash 条目软删(重复软删)仍返回 False;
16
+ - 不存在的 id 一切删除返回 False。
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ import shutil
22
+ import sqlite3
23
+ import tempfile
24
+ import unittest
25
+
26
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
27
+
28
+ from mindforge import MindForge
29
+ from mindforge.core.types import MemoryConfig
30
+
31
+
32
+ def _mk(tmp):
33
+ mf = MindForge(config=MemoryConfig(
34
+ db_path=os.path.join(tmp, "m.db"), key_file=None, encrypted=False))
35
+ mf.add("守卫探针 alpha-key-1 活条目")
36
+ mf.add("守卫探针 beta-key-2 待删条目")
37
+ raw = sqlite3.connect(os.path.join(tmp, "m.db"))
38
+ # memories.id 是 UUID(无序),必须按内容锚定——SELECT 顺序映射会让
39
+ # aid/bid 张冠李戴(本文件首次编写即踩此坑,ghost 断言假失败)。
40
+ aid = raw.execute(
41
+ "SELECT id FROM memories WHERE content LIKE '%alpha%'").fetchone()[0]
42
+ bid = raw.execute(
43
+ "SELECT id FROM memories WHERE content LIKE '%beta%'").fetchone()[0]
44
+ raw.close()
45
+ return mf, (aid, bid)
46
+
47
+
48
+ class TestTrashHardDelete(unittest.TestCase):
49
+
50
+ def test_hard_delete_soft_deleted_entry(self):
51
+ """核心:软删后 hard_delete 必须物理清除(此前静默 False)。"""
52
+ tmp = tempfile.mkdtemp(prefix="mf_thd_")
53
+ try:
54
+ mf, (aid, bid) = _mk(tmp)
55
+ self.assertTrue(mf.delete(bid)) # 软删
56
+ self.assertIsNone(mf.get(bid)) # trash 被 get 过滤
57
+ self.assertTrue(mf.delete(bid, hard_delete=True)) # ← 修复点
58
+ raw = sqlite3.connect(os.path.join(tmp, "m.db"))
59
+ rows = raw.execute(
60
+ "SELECT count(*) FROM memories WHERE id = ?", (bid,)).fetchone()[0]
61
+ fts = raw.execute("SELECT count(*) FROM memory_fts").fetchone()[0]
62
+ raw.close()
63
+ self.assertEqual(rows, 0, "trash 条目未被物理清除")
64
+ self.assertEqual(fts, 1, "FTS 未同步(应只剩活条目)")
65
+ self.assertEqual(len(mf.search("alpha-key-1").chunks), 1)
66
+ # 注意:不能断言 search("beta-key-2") chunks==0——fuzzy 宽召回
67
+ # 会把相近活条目(alpha)召回给 beta 查询(tfidf+fuzzy 策略设计)。
68
+ # 正确断言:召回内容中不含已物理删除的 beta 内容(无幽灵)。
69
+ ghost = [c for c in mf.search("beta-key-2").chunks
70
+ if "beta-key-2" in getattr(c, "content", "")]
71
+ self.assertEqual(len(ghost), 0, "已物理删除内容被召回(真幽灵)")
72
+ mf.close()
73
+ finally:
74
+ shutil.rmtree(tmp, ignore_errors=True)
75
+
76
+ def test_soft_delete_on_trash_still_false(self):
77
+ """对照:对 trash 条目再软删仍 False(重复软删无意义)。"""
78
+ tmp = tempfile.mkdtemp(prefix="mf_thd2_")
79
+ try:
80
+ mf, (aid, bid) = _mk(tmp)
81
+ mf.delete(bid)
82
+ self.assertFalse(mf.delete(bid)) # 软删 trash = False
83
+ mf.close()
84
+ finally:
85
+ shutil.rmtree(tmp, ignore_errors=True)
86
+
87
+ def test_nonexistent_id_hard_delete_false(self):
88
+ """对照:不存在 id 一切删除 False(不因修复误报成功)。"""
89
+ tmp = tempfile.mkdtemp(prefix="mf_thd3_")
90
+ try:
91
+ mf, (aid, bid) = _mk(tmp)
92
+ self.assertFalse(mf.delete("ghost-id-404", hard_delete=True))
93
+ self.assertFalse(mf.delete("ghost-id-404"))
94
+ mf.close()
95
+ finally:
96
+ shutil.rmtree(tmp, ignore_errors=True)
97
+
98
+ def test_is_in_trash_query(self):
99
+ """storage.is_in_trash 三态:活条目 False / 软删后 True / 物理删后 False。"""
100
+ tmp = tempfile.mkdtemp(prefix="mf_thd4_")
101
+ try:
102
+ mf, (aid, bid) = _mk(tmp)
103
+ st = mf._storage
104
+ self.assertFalse(st.is_in_trash(bid))
105
+ mf.delete(bid)
106
+ self.assertTrue(st.is_in_trash(bid))
107
+ mf.delete(bid, hard_delete=True)
108
+ self.assertFalse(st.is_in_trash(bid))
109
+ mf.close()
110
+ finally:
111
+ shutil.rmtree(tmp, ignore_errors=True)
112
+
113
+
114
+ if __name__ == "__main__":
115
+ unittest.main()
@@ -39,7 +39,7 @@ import mindforge.core.version as core_version
39
39
 
40
40
 
41
41
  _REPO = Path(__file__).resolve().parent.parent
42
- _EXPECTED_VERSION = "6.4.3"
42
+ _EXPECTED_VERSION = "6.4.6"
43
43
 
44
44
  # 本轮修复涉及的、必须与 mindforge/core/version.py 保持一致的源码文件
45
45
  _VERSION_TRUTH_FILES = (
@@ -40,8 +40,8 @@ class TestVersionTruth(unittest.TestCase):
40
40
  def test_core_version_matches_release(self):
41
41
  # 版本推进时必须一并更新此处的钉值(与 test_v568_fixes 的
42
42
  # _EXPECTED_VERSION 同一约定:让「忘记 bump 版本」在测试里显形)。
43
- self.assertEqual(core_version.__version__, "6.4.3")
44
- self.assertEqual(core_version.VERSION_INFO, (6, 4, 3))
43
+ self.assertEqual(core_version.__version__, "6.4.6")
44
+ self.assertEqual(core_version.VERSION_INFO, (6, 4, 6))
45
45
 
46
46
  def test_pyproject_matches_core_version(self):
47
47
  # v5.8.13 起 pyproject 不再写死静态版本:
@@ -662,8 +662,8 @@ class TestVersionConsistency(unittest.TestCase):
662
662
  def test_version_is_612(self):
663
663
  from mindforge.core.version import VERSION_INFO, __version__
664
664
  # v6.1.3:版本钉值随发版推进更新(历史方法名保留)
665
- self.assertEqual(__version__, "6.4.3")
666
- self.assertEqual(VERSION_INFO, (6, 4, 3))
665
+ self.assertEqual(__version__, "6.4.6")
666
+ self.assertEqual(VERSION_INFO, (6, 4, 6))
667
667
 
668
668
  def test_json_round_trip_safety_of_preview(self):
669
669
  """save_evolution 的预览序列化不得抛(不可 json 化的对象走 str 兜底)。"""
@@ -89,10 +89,12 @@ class TestSynchronousNormalGuard(_WALCase):
89
89
 
90
90
 
91
91
  class TestVersionBump(unittest.TestCase):
92
- """版本钉值:忘记 bump 时在此显形(与 v568/v575/v612 守卫同一约定)。"""
92
+ """版本钉值:忘记 bump 时在此显形(与 v568/v575/v612 守卫同一约定)。
93
+ v6.4.5 教训:每版 fixes 文件的版本断言在后续发版都要一并 bump——
94
+ 钉点集合随版本数递增,发版链清单必须扫全部 test_v*fixes.py。"""
93
95
 
94
- def test_version_is_643(self):
95
- self.assertEqual(__version__, "6.4.3")
96
+ def test_version_is_current(self):
97
+ self.assertEqual(__version__, "6.4.6")
96
98
 
97
99
 
98
100
  if __name__ == "__main__":
@@ -0,0 +1,108 @@
1
+ """
2
+ MindForge v6.4.5 回归测试
3
+
4
+ 覆盖发版前审计发现的两类缺陷(2026-11-10):
5
+
6
+ - Bug-A(P1): connectors.py 对外请求的 User-Agent 硬编码为
7
+ "MindForge-Connector/5.8.9"——5.8.9 时代写死后未跟上,v6.x 连发
8
+ 20+ 版本一直在对外谎报版本(event_bus.py v5.6.8 修过同款病,
9
+ 此处漏网)。修复:动态引用 mindforge/core/version.py 唯一真值。
10
+ - Bug-B(P2,数据过时): 官网「核心模块」数正文 17 / 统计卡 15,
11
+ 实测 mindforge/modules/ 为 31 个模块文件——两处口径双双过时且
12
+ 互相矛盾。修正为实测 31,并由本守卫钉住(website 数字必须等于
13
+ modules/ 实测文件数,防再漂移)。
14
+
15
+ 审计存档(非缺陷):WAL auto-checkpoint 行为验证——四轮 × 400 次
16
+ 混合操作(add+search),WAL 稳定收敛于 ~4.1MB(默认 1000 页阈值),
17
+ 无无限膨胀;主动 TRUNCATE 可归零。daemon 长连接场景健康。
18
+ """
19
+
20
+ import os
21
+ import re
22
+ import sys
23
+ import unittest
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
27
+
28
+ _REPO = Path(__file__).resolve().parent.parent
29
+
30
+ from mindforge.core.version import __version__
31
+
32
+
33
+ class TestUserAgentVersionTruth(unittest.TestCase):
34
+ """对外 User-Agent 的版本必须动态引用唯一真值,不得硬编码。"""
35
+
36
+ def test_connector_ua_is_dynamic(self):
37
+ t = (_REPO / "mindforge" / "modules" / "connectors.py").read_text(
38
+ encoding="utf-8")
39
+ # 修复后的形态:f-string 引用 __version__
40
+ self.assertIn('f"MindForge-Connector/{__version__}"', t,
41
+ "connectors UA 应动态引用 __version__")
42
+ # 病灶形态必须消失:任何带版本号的硬编码 UA
43
+ bad = re.findall(r'User-Agent[^)]*?/[0-9]+\.[0-9]+\.[0-9]+', t)
44
+ self.assertEqual(bad, [], f"发现硬编码版本 UA: {bad}")
45
+ # import 必须存在(防 f-string 引用悬空)
46
+ self.assertIn("from mindforge.core.version import __version__", t)
47
+
48
+ def test_no_stale_version_in_runtime_ua(self):
49
+ """全包扫描:运行时 UA/标识字符串不得携带过时版本字面量。"""
50
+ pat = re.compile(r'MindForge[-A-Za-z]*/(?:\d+\.\d+\.\d+)')
51
+ for py in (_REPO / "mindforge").rglob("*.py"):
52
+ hits = pat.findall(py.read_text(encoding="utf-8"))
53
+ # 允许出现的位置:注释里的历史说明(v6.4.5 fix 注释提及旧值)
54
+ for line_no, line in enumerate(
55
+ py.read_text(encoding="utf-8").splitlines(), 1):
56
+ if pat.search(line) and "v6.4.5 fix" not in line:
57
+ # schema 标识(mindforge-knowledge-graph/1.0)为协议
58
+ # 格式版本,非产品版本,豁免。
59
+ if "knowledge-graph" in line:
60
+ continue
61
+ self.fail(
62
+ f"{py.name}:{line_no} 疑似硬编码版本 UA: {line.strip()}")
63
+
64
+
65
+ class TestWebsiteModuleCountTruth(unittest.TestCase):
66
+ """官网「核心模块」数 = mindforge/modules/ 实测文件数(防再过时)。"""
67
+
68
+ def _modules_count(self) -> int:
69
+ return len([p for p in (_REPO / "mindforge" / "modules").glob("*.py")
70
+ if not p.name.startswith("_")])
71
+
72
+ def test_website_module_count_matches_reality(self):
73
+ want = str(self._modules_count())
74
+ index = (_REPO / "website" / "index.html").read_text(encoding="utf-8")
75
+ spots = {
76
+ "正文": re.findall(r'(\d+) 个核心模块', index),
77
+ "统计卡": re.findall(
78
+ r'class="about-stat"><b>(\d+)</b><span>核心模块<', index),
79
+ }
80
+ for k, v in spots.items():
81
+ self.assertEqual(len(v), 1, f"官网「{k}」核心模块数不唯一: {v}")
82
+ self.assertEqual(v[0], want,
83
+ f"官网「{k}」={v[0]},实测 modules/={want}")
84
+
85
+ def test_website_mcp_tool_count_matches_reality(self):
86
+ """MCP 工具数守卫(87 实测口径;import server 拿 TOOL_SCHEMAS)。"""
87
+ try:
88
+ from mindforge.mcp.server import TOOL_SCHEMAS
89
+ except Exception as exc: # pragma: no cover
90
+ self.skipTest(f"server 模块不可导入: {exc}")
91
+ want = str(len(TOOL_SCHEMAS))
92
+ index = (_REPO / "website" / "index.html").read_text(encoding="utf-8")
93
+ hits = re.findall(r'(\d+) 个? ?MCP 工具|(\d+) MCP tools', index)
94
+ flat = [x for pair in hits for x in pair if x]
95
+ self.assertTrue(flat, "官网未声明 MCP 工具数")
96
+ for v in flat:
97
+ self.assertEqual(v, want, f"官网 MCP 工具数 {v} ≠ 实测 {want}")
98
+
99
+
100
+ class TestVersionBump(unittest.TestCase):
101
+ """版本钉值:忘记 bump 时在此显形。"""
102
+
103
+ def test_version_is_current(self):
104
+ self.assertEqual(__version__, "6.4.6")
105
+
106
+
107
+ if __name__ == "__main__":
108
+ unittest.main()