memorymaster 3.22.0__tar.gz → 3.24.0__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 (399) hide show
  1. {memorymaster-3.22.0/memorymaster.egg-info → memorymaster-3.24.0}/PKG-INFO +4 -3
  2. {memorymaster-3.22.0 → memorymaster-3.24.0}/README.md +3 -2
  3. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/__init__.py +1 -1
  4. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli.py +7 -0
  5. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_handlers_basic.py +1 -0
  6. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_handlers_curation.py +37 -0
  7. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +27 -31
  8. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/contradiction_probe.py +129 -0
  9. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_cache.py +40 -9
  10. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/security.py +21 -0
  11. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/service.py +12 -1
  12. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward.py +75 -8
  13. memorymaster-3.24.0/memorymaster/verbatim_cleanup.py +175 -0
  14. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/verbatim_store.py +142 -133
  15. {memorymaster-3.22.0 → memorymaster-3.24.0/memorymaster.egg-info}/PKG-INFO +4 -3
  16. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/SOURCES.txt +3 -0
  17. {memorymaster-3.22.0 → memorymaster-3.24.0}/pyproject.toml +1 -1
  18. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/bench_longmemeval.py +42 -3
  19. memorymaster-3.24.0/tests/test_auto_ingest_hook_citations.py +102 -0
  20. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_cache.py +26 -0
  21. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_t07.py +42 -1
  22. memorymaster-3.24.0/tests/test_steward_contradiction_phase.py +181 -0
  23. memorymaster-3.24.0/tests/test_verbatim_cleanup.py +152 -0
  24. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_store_qdrant.py +28 -0
  25. memorymaster-3.22.0/tests/test_auto_ingest_hook_citations.py +0 -147
  26. {memorymaster-3.22.0 → memorymaster-3.24.0}/LICENSE +0 -0
  27. {memorymaster-3.22.0 → memorymaster-3.24.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
  28. {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/longmemeval_runner.py +0 -0
  29. {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/longmemeval_vector_runner.py +0 -0
  30. {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/perf_smoke.py +0 -0
  31. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/__main__.py +0 -0
  32. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_lifecycle.py +0 -0
  33. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_read.py +0 -0
  34. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_schema.py +0 -0
  35. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_shared.py +0 -0
  36. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_sources.py +0 -0
  37. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_write_claims.py +0 -0
  38. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/access_control.py +0 -0
  39. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/action_exporters.py +0 -0
  40. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/action_extractor.py +0 -0
  41. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/atlas_claim_extractor.py +0 -0
  42. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/atlas_contract.py +0 -0
  43. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/auto_extractor.py +0 -0
  44. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/auto_resolver.py +0 -0
  45. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/candidate_dedupe.py +0 -0
  46. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/claim_edges.py +0 -0
  47. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/claim_verifier.py +0 -0
  48. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_helpers.py +0 -0
  49. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/closets.py +0 -0
  50. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config.py +0 -0
  51. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/claude-md-append.md +0 -0
  52. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  53. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  54. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
  55. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  56. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  57. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  58. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
  59. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  60. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/conflict_resolver.py +0 -0
  61. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/connectors/__init__.py +0 -0
  62. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/connectors/whatsapp.py +0 -0
  63. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/context_hook.py +0 -0
  64. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/context_optimizer.py +0 -0
  65. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/daily_notes.py +0 -0
  66. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dashboard.py +0 -0
  67. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dashboard_auth.py +0 -0
  68. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/db_merge.py +0 -0
  69. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/delta_sync.py +0 -0
  70. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dream_bridge.py +0 -0
  71. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/embeddings.py +0 -0
  72. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_extractor.py +0 -0
  73. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_graph.py +0 -0
  74. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_registry.py +0 -0
  75. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/federated_graphify.py +0 -0
  76. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/feedback.py +0 -0
  77. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/graph_store.py +0 -0
  78. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/hook_log.py +0 -0
  79. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/__init__.py +0 -0
  80. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/calibration.py +0 -0
  81. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/compact_summaries.py +0 -0
  82. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/compactor.py +0 -0
  83. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/daydream_ingest.py +0 -0
  84. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/decay.py +0 -0
  85. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/dedup.py +0 -0
  86. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/deterministic.py +0 -0
  87. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/entity_graph_export.py +0 -0
  88. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/extractor.py +0 -0
  89. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/staleness.py +0 -0
  90. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/validator.py +0 -0
  91. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/key_rotator.py +0 -0
  92. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/lifecycle.py +0 -0
  93. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_budget.py +0 -0
  94. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_provider.py +0 -0
  95. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_rerank.py +0 -0
  96. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_steward.py +0 -0
  97. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_path_policy.py +0 -0
  98. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_server.py +0 -0
  99. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_usage.py +0 -0
  100. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/media_processing.py +0 -0
  101. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/media_providers.py +0 -0
  102. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/metrics_exporter.py +0 -0
  103. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0001_initial.py +0 -0
  104. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0002_miner_state.py +0 -0
  105. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0003_contradiction_verdicts.py +0 -0
  106. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0004_query_cache.py +0 -0
  107. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/__init__.py +0 -0
  108. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/runner.py +0 -0
  109. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/models.py +0 -0
  110. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/observability.py +0 -0
  111. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/operator.py +0 -0
  112. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/operator_queue.py +0 -0
  113. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/plugins.py +0 -0
  114. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/policy.py +0 -0
  115. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/postgres_store.py +0 -0
  116. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qdrant_backend.py +0 -0
  117. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qdrant_recall_fallback.py +0 -0
  118. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qmd_bridge.py +0 -0
  119. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_classifier.py +0 -0
  120. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_expansion.py +0 -0
  121. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/recall_fusion.py +0 -0
  122. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/recall_tokenizer.py +0 -0
  123. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/retrieval.py +0 -0
  124. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/retry.py +0 -0
  125. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/review.py +0 -0
  126. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rl_trainer.py +0 -0
  127. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rule_miner.py +0 -0
  128. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rules.py +0 -0
  129. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/scheduler.py +0 -0
  130. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema.py +0 -0
  131. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema.sql +0 -0
  132. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema_postgres.sql +0 -0
  133. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/scope_utils.py +0 -0
  134. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/session_tracker.py +0 -0
  135. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/setup_hooks.py +0 -0
  136. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/skill_evolver.py +0 -0
  137. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/snapshot.py +0 -0
  138. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward_classifier.py +0 -0
  139. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward_features.py +0 -0
  140. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/storage.py +0 -0
  141. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/store_factory.py +0 -0
  142. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/transcript_miner.py +0 -0
  143. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/turn_schema.py +0 -0
  144. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_bases.py +0 -0
  145. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_curator.py +0 -0
  146. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_exporter.py +0 -0
  147. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_linter.py +0 -0
  148. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_log.py +0 -0
  149. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_query_capture.py +0 -0
  150. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_synthesis.py +0 -0
  151. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/verbatim_recall.py +0 -0
  152. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/webhook.py +0 -0
  153. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_engine.py +0 -0
  154. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_freshness.py +0 -0
  155. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_similarity.py +0 -0
  156. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_suggest.py +0 -0
  157. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_validate.py +0 -0
  158. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/dependency_links.txt +0 -0
  159. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/entry_points.txt +0 -0
  160. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/requires.txt +0 -0
  161. {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/top_level.txt +0 -0
  162. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/agg_recall_latency.py +0 -0
  163. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/alert_operator_metrics.py +0 -0
  164. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/audit_dedupe_precision.py +0 -0
  165. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/autoresearch_daemon.py +0 -0
  166. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_entity_extraction.py +0 -0
  167. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_graph_store.py +0 -0
  168. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_stop_hook_citations.py +0 -0
  169. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backtest_steward_classifier.py +0 -0
  170. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/build_steward_training_set.py +0 -0
  171. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/check_hook_template_drift.py +0 -0
  172. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/claude_to_turns.py +0 -0
  173. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/codex_live_to_turns.py +0 -0
  174. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_edge_cases.py +0 -0
  175. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_trace_report.py +0 -0
  176. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_trace_validate.py +0 -0
  177. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/confusion_matrix_eval.py +0 -0
  178. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/conversation_importer.py +0 -0
  179. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/conversation_to_turns.py +0 -0
  180. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/e2e_operator.py +0 -0
  181. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/email_live_to_turns.py +0 -0
  182. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_bm25_sweep.py +0 -0
  183. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_classify_f1.py +0 -0
  184. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_memorymaster.py +0 -0
  185. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_recall_precision_at_5.py +0 -0
  186. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_recall_quality.py +0 -0
  187. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_steward_pareto.py +0 -0
  188. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_verbatim_recall.py +0 -0
  189. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/expand_recall_eval.py +0 -0
  190. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/generate_drill_signoff.py +0 -0
  191. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/git_to_turns.py +0 -0
  192. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/github_live_to_turns.py +0 -0
  193. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/gitnexus_to_claims.py +0 -0
  194. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/grid_recall_weights.py +0 -0
  195. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/index_claims_to_qdrant.py +0 -0
  196. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/ingest_planning_docs.py +0 -0
  197. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/jira_live_to_turns.py +0 -0
  198. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/label_prompts_with_judge.py +0 -0
  199. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/llm_benchmark.py +0 -0
  200. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/measure_dedupe_thresholds.py +0 -0
  201. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/merge_scope_variants.py +0 -0
  202. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/messages_to_turns.py +0 -0
  203. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/operator_metrics.py +0 -0
  204. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/precompute_candidates.py +0 -0
  205. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/recurring_incident_drill.py +0 -0
  206. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/release_readiness.py +0 -0
  207. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/run_codex_autologger.py +0 -0
  208. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/run_incident_drill.py +0 -0
  209. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/scheduled_ingest.py +0 -0
  210. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/setup-hooks.py +0 -0
  211. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/slack_live_to_turns.py +0 -0
  212. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/sync_hook_templates.py +0 -0
  213. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/tickets_to_turns.py +0 -0
  214. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/train_steward_classifier.py +0 -0
  215. {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/webhook_to_turns.py +0 -0
  216. {memorymaster-3.22.0 → memorymaster-3.24.0}/setup.cfg +0 -0
  217. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/conftest.py +0 -0
  218. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
  219. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_access_control.py +0 -0
  220. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_action_exporters.py +0 -0
  221. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_action_extractor.py +0 -0
  222. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_claim_extractor.py +0 -0
  223. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_contract.py +0 -0
  224. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_source_schema.py +0 -0
  225. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_extractor.py +0 -0
  226. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_ingest_hook_schema.py +0 -0
  227. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_resolver.py +0 -0
  228. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_validate.py +0 -0
  229. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_backend_parity.py +0 -0
  230. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_bm25_per_field.py +0 -0
  231. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_calibration.py +0 -0
  232. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_calibration_priors_applied.py +0 -0
  233. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_candidate_dedupe.py +0 -0
  234. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_edges.py +0 -0
  235. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_links.py +0 -0
  236. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_type_ranking.py +0 -0
  237. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_classify_hook_f1.py +0 -0
  238. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_classify_hook_latency.py +0 -0
  239. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claude_to_turns.py +0 -0
  240. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_dry_run.py +0 -0
  241. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_json_flag.py +0 -0
  242. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_ready.py +0 -0
  243. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_review_queue.py +0 -0
  244. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_subcommands.py +0 -0
  245. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_closets.py +0 -0
  246. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_closets_recall_integration.py +0 -0
  247. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compact_summaries.py +0 -0
  248. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compact_summaries_sensitivity.py +0 -0
  249. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compaction_trace.py +0 -0
  250. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compactor_artifact_order.py +0 -0
  251. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_config.py +0 -0
  252. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_conflict_resolver.py +0 -0
  253. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_confusion_matrix_eval.py +0 -0
  254. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_connection_retry.py +0 -0
  255. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_connectors.py +0 -0
  256. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_hook.py +0 -0
  257. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_optimizer.py +0 -0
  258. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_optimizer_provider.py +0 -0
  259. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_contradiction_probe.py +0 -0
  260. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_conversation_to_turns.py +0 -0
  261. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard.py +0 -0
  262. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_auth.py +0 -0
  263. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_coverage.py +0 -0
  264. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_latency.py +0 -0
  265. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_lineage.py +0 -0
  266. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_review_queue.py +0 -0
  267. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_daydream_ingest.py +0 -0
  268. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_db_merge_confidence_conflict.py +0 -0
  269. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_db_merge_coverage_v2.py +0 -0
  270. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_decay_coverage.py +0 -0
  271. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_decay_respects_pinned.py +0 -0
  272. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup.py +0 -0
  273. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup_cli.py +0 -0
  274. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup_conflict_disambiguation.py +0 -0
  275. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_delta_sync.py +0 -0
  276. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_deterministic_predicates.py +0 -0
  277. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dream_bridge_coverage_v2.py +0 -0
  278. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dream_bridge_sensitivity.py +0 -0
  279. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_embeddings_coverage.py +0 -0
  280. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_extractor.py +0 -0
  281. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_extractor_llm.py +0 -0
  282. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_graph.py +0 -0
  283. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_graph_export.py +0 -0
  284. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_new_kinds.py +0 -0
  285. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_regex_v3.py +0 -0
  286. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_registry.py +0 -0
  287. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_eval_harness.py +0 -0
  288. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_events_schema.py +0 -0
  289. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_extract_llm_ollama.py +0 -0
  290. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_federated_graphify_mcp.py +0 -0
  291. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_federated_query_safety.py +0 -0
  292. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_feedback.py +0 -0
  293. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_floor_gate.py +0 -0
  294. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_fts5_search.py +0 -0
  295. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_graph_distance.py +0 -0
  296. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_graph_store.py +0 -0
  297. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_handler_regressions.py +0 -0
  298. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_hook_env_isolation.py +0 -0
  299. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_human_id.py +0 -0
  300. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_incident_drill_runner.py +0 -0
  301. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_integration_workflows.py +0 -0
  302. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_key_rotator.py +0 -0
  303. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_lifecycle.py +0 -0
  304. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_lifecycle_supersede_invariant.py +0 -0
  305. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_budget.py +0 -0
  306. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_fallback.py +0 -0
  307. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_provider_claude_cli.py +0 -0
  308. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_provider_key_rotation.py +0 -0
  309. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_steward_coverage.py +0 -0
  310. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_steward_key_rotation.py +0 -0
  311. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_filter_bypass.py +0 -0
  312. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_helpers.py +0 -0
  313. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_path_policy.py +0 -0
  314. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_rate_limit.py +0 -0
  315. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_server_validation.py +0 -0
  316. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_usage.py +0 -0
  317. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_media_processing.py +0 -0
  318. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_meta_decisions.py +0 -0
  319. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_metrics_exporter.py +0 -0
  320. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_migrations.py +0 -0
  321. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_observability.py +0 -0
  322. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_obsidian_mind_patterns.py +0 -0
  323. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_operator.py +0 -0
  324. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_operator_queue.py +0 -0
  325. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_perf_smoke_config.py +0 -0
  326. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_plugins.py +0 -0
  327. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_policy_coverage.py +0 -0
  328. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_policy_mode_env.py +0 -0
  329. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_postgres_parity.py +0 -0
  330. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qdrant_backend.py +0 -0
  331. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qmd_bridge.py +0 -0
  332. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qrels_regression.py +0 -0
  333. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_classifier.py +0 -0
  334. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_expansion.py +0 -0
  335. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_entity_fanout.py +0 -0
  336. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_fusion.py +0 -0
  337. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_latency.py +0 -0
  338. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_precision_at_5.py +0 -0
  339. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_tokenizer.py +0 -0
  340. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_vector_fallback.py +0 -0
  341. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_reliability_hardening.py +0 -0
  342. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_resolvers_concurrent_supersede.py +0 -0
  343. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_profile.py +0 -0
  344. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_profiles.py +0 -0
  345. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_rrf_tiebreaker.py +0 -0
  346. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_weights.py +0 -0
  347. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_review.py +0 -0
  348. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rl_trainer.py +0 -0
  349. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rrf_auto_gate.py +0 -0
  350. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rule_claims.py +0 -0
  351. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rule_miner.py +0 -0
  352. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scheduler.py +0 -0
  353. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_schema.py +0 -0
  354. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scope_boost.py +0 -0
  355. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scope_utils.py +0 -0
  356. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_security_access.py +0 -0
  357. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_security_patterns.py +0 -0
  358. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
  359. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
  360. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_service_coverage.py +0 -0
  361. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_session_tracker.py +0 -0
  362. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_snapshot.py +0 -0
  363. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_snapshot_roundtrip.py +0 -0
  364. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sqlite_core.py +0 -0
  365. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_staleness.py +0 -0
  366. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_stealth_mode.py +0 -0
  367. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward.py +0 -0
  368. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_classifier.py +0 -0
  369. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_daydream_hook.py +0 -0
  370. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_features.py +0 -0
  371. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_features_v3.py +0 -0
  372. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_resolution_parity.py +0 -0
  373. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_storage_parity.py +0 -0
  374. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_store_factory.py +0 -0
  375. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_tenant_isolation.py +0 -0
  376. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_turn_schema.py +0 -0
  377. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_two_pass_recall.py +0 -0
  378. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v311_fixes.py +0 -0
  379. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v313_e2e.py +0 -0
  380. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v313_run_cycle_dedupe.py +0 -0
  381. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v390_e2e.py +0 -0
  382. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v391_strict_warnings.py +0 -0
  383. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vault_exporter.py +0 -0
  384. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vault_linter_orphan.py +0 -0
  385. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vector_search.py +0 -0
  386. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_dedup.py +0 -0
  387. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_recall.py +0 -0
  388. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_store.py +0 -0
  389. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_webhook.py +0 -0
  390. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_webhook_hmac.py +0 -0
  391. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_whatsapp_importer.py +0 -0
  392. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_autopromote.py +0 -0
  393. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_binding.py +0 -0
  394. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_engine_idempotency.py +0 -0
  395. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_explored_and_contradictions.py +0 -0
  396. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_freshness.py +0 -0
  397. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_similarity_multiscope.py +0 -0
  398. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_suggest.py +0 -0
  399. {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_validate_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.22.0
3
+ Version: 3.24.0
4
4
  Summary: Production-grade memory reliability system for AI coding agents. Lifecycle-managed claims with citations, conflict detection, steward governance, and MCP integration.
5
5
  Author: wolverin0
6
6
  License: MIT
@@ -52,7 +52,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
52
52
 
53
53
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
54
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
55
- [![Tests](https://img.shields.io/badge/tests-2214-green.svg)]()
55
+ [![Tests](https://img.shields.io/badge/tests-2230-green.svg)]()
56
56
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
57
57
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
58
58
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](https://pypi.org/project/memorymaster/)
@@ -91,7 +91,8 @@ recent PR status, and sensitivity-filter invariants.
91
91
  - **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
92
92
  - **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
93
93
  - **Retrieval quality** (new in v3.22.0): floor-ratio boost gate (`MEMORYMASTER_BOOST_FLOOR_RATIO`) stops fresh-but-wrong claims outranking the true match; `query --explain` shows per-stage score attribution; an opt-in correctness-safe query cache (`MEMORYMASTER_QUERY_CACHE`) with a generation gate
94
- - **Semantic contradiction probe** (new in v3.22.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache
94
+ - **Semantic contradiction probe** (new in v3.22.0, wired as a steward phase in v3.23.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache; in v3.23 the same probe runs inside `run-steward` and emits paste-ready `conflicted` proposals
95
+ - **Verbatim archive cleanup** (new in v3.23.0): `verbatim-cleanup` dedups the raw-transcript table and optionally purges pre-#128 junk rows, with a dry-run default and FTS5 mirror sync
95
96
  - **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
96
97
  - **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
97
98
  - **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
@@ -6,7 +6,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
8
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
9
- [![Tests](https://img.shields.io/badge/tests-2214-green.svg)]()
9
+ [![Tests](https://img.shields.io/badge/tests-2230-green.svg)]()
10
10
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
11
11
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
12
12
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](https://pypi.org/project/memorymaster/)
@@ -45,7 +45,8 @@ recent PR status, and sensitivity-filter invariants.
45
45
  - **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
46
46
  - **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
47
47
  - **Retrieval quality** (new in v3.22.0): floor-ratio boost gate (`MEMORYMASTER_BOOST_FLOOR_RATIO`) stops fresh-but-wrong claims outranking the true match; `query --explain` shows per-stage score attribution; an opt-in correctness-safe query cache (`MEMORYMASTER_QUERY_CACHE`) with a generation gate
48
- - **Semantic contradiction probe** (new in v3.22.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache
48
+ - **Semantic contradiction probe** (new in v3.22.0, wired as a steward phase in v3.23.0): `detect-contradictions` finds claims that genuinely contradict each other (beyond the deterministic same-subject conflict check) via an LLM judge with a Wilson-CI rate and verdict cache; in v3.23 the same probe runs inside `run-steward` and emits paste-ready `conflicted` proposals
49
+ - **Verbatim archive cleanup** (new in v3.23.0): `verbatim-cleanup` dedups the raw-transcript table and optionally purges pre-#128 junk rows, with a dry-run default and FTS5 mirror sync
49
50
  - **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
50
51
  - **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
51
52
  - **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "3.22.0"
5
+ __version__ = "3.24.0"
@@ -332,6 +332,7 @@ def build_parser() -> argparse.ArgumentParser:
332
332
  steward.add_argument("--probe-failure-threshold", type=int, default=3, help="Open circuit breaker for a probe type after this many timeout/error failures")
333
333
  steward.add_argument("--disable-semantic-probe", action="store_true", help="Disable semantic retrieval probe in steward planner")
334
334
  steward.add_argument("--disable-tool-probe", action="store_true", help="Disable tool/storage probe in steward planner")
335
+ steward.add_argument("--disable-contradiction-probe", action="store_true", help="Disable semantic contradiction probe in steward planner (v3.23)")
335
336
  steward.add_argument("--allow-sensitive", action="store_true", help="Include sensitive claims in stewardship scan")
336
337
  steward.add_argument("--apply", action="store_true", help="Apply proposed status transitions")
337
338
  steward.add_argument("--artifact-json", default="artifacts/steward/steward_report.json", help="Path to steward JSON report artifact")
@@ -464,6 +465,12 @@ def build_parser() -> argparse.ArgumentParser:
464
465
  mine_rules_cmd.add_argument("--provider", default="claude_cli", help="LLM provider for this run (default: claude_cli)")
465
466
  mine_rules_cmd.add_argument("--reset", action="store_true", help="Clear the stored watermark before running (re-scan from the start)")
466
467
 
468
+ verbatim_clean = sub.add_parser("verbatim-cleanup", help="Dedup the verbatim archive + optionally purge pre-#128 capture-bug junk (v3.23)")
469
+ verbatim_clean.add_argument("--analyze-only", dest="analyze_only", action="store_true", help="Report composition only; do not delete")
470
+ verbatim_clean.add_argument("--apply", action="store_true", help="Actually delete (default is dry-run)")
471
+ verbatim_clean.add_argument("--no-dedup", dest="no_dedup", action="store_true", help="Skip the (session_id, content) exact-dup pass")
472
+ verbatim_clean.add_argument("--purge-junk", dest="purge_junk", action="store_true", help="Also delete rows matching known pre-#128 junk prefixes (wiki-absorb prompt, etc.)")
473
+
467
474
  detect_contra = sub.add_parser("detect-contradictions", help="Find semantic contradictions between topically-similar claims via an LLM judge (v3.22)")
468
475
  detect_contra.add_argument("--limit", type=int, default=200, help="Max claims to load for pair sampling")
469
476
  detect_contra.add_argument("--sample", type=int, default=50, help="Max candidate pairs to judge this run (caps LLM calls)")
@@ -1228,6 +1228,7 @@ def _handle_run_steward(args: argparse.Namespace, service, parser: argparse.Argu
1228
1228
  probe_failure_threshold=args.probe_failure_threshold,
1229
1229
  enable_semantic_probe=not args.disable_semantic_probe,
1230
1230
  enable_tool_probe=not args.disable_tool_probe,
1231
+ enable_contradiction_probe=not getattr(args, "disable_contradiction_probe", False),
1231
1232
  artifact_path=Path(args.artifact_json),
1232
1233
  )
1233
1234
  elapsed_ms = (time.perf_counter() - t0) * 1000
@@ -397,6 +397,42 @@ def _handle_detect_contradictions(args: argparse.Namespace, service, parser: arg
397
397
  return 0
398
398
 
399
399
 
400
+ def _handle_verbatim_cleanup(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
401
+ from memorymaster.verbatim_cleanup import analyze, cleanup
402
+ t0 = time.perf_counter()
403
+ if getattr(args, "analyze_only", False):
404
+ result = analyze(effective_db)
405
+ else:
406
+ result = cleanup(
407
+ effective_db,
408
+ dedup=not getattr(args, "no_dedup", False),
409
+ purge_junk=getattr(args, "purge_junk", False),
410
+ dry_run=not getattr(args, "apply", False),
411
+ )
412
+ elapsed_ms = (time.perf_counter() - t0) * 1000
413
+ if args.json_output:
414
+ print(_json_envelope(result, query_ms=elapsed_ms))
415
+ else:
416
+ if not result.get("verbatim_present", True):
417
+ print("verbatim-cleanup: verbatim_memories table not present; nothing to do")
418
+ elif "before_total" in result: # cleanup result
419
+ tag = "(dry-run)" if result["dry_run"] else "(applied)"
420
+ print(
421
+ f"verbatim-cleanup {tag}: dedup_deleted={result['dedup_deleted']}, "
422
+ f"junk_deleted={result['junk_deleted']}, "
423
+ f"before={result['before_total']} -> after={result['after_total']} "
424
+ f"({elapsed_ms:.0f}ms)"
425
+ )
426
+ else: # analyze result
427
+ print(
428
+ f"verbatim analyze: total={result['total']}, distinct={result['distinct_content']}, "
429
+ f"duplicate_extras={result['duplicate_extras']}, "
430
+ f"empty_role={result['empty_role_rows']}, junk_prefix={result['junk_prefix_rows']} "
431
+ f"({elapsed_ms:.0f}ms)"
432
+ )
433
+ return 0
434
+
435
+
400
436
  def _handle_wiki_breakdown(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
401
437
  from memorymaster.wiki_engine import breakdown
402
438
  t0 = time.perf_counter()
@@ -759,6 +795,7 @@ COMMAND_HANDLERS: dict[str, object] = {
759
795
  "mine-transcript": _handle_mine_transcript,
760
796
  "mine-rules": _handle_mine_rules,
761
797
  "detect-contradictions": _handle_detect_contradictions,
798
+ "verbatim-cleanup": _handle_verbatim_cleanup,
762
799
  "verify-claims": _handle_verify_claims,
763
800
  "extract-entities": _handle_extract_entities,
764
801
  "entity-stats": _handle_entity_stats,
@@ -10,9 +10,8 @@ import json
10
10
  import os
11
11
  import sys
12
12
  import re
13
- import sqlite3
14
13
  import hashlib
15
- from datetime import datetime, timezone
14
+ from datetime import datetime
16
15
  from pathlib import Path
17
16
 
18
17
  PROJECT_ROOT = "__MEMORYMASTER_PROJECT_ROOT__"
@@ -139,43 +138,40 @@ Only: bug root causes, decisions, gotchas, constraints. Never: credentials, IPs,
139
138
  return
140
139
 
141
140
  scope = "project:" + os.path.basename(cwd).lower().replace(" ", "-") if cwd else "global"
142
- now = datetime.now(timezone.utc).isoformat()
143
141
 
144
- conn = sqlite3.connect(DB_PATH)
142
+ # Route through MemoryService.ingest instead of raw SQL so claims gain
143
+ # the canonical ingest path: sensitivity sanitize (defense-in-depth on
144
+ # top of the _is_sensitive_claim drop above), content-hash + idempotency
145
+ # dedup, entity resolution, auto-citation, observability, and webhook.
146
+ # Mirrors _run_rule_extraction, which already uses the service.
147
+ from memorymaster.service import MemoryService
148
+ from memorymaster.models import CitationInput
149
+
150
+ svc = MemoryService(DB_PATH, workspace_root=Path(cwd or PROJECT_ROOT))
151
+ ingested = 0
145
152
  for c in claims:
146
153
  text = c.get("text", "")
147
154
  if not text or len(text) < 10:
148
155
  continue
149
- # Duplicate check by content hash
150
156
  text_hash = hashlib.sha256(text.strip().lower().encode()).hexdigest()[:16]
151
- idem = f"llm-stop-{text_hash}"
152
- if conn.execute("SELECT id FROM claims WHERE idempotency_key = ?", (idem,)).fetchone():
153
- continue
154
- cur = conn.execute(
155
- """INSERT INTO claims (text, idempotency_key, normalized_text, claim_type,
156
- subject, predicate, scope, status, confidence,
157
- source_agent, created_at, updated_at, tier, version, visibility,
158
- valid_from)
159
- VALUES (?, ?, ?, ?, ?, ?, ?, 'candidate', 0.6,
160
- 'llm-stop-hook', ?, ?, 'working', 1, 'public', ?)""",
161
- (text, idem, text.lower(), c.get("claim_type", "fact"),
162
- c.get("subject", "codebase"), c.get("predicate", "observation"),
163
- scope, now, now, now),
164
- )
165
- # Auto-citation: steward's min_citations>=1 gate requires at least one
166
- # row per claim. Without this, every llm-stop-hook claim is born
167
- # unpromotable. Source is the hook itself; locator = scope for
168
- # traceability; excerpt preserves the first 200 chars of the claim.
169
- conn.execute(
170
- """INSERT INTO citations (claim_id, source, locator, excerpt, created_at)
171
- VALUES (?, 'llm-stop-hook', ?, ?, ?)""",
172
- (cur.lastrowid, scope, text[:200], now),
173
- )
174
- conn.commit()
175
- conn.close()
157
+ try:
158
+ svc.ingest(
159
+ text=text,
160
+ citations=[CitationInput(source="llm-stop-hook", locator=scope, excerpt=text[:200])],
161
+ idempotency_key=f"llm-stop-{text_hash}",
162
+ claim_type=c.get("claim_type", "fact"),
163
+ subject=c.get("subject", "codebase"),
164
+ predicate=c.get("predicate", "observation"),
165
+ scope=scope,
166
+ confidence=0.6,
167
+ source_agent="llm-stop-hook",
168
+ )
169
+ ingested += 1
170
+ except Exception:
171
+ continue # one bad claim must not abort the rest
176
172
 
177
173
  provider = os.environ.get("MEMORYMASTER_LLM_PROVIDER", "google")
178
- sys.stderr.write(f"[MemoryMaster] {provider} extracted {len(claims)} learnings\n")
174
+ sys.stderr.write(f"[MemoryMaster] {provider} extracted {ingested} learnings\n")
179
175
  except Exception:
180
176
  pass
181
177
 
@@ -26,6 +26,7 @@ import logging
26
26
  import math
27
27
  import os
28
28
  import sqlite3
29
+ import time
29
30
  from datetime import datetime, timezone
30
31
  from typing import Any
31
32
 
@@ -33,6 +34,7 @@ from memorymaster import llm_budget, llm_provider
33
34
  from memorymaster.embeddings import EmbeddingProvider, cosine_similarity, create_best_provider
34
35
  from memorymaster.lifecycle import transition_claim
35
36
  from memorymaster.models import Claim
37
+ from memorymaster.security import redact_text
36
38
 
37
39
  logger = logging.getLogger(__name__)
38
40
 
@@ -324,3 +326,130 @@ def run_probe(
324
326
  lo, hi = wilson_interval(stats["contradictions"], n)
325
327
  stats["rate_ci"] = [round(lo, 4), round(hi, 4)]
326
328
  return stats
329
+
330
+
331
+ # ---------------------------------------------------------------------------
332
+ # Per-claim steward-phase entry point (v3.23)
333
+ # ---------------------------------------------------------------------------
334
+
335
+
336
+ def probe_for_claim(
337
+ service: Any,
338
+ claim: Any,
339
+ *,
340
+ sim_low: float = 0.60,
341
+ sim_high: float = 0.92,
342
+ max_pairs: int = 5,
343
+ peer_limit: int = 20,
344
+ ) -> dict[str, Any]:
345
+ """Per-claim contradiction probe for the steward cycle.
346
+
347
+ Finds topically-similar peers for ``claim`` via the existing hybrid
348
+ retrieval (so we reuse the embedder + ranker + cache), excludes pairs the
349
+ deterministic resolver / supersession owns, judges remaining candidates
350
+ against the verdict cache + LLM, and returns a normalized result dict the
351
+ steward wraps in a ``ProbeResult``.
352
+
353
+ Returns ``{passed, reasons: [...], metrics: {...}}``. ``passed`` is False
354
+ iff at least one contradiction was found.
355
+ """
356
+ started = time.monotonic()
357
+ metrics: dict[str, Any] = {
358
+ "pairs_checked": 0, "contradictions": 0,
359
+ "cache_hits": 0, "llm_calls": 0, "errors": 0,
360
+ "timed_out": False, "duration_ms": 0.0,
361
+ }
362
+ reasons: list[dict[str, Any]] = []
363
+
364
+ def _done(passed: bool) -> dict[str, Any]:
365
+ metrics["duration_ms"] = round((time.monotonic() - started) * 1000.0, 3)
366
+ return {"passed": passed, "reasons": reasons, "metrics": metrics}
367
+
368
+ if claim.status in _SKIP_STATUSES or not (claim.text or "").strip():
369
+ return _done(True)
370
+
371
+ try:
372
+ peers_rows = service.query_rows(
373
+ query_text=claim.text or "",
374
+ limit=peer_limit,
375
+ retrieval_mode="hybrid",
376
+ include_candidates=True,
377
+ include_stale=True,
378
+ include_conflicted=True,
379
+ )
380
+ except Exception as exc:
381
+ reasons.append({
382
+ "code": "contradiction_probe.peer_fetch.error",
383
+ "severity": "low",
384
+ "detail": f"peer fetch failed: {type(exc).__name__}",
385
+ "evidence": {},
386
+ })
387
+ return _done(False)
388
+
389
+ candidates: list[tuple[Any, float]] = []
390
+ for row in peers_rows:
391
+ peer = row.get("claim")
392
+ if peer is None or peer.id == claim.id:
393
+ continue
394
+ if peer.status in _SKIP_STATUSES:
395
+ continue
396
+ if _same_subject_predicate(claim, peer):
397
+ continue # deterministic resolver's domain
398
+ if _already_linked(claim, peer):
399
+ continue
400
+ vs = float(row.get("vector_score") or 0.0)
401
+ # If vectors are present, only accept pairs in the contradiction band.
402
+ # If absent (pure-lexical hybrid), accept all topical peers.
403
+ if vs > 0.0 and not (sim_low <= vs < sim_high):
404
+ continue
405
+ candidates.append((peer, vs))
406
+ if len(candidates) >= max_pairs:
407
+ break
408
+
409
+ if not candidates:
410
+ return _done(True)
411
+
412
+ db_path = getattr(service.store, "db_path", None)
413
+ if not db_path or "://" in str(db_path):
414
+ return _done(True) # verdict cache requires SQLite; skip silently on PG
415
+
416
+ conn = sqlite3.connect(str(db_path))
417
+ try:
418
+ _ensure_verdict_table(conn)
419
+ model = _model_key()
420
+ for peer, sim in candidates:
421
+ metrics["pairs_checked"] += 1
422
+ verdict = _cache_get(conn, claim.id, peer.id, model)
423
+ if verdict is not None:
424
+ metrics["cache_hits"] += 1
425
+ else:
426
+ try:
427
+ verdict = _judge_llm(claim, peer)
428
+ except llm_budget.LLMBudgetExceeded:
429
+ metrics["timed_out"] = True
430
+ break
431
+ metrics["llm_calls"] += 1
432
+ if verdict is None:
433
+ metrics["errors"] += 1
434
+ continue
435
+ _cache_put(conn, claim.id, peer.id, model, verdict)
436
+ if not verdict.get("contradicts"):
437
+ continue
438
+ verdict_reason = verdict.get("reason", "")
439
+ _, leaks = redact_text(verdict_reason)
440
+ if leaks:
441
+ continue # drop rather than surface a sensitive judge-reason
442
+ metrics["contradictions"] += 1
443
+ reasons.append({
444
+ "code": "contradiction_probe.semantic_pair",
445
+ "severity": verdict.get("severity", "medium"),
446
+ "detail": f"Semantic contradiction with claim {peer.id}: {verdict_reason}",
447
+ "evidence": {
448
+ "peer_claim_id": int(peer.id),
449
+ "similarity": round(float(sim), 4),
450
+ "from_cache": bool(verdict.get("cached")),
451
+ },
452
+ })
453
+ finally:
454
+ conn.close()
455
+ return _done(metrics["contradictions"] == 0)
@@ -84,11 +84,31 @@ def current_generation(conn: sqlite3.Connection) -> int:
84
84
  return int(row[0]) if row else 0
85
85
 
86
86
 
87
+ def read_generation(db_path: str) -> int:
88
+ """Return the current corpus generation, or 0 on any error.
89
+
90
+ Callers capture this BEFORE reading the corpus they are about to compute a
91
+ result from, then pass it to ``write()`` — see the TOCTOU note there."""
92
+ try:
93
+ conn = _connect(db_path)
94
+ except sqlite3.Error as exc:
95
+ logger.warning("query_cache.read_generation connect failed: %s", exc)
96
+ return 0
97
+ try:
98
+ return current_generation(conn)
99
+ except (sqlite3.Error, ValueError) as exc:
100
+ logger.warning("query_cache.read_generation failed: %s", exc)
101
+ return 0
102
+ finally:
103
+ conn.close()
104
+
105
+
87
106
  def read(db_path: str, cache_key: str) -> list[dict] | None:
88
107
  """Return cached result stubs if present AND still fresh (generation match)."""
89
108
  try:
90
109
  conn = _connect(db_path)
91
- except sqlite3.Error:
110
+ except sqlite3.Error as exc:
111
+ logger.warning("query_cache.read connect failed: %s", exc)
92
112
  return None
93
113
  try:
94
114
  gen = current_generation(conn)
@@ -99,20 +119,31 @@ def read(db_path: str, cache_key: str) -> list[dict] | None:
99
119
  if row is None or int(row["generation"]) != gen:
100
120
  return None
101
121
  return json.loads(row["result_json"])
102
- except (sqlite3.Error, json.JSONDecodeError, ValueError):
122
+ except (sqlite3.Error, json.JSONDecodeError, ValueError) as exc:
123
+ logger.warning("query_cache.read failed (cache disabled for this query): %s", exc)
103
124
  return None
104
125
  finally:
105
126
  conn.close()
106
127
 
107
128
 
108
- def write(db_path: str, cache_key: str, stub_rows: list[dict]) -> None:
109
- """Store result stubs tagged with the current generation. Best-effort."""
129
+ def write(db_path: str, cache_key: str, stub_rows: list[dict], generation: int) -> None:
130
+ """Store result stubs tagged with ``generation``. Best-effort.
131
+
132
+ TOCTOU correctness: ``generation`` MUST be the corpus generation captured by
133
+ the caller BEFORE it read the candidates it computed ``stub_rows`` from. If
134
+ we re-read the generation here instead, a claim write that raced in between
135
+ the caller's corpus read and this write would have bumped the counter, and
136
+ we would tag a stale (generation-G) result as the new generation (G+1) — so
137
+ a subsequent read at G+1 would serve the stale ranking, defeating the
138
+ generation gate. Tagging with the compute-time generation guarantees any
139
+ racing write correctly invalidates this entry. (audit: qc-generation-toctou)
140
+ """
110
141
  try:
111
142
  conn = _connect(db_path)
112
- except sqlite3.Error:
143
+ except sqlite3.Error as exc:
144
+ logger.warning("query_cache.write connect failed: %s", exc)
113
145
  return
114
146
  try:
115
- gen = current_generation(conn)
116
147
  conn.execute(
117
148
  """INSERT INTO query_cache (cache_key, result_json, generation, created_at)
118
149
  VALUES (?, ?, ?, ?)
@@ -120,10 +151,10 @@ def write(db_path: str, cache_key: str, stub_rows: list[dict]) -> None:
120
151
  result_json = excluded.result_json,
121
152
  generation = excluded.generation,
122
153
  created_at = excluded.created_at""",
123
- (cache_key, json.dumps(stub_rows), gen, datetime.now(timezone.utc).isoformat()),
154
+ (cache_key, json.dumps(stub_rows), generation, datetime.now(timezone.utc).isoformat()),
124
155
  )
125
156
  conn.commit()
126
- except (sqlite3.Error, TypeError, ValueError):
127
- pass
157
+ except (sqlite3.Error, TypeError, ValueError) as exc:
158
+ logger.warning("query_cache.write failed (result not cached): %s", exc)
128
159
  finally:
129
160
  conn.close()
@@ -157,6 +157,8 @@ class SanitizedClaimInput:
157
157
  is_sensitive: bool
158
158
  findings: list[str]
159
159
  encrypted_payload: str | None
160
+ subject: str | None = None
161
+ predicate: str | None = None
160
162
 
161
163
 
162
164
  def _as_bool(value: object, *, field: str) -> bool:
@@ -320,6 +322,8 @@ def sanitize_claim_input(
320
322
  text: str,
321
323
  object_value: str | None,
322
324
  citations: list[CitationInput],
325
+ subject: str | None = None,
326
+ predicate: str | None = None,
323
327
  ) -> SanitizedClaimInput:
324
328
  redacted_text, findings = _redact(text)
325
329
  redacted_object = object_value
@@ -328,6 +332,19 @@ def sanitize_claim_input(
328
332
  redacted_object, object_findings = _redact(object_value)
329
333
  findings.extend(object_findings)
330
334
 
335
+ # subject/predicate are structured-claim fields that reach the store
336
+ # alongside text/object_value. They are exposed MCP ingest parameters, so a
337
+ # secret placed there must be caught by the ingest filter — the last line of
338
+ # defense — not only at display time. (audit: ingest-subject-skips-filter)
339
+ redacted_subject = subject
340
+ if subject:
341
+ redacted_subject, subject_findings = _redact(subject)
342
+ findings.extend(subject_findings)
343
+ redacted_predicate = predicate
344
+ if predicate:
345
+ redacted_predicate, predicate_findings = _redact(predicate)
346
+ findings.extend(predicate_findings)
347
+
331
348
  sanitized_citations: list[CitationInput] = []
332
349
  citation_findings: list[str] = []
333
350
  for cite in citations:
@@ -344,6 +361,8 @@ def sanitize_claim_input(
344
361
  {
345
362
  "text": text,
346
363
  "object_value": object_value,
364
+ "subject": subject,
365
+ "predicate": predicate,
347
366
  "citations": [asdict(c) for c in citations],
348
367
  }
349
368
  ) if is_sensitive else None
@@ -355,6 +374,8 @@ def sanitize_claim_input(
355
374
  is_sensitive=is_sensitive,
356
375
  findings=dedup_findings,
357
376
  encrypted_payload=encrypted_payload,
377
+ subject=redacted_subject,
378
+ predicate=redacted_predicate,
358
379
  )
359
380
 
360
381
 
@@ -251,9 +251,15 @@ class MemoryService:
251
251
  text=text.strip(),
252
252
  object_value=object_value,
253
253
  citations=citations,
254
+ subject=subject,
255
+ predicate=predicate,
254
256
  )
255
257
  if not sanitized.citations:
256
258
  raise ValueError("At least one citation is required.")
259
+ # Use the sanitized subject/predicate everywhere downstream so a secret
260
+ # placed in those fields is redacted at rest, not just at display time.
261
+ subject = sanitized.subject
262
+ predicate = sanitized.predicate
257
263
  # Resolve subject → canonical entity (GBrain-inspired entity registry)
258
264
  # and mine text for pattern-based entities (#127 Wave 3).
259
265
  entity_id = 0
@@ -667,6 +673,11 @@ class MemoryService:
667
673
  rows = self._rehydrate_cached_rows(cached)
668
674
  self._record_accesses(rows, query_text=query_text)
669
675
  return rows
676
+ # Capture the corpus generation BEFORE reading candidates so the cache
677
+ # entry is tagged with the generation it was actually computed against,
678
+ # not whatever it is after ranking/LLM-rerank (which a concurrent claim
679
+ # write could have bumped). See query_cache.write TOCTOU note.
680
+ cache_generation = query_cache.read_generation(cache_path) if cache_path else 0
670
681
  candidate_limit = max(limit * 6, 60, 50 if use_llm_rerank else 0)
671
682
  candidates = self.store.list_claims(
672
683
  limit=candidate_limit,
@@ -733,7 +744,7 @@ class MemoryService:
733
744
  "breakdown": r.get("breakdown"),
734
745
  }
735
746
  for r in results if r.get("claim") is not None
736
- ])
747
+ ], cache_generation)
737
748
  return results
738
749
 
739
750
  def _rehydrate_cached_rows(self, stubs: list[dict[str, Any]]) -> list[dict[str, Any]]: