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.
- {memorymaster-3.22.0/memorymaster.egg-info → memorymaster-3.24.0}/PKG-INFO +4 -3
- {memorymaster-3.22.0 → memorymaster-3.24.0}/README.md +3 -2
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/__init__.py +1 -1
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli.py +7 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_handlers_basic.py +1 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_handlers_curation.py +37 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +27 -31
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/contradiction_probe.py +129 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_cache.py +40 -9
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/security.py +21 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/service.py +12 -1
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward.py +75 -8
- memorymaster-3.24.0/memorymaster/verbatim_cleanup.py +175 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/verbatim_store.py +142 -133
- {memorymaster-3.22.0 → memorymaster-3.24.0/memorymaster.egg-info}/PKG-INFO +4 -3
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/SOURCES.txt +3 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/pyproject.toml +1 -1
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/bench_longmemeval.py +42 -3
- memorymaster-3.24.0/tests/test_auto_ingest_hook_citations.py +102 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_cache.py +26 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_t07.py +42 -1
- memorymaster-3.24.0/tests/test_steward_contradiction_phase.py +181 -0
- memorymaster-3.24.0/tests/test_verbatim_cleanup.py +152 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_store_qdrant.py +28 -0
- memorymaster-3.22.0/tests/test_auto_ingest_hook_citations.py +0 -147
- {memorymaster-3.22.0 → memorymaster-3.24.0}/LICENSE +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/longmemeval_runner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/longmemeval_vector_runner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/benchmarks/perf_smoke.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/__main__.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_lifecycle.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_read.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_shared.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_sources.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/_storage_write_claims.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/access_control.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/action_exporters.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/action_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/atlas_claim_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/atlas_contract.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/auto_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/auto_resolver.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/candidate_dedupe.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/claim_edges.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/claim_verifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/cli_helpers.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/closets.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/claude-md-append.md +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/conflict_resolver.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/connectors/__init__.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/connectors/whatsapp.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/context_hook.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/context_optimizer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/daily_notes.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dashboard.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dashboard_auth.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/db_merge.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/delta_sync.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/dream_bridge.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/embeddings.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_graph.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/entity_registry.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/federated_graphify.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/feedback.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/graph_store.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/hook_log.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/__init__.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/calibration.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/compact_summaries.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/compactor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/daydream_ingest.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/decay.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/dedup.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/deterministic.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/entity_graph_export.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/staleness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/jobs/validator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/key_rotator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/lifecycle.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_budget.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_provider.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_rerank.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/llm_steward.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_path_policy.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_server.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/mcp_usage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/media_processing.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/media_providers.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/metrics_exporter.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0001_initial.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0002_miner_state.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0003_contradiction_verdicts.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/0004_query_cache.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/__init__.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/migrations/runner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/models.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/observability.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/operator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/operator_queue.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/plugins.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/policy.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/postgres_store.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qdrant_backend.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qdrant_recall_fallback.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/qmd_bridge.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/query_expansion.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/recall_fusion.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/recall_tokenizer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/retrieval.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/retry.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/review.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rl_trainer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rule_miner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/rules.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/scheduler.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema.sql +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/schema_postgres.sql +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/scope_utils.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/session_tracker.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/setup_hooks.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/skill_evolver.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/snapshot.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/steward_features.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/storage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/store_factory.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/transcript_miner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/turn_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_bases.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_curator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_exporter.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_linter.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_log.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_query_capture.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/vault_synthesis.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/verbatim_recall.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/webhook.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_engine.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_freshness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_similarity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_suggest.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster/wiki_validate.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/dependency_links.txt +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/entry_points.txt +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/requires.txt +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/memorymaster.egg-info/top_level.txt +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/agg_recall_latency.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/alert_operator_metrics.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/audit_dedupe_precision.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/autoresearch_daemon.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_entity_extraction.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_graph_store.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backfill_stop_hook_citations.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/backtest_steward_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/build_steward_training_set.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/check_hook_template_drift.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/claude_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/codex_live_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_edge_cases.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_trace_report.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/compaction_trace_validate.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/confusion_matrix_eval.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/conversation_importer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/conversation_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/e2e_operator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/email_live_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_bm25_sweep.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_classify_f1.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_memorymaster.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_recall_precision_at_5.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_recall_quality.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_steward_pareto.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/eval_verbatim_recall.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/expand_recall_eval.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/generate_drill_signoff.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/git_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/github_live_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/gitnexus_to_claims.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/grid_recall_weights.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/index_claims_to_qdrant.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/ingest_planning_docs.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/jira_live_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/label_prompts_with_judge.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/llm_benchmark.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/measure_dedupe_thresholds.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/merge_scope_variants.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/messages_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/operator_metrics.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/precompute_candidates.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/recurring_incident_drill.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/release_readiness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/run_codex_autologger.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/run_incident_drill.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/scheduled_ingest.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/setup-hooks.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/slack_live_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/sync_hook_templates.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/tickets_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/train_steward_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/scripts/webhook_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/setup.cfg +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/conftest.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_access_control.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_action_exporters.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_action_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_claim_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_contract.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_atlas_source_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_ingest_hook_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_resolver.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_auto_validate.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_backend_parity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_bm25_per_field.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_calibration.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_calibration_priors_applied.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_candidate_dedupe.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_edges.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_links.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claim_type_ranking.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_classify_hook_f1.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_classify_hook_latency.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_claude_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_dry_run.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_json_flag.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_ready.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_review_queue.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_cli_subcommands.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_closets.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_closets_recall_integration.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compact_summaries.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compact_summaries_sensitivity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compaction_trace.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_compactor_artifact_order.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_config.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_conflict_resolver.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_confusion_matrix_eval.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_connection_retry.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_connectors.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_hook.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_optimizer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_context_optimizer_provider.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_contradiction_probe.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_conversation_to_turns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_auth.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_latency.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_lineage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dashboard_review_queue.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_daydream_ingest.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_db_merge_confidence_conflict.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_db_merge_coverage_v2.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_decay_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_decay_respects_pinned.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup_cli.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dedup_conflict_disambiguation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_delta_sync.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_deterministic_predicates.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dream_bridge_coverage_v2.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_dream_bridge_sensitivity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_embeddings_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_extractor.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_extractor_llm.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_graph.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_graph_export.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_new_kinds.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_regex_v3.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_entity_registry.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_eval_harness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_events_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_extract_llm_ollama.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_federated_graphify_mcp.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_federated_query_safety.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_feedback.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_floor_gate.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_fts5_search.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_graph_distance.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_graph_store.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_handler_regressions.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_hook_env_isolation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_human_id.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_incident_drill_runner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_integration_workflows.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_key_rotator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_lifecycle.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_lifecycle_supersede_invariant.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_budget.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_fallback.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_provider_claude_cli.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_provider_key_rotation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_steward_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_llm_steward_key_rotation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_filter_bypass.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_helpers.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_path_policy.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_rate_limit.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_server_validation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_mcp_usage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_media_processing.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_meta_decisions.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_metrics_exporter.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_migrations.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_observability.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_obsidian_mind_patterns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_operator.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_operator_queue.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_perf_smoke_config.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_plugins.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_policy_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_policy_mode_env.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_postgres_parity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qdrant_backend.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qmd_bridge.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_qrels_regression.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_query_expansion.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_entity_fanout.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_fusion.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_latency.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_precision_at_5.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_tokenizer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_recall_vector_fallback.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_reliability_hardening.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_resolvers_concurrent_supersede.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_profile.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_profiles.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_rrf_tiebreaker.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_retrieval_weights.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_review.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rl_trainer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rrf_auto_gate.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rule_claims.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_rule_miner.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scheduler.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scope_boost.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_scope_utils.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_security_access.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_security_patterns.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_service_coverage.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_session_tracker.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_snapshot.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_snapshot_roundtrip.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_sqlite_core.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_staleness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_stealth_mode.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_classifier.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_daydream_hook.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_features.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_features_v3.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_steward_resolution_parity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_storage_parity.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_store_factory.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_tenant_isolation.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_turn_schema.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_two_pass_recall.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v311_fixes.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v313_e2e.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v313_run_cycle_dedupe.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v390_e2e.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_v391_strict_warnings.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vault_exporter.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vault_linter_orphan.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_vector_search.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_dedup.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_recall.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_verbatim_store.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_webhook.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_webhook_hmac.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_whatsapp_importer.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_autopromote.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_binding.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_engine_idempotency.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_explored_and_contradictions.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_freshness.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_similarity_multiscope.py +0 -0
- {memorymaster-3.22.0 → memorymaster-3.24.0}/tests/test_wiki_suggest.py +0 -0
- {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.
|
|
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)
|
|
54
54
|
[](https://www.python.org/downloads/)
|
|
55
|
-
[]()
|
|
56
56
|
[]()
|
|
57
57
|
[]()
|
|
58
58
|
[](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)
|
|
8
8
|
[](https://www.python.org/downloads/)
|
|
9
|
-
[]()
|
|
10
10
|
[]()
|
|
11
11
|
[]()
|
|
12
12
|
[](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
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 {
|
|
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
|
|
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),
|
|
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
|
-
|
|
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]]:
|