mindforge-memory 6.4.1__tar.gz → 6.4.3__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 (137) hide show
  1. {mindforge_memory-6.4.1/mindforge_memory.egg-info → mindforge_memory-6.4.3}/PKG-INFO +7 -5
  2. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/README.md +6 -4
  3. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/query.py +13 -1
  4. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/storage.py +71 -15
  5. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/version.py +2 -2
  6. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3/mindforge_memory.egg-info}/PKG-INFO +7 -5
  7. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/SOURCES.txt +1 -0
  8. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v568_fixes.py +1 -1
  9. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v575_release_consistency.py +2 -2
  10. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v612_fixes.py +2 -2
  11. mindforge_memory-6.4.3/tests/test_v643_fixes.py +99 -0
  12. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/LICENSE +0 -0
  13. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/MindForge.py +0 -0
  14. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/__init__.py +0 -0
  15. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/__init__.py +0 -0
  16. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/claude_adapter.py +0 -0
  17. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/crewai_memory.py +0 -0
  18. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/generic_api.py +0 -0
  19. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/langgraph_store.py +0 -0
  20. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/adapters/openclaw_adapter.py +0 -0
  21. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/api/__init__.py +0 -0
  22. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/api/server.py +0 -0
  23. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/cli/__init__.py +0 -0
  24. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/cli/main.py +0 -0
  25. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/__init__.py +0 -0
  26. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/embedding.py +0 -0
  27. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/encryption.py +0 -0
  28. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/indexer.py +0 -0
  29. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/mindforge.py +0 -0
  30. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/paths.py +0 -0
  31. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/core/types.py +0 -0
  32. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/daemon/__init__.py +0 -0
  33. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/daemon/server.py +0 -0
  34. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/mcp/__init__.py +0 -0
  35. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/mcp/server.py +0 -0
  36. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/__init__.py +0 -0
  37. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/categorizer.py +0 -0
  38. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/conflict_detector.py +0 -0
  39. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/connectors.py +0 -0
  40. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/context_window.py +0 -0
  41. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/event_bus.py +0 -0
  42. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/evolution.py +0 -0
  43. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/experience.py +0 -0
  44. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/federated.py +0 -0
  45. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/federated_acl.py +0 -0
  46. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/follow_up.py +0 -0
  47. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/hybrid_search.py +0 -0
  48. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/importer.py +0 -0
  49. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/integrator.py +0 -0
  50. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/intent_router.py +0 -0
  51. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/knowledge_graph.py +0 -0
  52. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_compressor.py +0 -0
  53. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_decay.py +0 -0
  54. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/memory_passport.py +0 -0
  55. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/multi_agent.py +0 -0
  56. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/multimodal.py +0 -0
  57. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/outcome_learning.py +0 -0
  58. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/personality.py +0 -0
  59. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/principled_forgetting.py +0 -0
  60. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/privacy.py +0 -0
  61. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/recall.py +0 -0
  62. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/session_focus.py +0 -0
  63. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/share_conflict.py +0 -0
  64. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/skill_extractor.py +0 -0
  65. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/skill_pack.py +0 -0
  66. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/timeline_viz.py +0 -0
  67. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge/modules/v6.py +0 -0
  68. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/dependency_links.txt +0 -0
  69. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/entry_points.txt +0 -0
  70. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/requires.txt +0 -0
  71. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/mindforge_memory.egg-info/top_level.txt +0 -0
  72. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/pyproject.toml +0 -0
  73. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/setup.cfg +0 -0
  74. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/setup.py +0 -0
  75. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_core.py +0 -0
  76. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_security_hardening.py +0 -0
  77. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v546_features.py +0 -0
  78. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v547_fixes.py +0 -0
  79. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v548_features.py +0 -0
  80. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v550_features.py +0 -0
  81. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v552_features.py +0 -0
  82. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v554_features.py +0 -0
  83. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v556_features.py +0 -0
  84. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_banner_insight_wal.py +0 -0
  85. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_security_fixes.py +0 -0
  86. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v557_webhook_sig_export.py +0 -0
  87. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v558_audit_fixes.py +0 -0
  88. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v558_features.py +0 -0
  89. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_decay_gc.py +0 -0
  90. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_kdf_versioning.py +0 -0
  91. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v560_module_coverage.py +0 -0
  92. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v561_security.py +0 -0
  93. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v562_p2_fixes.py +0 -0
  94. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v563_entry_coverage.py +0 -0
  95. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v563_perf_fixes.py +0 -0
  96. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v565_security.py +0 -0
  97. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v566_encrypted_search.py +0 -0
  98. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v567_security_fixes.py +0 -0
  99. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v569_fts_sync.py +0 -0
  100. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_coverage_expansion.py +0 -0
  101. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_drama_and_utils.py +0 -0
  102. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v570_encrypted_fallback.py +0 -0
  103. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v571_fixes.py +0 -0
  104. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v572_security.py +0 -0
  105. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v573_encrypted_export.py +0 -0
  106. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v573_security.py +0 -0
  107. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v576_bitemporal.py +0 -0
  108. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v576_gdpr.py +0 -0
  109. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v577_agent_governance.py +0 -0
  110. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v578_embedding.py +0 -0
  111. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v579_graph.py +0 -0
  112. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v580_experience.py +0 -0
  113. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v5812_bugfixes.py +0 -0
  114. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v583_security.py +0 -0
  115. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v584_adapters.py +0 -0
  116. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v585_benchmark.py +0 -0
  117. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v586_federated_mcp.py +0 -0
  118. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v590_features.py +0 -0
  119. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v592_features.py +0 -0
  120. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v593_features.py +0 -0
  121. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v595_features.py +0 -0
  122. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_forgetting_followup.py +0 -0
  123. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_import_timeline.py +0 -0
  124. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_multi_agent_formal.py +0 -0
  125. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_outcome_passport.py +0 -0
  126. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v600_skillpack_evolution.py +0 -0
  127. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v611_fixes.py +0 -0
  128. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v612_cascade_fixes.py +0 -0
  129. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v613_integrity.py +0 -0
  130. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v620_growth_governance.py +0 -0
  131. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v630_search_explain.py +0 -0
  132. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v631_scrypt.py +0 -0
  133. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v632_explain_interfaces.py +0 -0
  134. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v635_features.py +0 -0
  135. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v635_integration.py +0 -0
  136. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/tests/test_v636_fixes.py +0 -0
  137. {mindforge_memory-6.4.1 → mindforge_memory-6.4.3}/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.1
3
+ Version: 6.4.3
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.1: no team, no funding, 1,220 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.3: no team, no funding, 1,227 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.1-green.svg)](https://github.com/opok-ops/MindForge/releases)
43
+ [![Version](https://img.shields.io/badge/version-6.4.3-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)
@@ -96,8 +96,10 @@ MindForge stats
96
96
 
97
97
  ## What's New
98
98
 
99
- v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,224 tests** (1219+4 passed, 1 skipped).
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).
100
100
 
101
+ - **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
+ - **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.)*
101
103
  - **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.
102
104
 
103
105
  - **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
@@ -165,7 +167,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
165
167
 
166
168
  ```
167
169
  ┌──────────────────────────────────────────────────────┐
168
- │ MindForge v6.4.1 │
170
+ │ MindForge v6.4.3 │
169
171
  │ Cognitive Layer Personality · KnowledgeGraph │
170
172
  │ MemoryEvolution · FederatedMemory │
171
173
  ├──────────────────────────────────────────────────────┤
@@ -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.1: no team, no funding, 1,220 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.3: no team, no funding, 1,227 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.1-green.svg)](https://github.com/opok-ops/MindForge/releases)
13
+ [![Version](https://img.shields.io/badge/version-6.4.3-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)
@@ -66,8 +66,10 @@ MindForge stats
66
66
 
67
67
  ## What's New
68
68
 
69
- v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,224 tests** (1219+4 passed, 1 skipped).
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).
70
70
 
71
+ - **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
+ - **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.)*
71
73
  - **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.
72
74
 
73
75
  - **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
@@ -135,7 +137,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
135
137
 
136
138
  ```
137
139
  ┌──────────────────────────────────────────────────────┐
138
- │ MindForge v6.4.1 │
140
+ │ MindForge v6.4.3 │
139
141
  │ Cognitive Layer Personality · KnowledgeGraph │
140
142
  │ MemoryEvolution · FederatedMemory │
141
143
  ├──────────────────────────────────────────────────────┤
@@ -380,7 +380,10 @@ class QueryEngine:
380
380
 
381
381
  def _cached_get(mid: str) -> Optional[Any]:
382
382
  if mid not in _entry_cache:
383
- _entry_cache[mid] = self.storage.get_memory(mid)
383
+ # v6.4.2 perf: 批量检索上下文——首访访问计数走挂账,
384
+ # 由本 search 尾部的 flush_all_access 统一落库。
385
+ _entry_cache[mid] = self.storage.get_memory(
386
+ mid, _batch_access=True)
384
387
  return _entry_cache[mid]
385
388
 
386
389
  # 预过滤:按 categories/layers 筛选 score_map,避免非匹配记忆占据排序位
@@ -470,6 +473,15 @@ class QueryEngine:
470
473
  except Exception:
471
474
  pass
472
475
 
476
+ # v6.4.2 perf: 本轮 search 命中的首访访问计数 + 挂账的 search 审计
477
+ # 在此统一批量落库(executemany + 单次 commit,含审计 INSERT 合并)。
478
+ # 挂账期上限 = 单次 search 时长,recently_accessed 排序在 search
479
+ # 返回后即正确;同条记忆 60s 节流窗口内重复命中仍免落库(v5.6.3 契约)。
480
+ try:
481
+ self.storage.flush_access()
482
+ except Exception:
483
+ pass
484
+
473
485
  elapsed = (time.time() - start) * 1000
474
486
  token_estimate = sum(len(c.content) for c in chunks) // 4
475
487
 
@@ -694,6 +694,11 @@ class MemoryCache:
694
694
 
695
695
 
696
696
  # v5.6.3 性能修复:记忆读缓存 TTL(秒)。除写路径主动失效外的兜底,
697
+ # v6.4.2 perf: 枚举降级 memo(行反序列化高频路径)。值域天然有界
698
+ # (枚举成员数 × 类数);Enum 单例不可变共享安全;跨模块路径加载的
699
+ # 同名类副本身份不同,作为 dict key 天然隔离不串台。
700
+ _ENUM_DOWNGRADE_MEMO: Dict[type, Dict[Any, Any]] = {}
701
+
697
702
  # 任何遗漏的写路径最多造成该窗口内的短时脏读,避免长期脏数据。
698
703
  _ENTRY_CACHE_TTL = 30.0
699
704
 
@@ -731,6 +736,8 @@ class StorageEngine:
731
736
  # v6.2.0 增长治理:搜索审计去抖状态(同词 60s 内只落一条)
732
737
  self._search_dedup_lock = threading.Lock()
733
738
  self._search_dedup: Dict[str, float] = {}
739
+ # v6.4.2 perf: search 审计挂账标记(INSERT 已进事务缓冲未 commit)
740
+ self._search_audit_dirty = False
734
741
  self._init_db()
735
742
 
736
743
  def _get_conn(self) -> sqlite3.Connection:
@@ -750,6 +757,15 @@ class StorageEngine:
750
757
  conn.execute("PRAGMA journal_mode=DELETE")
751
758
  else:
752
759
  conn.execute("PRAGMA journal_mode=WAL")
760
+ # v6.4.3 fix: WAL 配套 synchronous=NORMAL(SQLite 官方推荐
761
+ # 组合)。v6.4.2 文档宣称了该优化但代码从未落地——本补丁
762
+ # 将其补齐:WAL+NORMAL 下 commit 免逐次 fsync,挂账批量
763
+ # 落库(search 尾事务)不再付 fsync 代价。
764
+ # 注意:synchronous 是 per-connection 参数,必须在每个
765
+ # 新连接创建时设置,仅在 WAL 分支生效;网络 FS 降级
766
+ # DELETE journal 的分支维持默认 FULL(远程盘耐久性语义
767
+ # 从严,不做削弱)。
768
+ conn.execute("PRAGMA synchronous=NORMAL")
753
769
  conn.execute("PRAGMA foreign_keys=ON")
754
770
  conn.execute("PRAGMA busy_timeout=5000")
755
771
  self._conn_local.conn = conn
@@ -1168,7 +1184,23 @@ class StorageEngine:
1168
1184
  v5.5.4 fix: 兼容双重导入路径导致的枚举类不一致问题。
1169
1185
  当 isinstance() 因不同模块路径加载同名枚举而失败时,
1170
1186
  通过 .value / .name 属性匹配来识别。
1187
+
1188
+ v6.4.2 perf: str 输入路径先查 memo(命中即免逐值遍历匹配);
1189
+ key 含 default,不假设同一 value 恒定同一 default。
1171
1190
  """
1191
+ if isinstance(value, str):
1192
+ memo = _ENUM_DOWNGRADE_MEMO.setdefault(enum_cls, {})
1193
+ hit = memo.get((value, default))
1194
+ if hit is not None:
1195
+ return hit
1196
+ result = StorageEngine._downgrade_enum_slow(value, enum_cls, default)
1197
+ memo[(value, default)] = result
1198
+ return result
1199
+ return StorageEngine._downgrade_enum_slow(value, enum_cls, default)
1200
+
1201
+ @staticmethod
1202
+ def _downgrade_enum_slow(value: Any, enum_cls: type, default: Any) -> Any:
1203
+ """未命中 memo 时的逐值匹配内核(原 _downgrade_enum 逻辑)。"""
1172
1204
  if isinstance(value, enum_cls):
1173
1205
  return value
1174
1206
  # 兼容双重导入:isinstance 失败但 value 有枚举属性时,用 .value 匹配
@@ -1345,7 +1377,8 @@ class StorageEngine:
1345
1377
  return entry
1346
1378
 
1347
1379
  def get_memory(self, memory_id: str,
1348
- actor: str = "", session_id: str = "") -> Optional[MemoryEntry]:
1380
+ actor: str = "", session_id: str = "",
1381
+ _batch_access: bool = False) -> Optional[MemoryEntry]:
1349
1382
  """获取记忆
1350
1383
 
1351
1384
  v5.5.2: 自动过期检查——若 expires_at > 0 且已过期,自动移入回收站并返回 None。
@@ -1433,7 +1466,8 @@ class StorageEngine:
1433
1466
  return entry
1434
1467
  return None
1435
1468
 
1436
- self._update_access(entry, actor, session_id)
1469
+ self._update_access(entry, actor, session_id,
1470
+ _batch=_batch_access)
1437
1471
  return entry
1438
1472
 
1439
1473
  def get_memories_by_ids(self, memory_ids: List[str]) -> List[Optional[MemoryEntry]]:
@@ -7285,6 +7319,8 @@ class StorageEngine:
7285
7319
 
7286
7320
  def _row_to_entry(self, row: sqlite3.Row) -> MemoryEntry:
7287
7321
  # v5.3.7 修复:tags/metadata 可能已经是 list/dict(非 JSON 字符串)
7322
+ # v6.4.2 perf: 一次构造列名集合,避免每行 5 次 row.keys() 分配 + 线性 in 扫描
7323
+ _rk = set(row.keys())
7288
7324
  raw_tags = row["tags"]
7289
7325
  if isinstance(raw_tags, (list, tuple)):
7290
7326
  tags_val = list(raw_tags)
@@ -7324,11 +7360,11 @@ class StorageEngine:
7324
7360
  consolidation_count=row["consolidation_count"],
7325
7361
  forgetting_score=row["forgetting_score"],
7326
7362
  strength=row["strength"],
7327
- starred=bool(row["starred"]) if "starred" in row.keys() else False,
7328
- pinned=bool(row["pinned"]) if "pinned" in row.keys() else False,
7329
- expires_at=float(row["expires_at"]) if "expires_at" in row.keys() and row["expires_at"] else 0.0,
7330
- valid_from=float(row["valid_from"]) if "valid_from" in row.keys() and row["valid_from"] else 0.0,
7331
- valid_to=float(row["valid_to"]) if "valid_to" in row.keys() and row["valid_to"] else 0.0,
7363
+ starred=bool(row["starred"]) if "starred" in _rk else False,
7364
+ pinned=bool(row["pinned"]) if "pinned" in _rk else False,
7365
+ expires_at=float(row["expires_at"]) if "expires_at" in _rk and row["expires_at"] else 0.0,
7366
+ valid_from=float(row["valid_from"]) if "valid_from" in _rk and row["valid_from"] else 0.0,
7367
+ valid_to=float(row["valid_to"]) if "valid_to" in _rk and row["valid_to"] else 0.0,
7332
7368
  metadata=meta_val,
7333
7369
  encrypted=bool(row["encrypted"]),
7334
7370
  ciphertext=row["ciphertext"],
@@ -7336,7 +7372,8 @@ class StorageEngine:
7336
7372
  salt=row["salt"],
7337
7373
  )
7338
7374
 
7339
- def _update_access(self, entry: MemoryEntry, actor: str, session_id: str):
7375
+ def _update_access(self, entry: MemoryEntry, actor: str, session_id: str,
7376
+ _batch: bool = False):
7340
7377
  """更新访问计数
7341
7378
 
7342
7379
  v5.1.2:不再每次 get 都写审计日志。
@@ -7352,7 +7389,16 @@ class StorageEngine:
7352
7389
  self._access_pending[entry.id] = (
7353
7390
  self._access_pending.get(entry.id, 0) + 1)
7354
7391
  last = self._access_last_flush.get(entry.id, 0.0)
7355
- if last > 0 and now - last < _ACCESS_PERSIST_INTERVAL:
7392
+ if last == 0.0:
7393
+ # v6.4.2 perf: 首访起表。批量检索上下文(_batch=True,如
7394
+ # QueryEngine.search 的结果构建)首访只挂账,由调用方统一
7395
+ # 批量落库(search 尾部 flush_all_access,单次 executemany
7396
+ # + commit);直连 get_memory 首访仍立即落库(v5.6.3 契约)。
7397
+ self._access_last_flush[entry.id] = now
7398
+ if _batch:
7399
+ self._maybe_prune_access_locked(now)
7400
+ return
7401
+ elif now - last < _ACCESS_PERSIST_INTERVAL:
7356
7402
  self._maybe_prune_access_locked(now)
7357
7403
  return
7358
7404
  self._flush_access_locked(entry.id, now)
@@ -7420,8 +7466,10 @@ class StorageEngine:
7420
7466
  字典,计数不丢。
7421
7467
  """
7422
7468
  with self._access_lock:
7423
- if not self._access_pending:
7469
+ audit_dirty = self._search_audit_dirty
7470
+ if not self._access_pending and not audit_dirty:
7424
7471
  return
7472
+ self._search_audit_dirty = False
7425
7473
  now = time.time()
7426
7474
  pending = self._access_pending
7427
7475
  self._access_pending = {}
@@ -7429,10 +7477,12 @@ class StorageEngine:
7429
7477
  items = [(inc, now, mid) for mid, inc in pending.items()]
7430
7478
  conn = self._get_conn()
7431
7479
  try:
7432
- conn.executemany("""
7433
- UPDATE memories SET access_count = access_count + ?, last_accessed_at = ?
7434
- WHERE id = ?
7435
- """, items)
7480
+ if items:
7481
+ conn.executemany("""
7482
+ UPDATE memories SET access_count = access_count + ?, last_accessed_at = ?
7483
+ WHERE id = ?
7484
+ """, items)
7485
+ # v6.4.2 perf: 挂账的 search 审计 INSERT(若有)随本事务一并提交
7436
7486
  conn.commit()
7437
7487
  except Exception as e:
7438
7488
  with self._access_lock:
@@ -9248,8 +9298,14 @@ class StorageEngine:
9248
9298
  self._search_dedup_lock = threading.Lock()
9249
9299
  self._search_dedup = {}
9250
9300
  try:
9301
+ # v6.4.2 perf: search 审计 INSERT 挂账不 commit,与下一次
9302
+ # flush_access() 的批量 UPDATE 合并成同一次事务提交(IO 慢态
9303
+ # 下 commit ~1ms,search 热路径 100 次 commit → 与挂账合并归零)。
9304
+ # search 审计为非合规数据(v6.2.0 prune 同一分类),崩溃窗口
9305
+ # ≤ 单次 search 时长,可接受;合规类审计不受影响仍即时 commit。
9306
+ self._search_audit_dirty = True
9251
9307
  self._add_audit("search", q, "", "", "INTERNAL",
9252
- {"query": q}, commit=True)
9308
+ {"query": q}, commit=False)
9253
9309
  except Exception as _err:
9254
9310
  logger.warning("record_search 审计写入失败: %s", _err)
9255
9311
 
@@ -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.1"
23
+ __version__: str = "6.4.3"
24
24
 
25
25
  #: 结构化版本元组,便于比较(如 `VERSION_INFO >= (6, 1, 0)`)
26
- VERSION_INFO: Tuple[int, int, int] = (6, 4, 1)
26
+ VERSION_INFO: Tuple[int, int, int] = (6, 4, 3)
27
27
 
28
28
  #: 下一个大版本的目标版本号(供 v6 端口预留引用)
29
29
  NEXT_MAJOR_TARGET: str = "7.0.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindforge-memory
3
- Version: 6.4.1
3
+ Version: 6.4.3
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.1: no team, no funding, 1,220 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.3: no team, no funding, 1,227 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.1-green.svg)](https://github.com/opok-ops/MindForge/releases)
43
+ [![Version](https://img.shields.io/badge/version-6.4.3-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)
@@ -96,8 +96,10 @@ MindForge stats
96
96
 
97
97
  ## What's New
98
98
 
99
- v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,224 tests** (1219+4 passed, 1 skipped).
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).
100
100
 
101
+ - **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
+ - **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.)*
101
103
  - **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.
102
104
 
103
105
  - **v6.4.0 — resident daemon & performance guards** (2026-11-08). New `mindforge daemon` subcommand: line-JSON over stdio, engine resident in a long-lived process — per-request p50 2.5ms vs 400–700ms for CLI subprocess spawning (two orders of magnitude; 2026-11-09 quiet-machine re-bench: 2.5ms vs 401ms p50, ~160×). CI now guards latency (engine in-process thresholds) and daemon 13-step E2E alongside the 16-step CLI E2E guard.
@@ -165,7 +167,7 @@ v6.1.3 – v6.4.1 shipped in nine days (2026-11-01 → 11-09), guarded by **1,22
165
167
 
166
168
  ```
167
169
  ┌──────────────────────────────────────────────────────┐
168
- │ MindForge v6.4.1 │
170
+ │ MindForge v6.4.3 │
169
171
  │ Cognitive Layer Personality · KnowledgeGraph │
170
172
  │ MemoryEvolution · FederatedMemory │
171
173
  ├──────────────────────────────────────────────────────┤
@@ -131,4 +131,5 @@ tests/test_v632_explain_interfaces.py
131
131
  tests/test_v635_features.py
132
132
  tests/test_v635_integration.py
133
133
  tests/test_v636_fixes.py
134
+ tests/test_v643_fixes.py
134
135
  tests/test_v6_ports.py
@@ -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.1"
42
+ _EXPECTED_VERSION = "6.4.3"
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.1")
44
- self.assertEqual(core_version.VERSION_INFO, (6, 4, 1))
43
+ self.assertEqual(core_version.__version__, "6.4.3")
44
+ self.assertEqual(core_version.VERSION_INFO, (6, 4, 3))
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.1")
666
- self.assertEqual(VERSION_INFO, (6, 4, 1))
665
+ self.assertEqual(__version__, "6.4.3")
666
+ self.assertEqual(VERSION_INFO, (6, 4, 3))
667
667
 
668
668
  def test_json_round_trip_safety_of_preview(self):
669
669
  """save_evolution 的预览序列化不得抛(不可 json 化的对象走 str 兜底)。"""
@@ -0,0 +1,99 @@
1
+ """
2
+ MindForge v6.4.3 回归测试
3
+
4
+ 覆盖测试过程中发现的 1 个文档-代码不一致缺陷(2026-11-10):
5
+
6
+ - Bug-A(P0): v6.4.2 宣称的「WAL + synchronous=NORMAL」五刀第一刀
7
+ 从未落地——CHANGELOG/README/website 与 commit message 均宣称该
8
+ 组合已上线,但 _get_conn() 仅设置了 journal_mode=WAL,
9
+ synchronous 维持 SQLite 默认 FULL(WAL 下逐 commit fsync)。
10
+ 修复:WAL 分支补 PRAGMA synchronous=NORMAL(per-connection,
11
+ 每连接创建时设置;网络 FS 降级 DELETE 分支维持 FULL 从严)。
12
+
13
+ 守卫目标:让「PRAGMA 只在部分连接生效」「优化宣称了但没落地」
14
+ 这两类回归在测试里显形。
15
+ """
16
+
17
+ import os
18
+ import sys
19
+ import tempfile
20
+ import threading
21
+ import unittest
22
+ from pathlib import Path
23
+
24
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
25
+
26
+ from mindforge.core import storage as core_storage
27
+ from mindforge.core.version import __version__
28
+
29
+
30
+ class _WALCase(unittest.TestCase):
31
+ """本地盘临时库(走 WAL 分支)公共脚手架。"""
32
+
33
+ def setUp(self):
34
+ self.tmpdir = tempfile.TemporaryDirectory(prefix="mf_v643_fix_")
35
+ self.db = os.path.join(self.tmpdir.name, "memory.db")
36
+ self.storage = core_storage.StorageEngine(
37
+ db_path=self.db, encrypted=False
38
+ )
39
+
40
+ def tearDown(self):
41
+ try:
42
+ self.storage.close()
43
+ finally:
44
+ self.tmpdir.cleanup()
45
+
46
+
47
+ class TestSynchronousNormalGuard(_WALCase):
48
+ """Bug-A 守卫:WAL + synchronous=NORMAL 必须真实生效。"""
49
+
50
+ def test_synchronous_normal_on_local_wal(self):
51
+ # SQLite PRAGMA synchronous 返回值:0=OFF, 1=NORMAL, 2=FULL。
52
+ # v6.4.2 缺陷形态即此值恒为 2(FULL)。
53
+ conn = self.storage._get_conn()
54
+ mode = conn.execute("PRAGMA synchronous").fetchone()[0]
55
+ self.assertEqual(
56
+ mode, 1,
57
+ f"synchronous 应为 NORMAL(1),实际 {mode}——"
58
+ "v6.4.3 的 WAL+synchronous=NORMAL 修复未生效或被回退",
59
+ )
60
+
61
+ def test_journal_mode_wal_on_local_disk(self):
62
+ conn = self.storage._get_conn()
63
+ journal = conn.execute("PRAGMA journal_mode").fetchone()[0]
64
+ self.assertEqual(
65
+ str(journal).lower(), "wal",
66
+ f"本地盘 journal_mode 应为 wal,实际 {journal}",
67
+ )
68
+
69
+ def test_synchronous_is_per_connection(self):
70
+ # synchronous 是 per-connection 参数:StorageEngine 用
71
+ # threading.local 维护连接,新线程的连接也必须带上 NORMAL,
72
+ # 否则只保护了首线程(REST/daemon 多线程场景将静默回退 FULL)。
73
+ results = {}
74
+
75
+ def probe():
76
+ try:
77
+ conn = self.storage._get_conn()
78
+ results["mode"] = conn.execute(
79
+ "PRAGMA synchronous"
80
+ ).fetchone()[0]
81
+ except Exception as exc: # pragma: no cover - 显形优先
82
+ results["error"] = repr(exc)
83
+
84
+ t = threading.Thread(target=probe)
85
+ t.start()
86
+ t.join(timeout=10)
87
+ self.assertNotIn("error", results, results.get("error", ""))
88
+ self.assertEqual(results.get("mode"), 1)
89
+
90
+
91
+ class TestVersionBump(unittest.TestCase):
92
+ """版本钉值:忘记 bump 时在此显形(与 v568/v575/v612 守卫同一约定)。"""
93
+
94
+ def test_version_is_643(self):
95
+ self.assertEqual(__version__, "6.4.3")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ unittest.main()