@remnic/core 9.3.722 → 9.3.724
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.
- package/dist/access-boundary.d.ts +6 -11
- package/dist/access-boundary.js +9 -9
- package/dist/access-cli.js +29 -23
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +19 -14
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +18 -13
- package/dist/access-operations-batch.js +16 -10
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +17 -11
- package/dist/{access-service-Dj3I-VBS.d.ts → access-service-B2PlPtxh.d.ts} +3 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +8 -8
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-surface-catalog.js +82 -82
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-F-OEVbRQ.d.ts → catalog-CzUb-GyD.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-EVPNWN7Z.js → chunk-4R3CGCI5.js} +2 -2
- package/dist/{chunk-UO5ZHIIH.js → chunk-4WZ3J7M6.js} +2 -2
- package/dist/{chunk-PD42EYQN.js → chunk-5OPYYPJN.js} +2 -2
- package/dist/{chunk-EEJMD2L2.js → chunk-6CWGS2LY.js} +2 -2
- package/dist/{chunk-7CTNUVHZ.js → chunk-6SQQOJ4U.js} +2 -2
- package/dist/{chunk-ILFSEIRT.js → chunk-7AG54AF3.js} +1 -1
- package/dist/{chunk-ILFSEIRT.js.map → chunk-7AG54AF3.js.map} +1 -1
- package/dist/{chunk-UIXXE6NI.js → chunk-C6BWLYW5.js} +24 -15
- package/dist/chunk-C6BWLYW5.js.map +1 -0
- package/dist/{chunk-A5XNBK2D.js → chunk-DF3I6JGP.js} +6 -6
- package/dist/{chunk-GJSL4WG2.js → chunk-F3XZN24Z.js} +2 -2
- package/dist/{chunk-J3JPB7ZI.js → chunk-FACTVMXQ.js} +2 -2
- package/dist/{chunk-B7WISIAJ.js → chunk-FN6QP7JQ.js} +154 -1067
- package/dist/chunk-FN6QP7JQ.js.map +1 -0
- package/dist/{chunk-EBDA5TYA.js → chunk-FXCIOLRB.js} +2 -2
- package/dist/{chunk-TXQCG6SE.js → chunk-H7HUQIY7.js} +3 -3
- package/dist/{chunk-QD6QP5P6.js → chunk-I2HL5M6T.js} +17 -17
- package/dist/{chunk-M7R5R6OJ.js → chunk-IAIPNEHD.js} +20 -2
- package/dist/{chunk-M7R5R6OJ.js.map → chunk-IAIPNEHD.js.map} +1 -1
- package/dist/{chunk-Z4RU5VPM.js → chunk-J334N6QV.js} +2 -2
- package/dist/{chunk-NXRUUDUF.js → chunk-JJJCVCCQ.js} +2 -2
- package/dist/{chunk-GDUOCHCD.js → chunk-KGZF2VO3.js} +10 -10
- package/dist/{chunk-LA3HZKES.js → chunk-KLFYKC3K.js} +448 -49
- package/dist/chunk-KLFYKC3K.js.map +1 -0
- package/dist/{chunk-LLUGAZ3B.js → chunk-NT36GU6X.js} +30 -4
- package/dist/chunk-NT36GU6X.js.map +1 -0
- package/dist/{chunk-OHTOPFRF.js → chunk-SC2YAWRF.js} +2 -2
- package/dist/{chunk-YEE5KJQQ.js → chunk-T55EGZSJ.js} +2 -2
- package/dist/{chunk-N77ELJ2D.js → chunk-TT5U3UDO.js} +4 -4
- package/dist/{chunk-6FAMMBQ3.js → chunk-UBHVAGO7.js} +2 -2
- package/dist/chunk-VI5SGGXB.js +420 -0
- package/dist/chunk-VI5SGGXB.js.map +1 -0
- package/dist/{chunk-EDOQM4E4.js → chunk-WAFDHKES.js} +2 -2
- package/dist/{chunk-EXUCUPV2.js → chunk-WM7DNKBD.js} +2 -2
- package/dist/{dreams-ledger-3WSCI5V4.js → chunk-YF6FMLBT.js} +5 -6
- package/dist/{dreams-ledger-3WSCI5V4.js.map → chunk-YF6FMLBT.js.map} +1 -1
- package/dist/{chunk-WQYSDPMA.js → chunk-YQMPUYHE.js} +2 -2
- package/dist/{chunk-5O42UKXI.js → chunk-YUYPWJD3.js} +2 -2
- package/dist/{chunk-VCLGUIFM.js → chunk-Z3FGC3QE.js} +2 -2
- package/dist/{chunk-A7TJZLPK.js → chunk-ZK2L72VN.js} +2 -2
- package/dist/{chunk-BXEOHV7K.js → chunk-ZOHMPFOB.js} +3 -3
- package/dist/{graph-edge-decay-QPRJQ7DL.js → chunk-ZQNCHPTZ.js} +4 -5
- package/dist/{graph-edge-decay-QPRJQ7DL.js.map → chunk-ZQNCHPTZ.js.map} +1 -1
- package/dist/{cli-P_GaYcCY.d.ts → cli-IOlDHSBT.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +31 -26
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/dreams-ledger-6UODQOX6.js +22 -0
- package/dist/dreams-ledger-6UODQOX6.js.map +1 -0
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/event-time.d.ts +33 -1
- package/dist/event-time.js +3 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-decay-RXUCJIN4.js +22 -0
- package/dist/graph-edge-decay-RXUCJIN4.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +58 -56
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +8 -8
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +5 -5
- package/dist/{orchestrator-f5xv19kp.d.ts → orchestrator-C_OMSIMZ.d.ts} +34 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +17 -17
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BQm1e1Pn.d.ts → semantic-consolidation-B8LiYtvx.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DwO1Ot4n.d.ts → types-DmTaqob2.d.ts} +14 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +36 -2
- package/src/access-http.ts +17 -8
- package/src/access-mcp.ts +149 -1189
- package/src/access-operations-batch.ts +227 -1439
- package/src/access-surface-catalog.test.ts +4 -15
- package/src/access-surface-catalog.ts +82 -82
- package/src/bitemporal-dedup-backfill.test.ts +169 -0
- package/src/event-time.ts +57 -0
- package/src/extraction-eventtime-wiring.test.ts +101 -1
- package/src/orchestrator.ts +238 -43
- package/src/recall/archive-scoring.ts +526 -0
- package/src/types.ts +14 -0
- package/dist/chunk-B7WISIAJ.js.map +0 -1
- package/dist/chunk-EM2LDXJ2.js +0 -1315
- package/dist/chunk-EM2LDXJ2.js.map +0 -1
- package/dist/chunk-LA3HZKES.js.map +0 -1
- package/dist/chunk-LLUGAZ3B.js.map +0 -1
- package/dist/chunk-UIXXE6NI.js.map +0 -1
- /package/dist/{chunk-EVPNWN7Z.js.map → chunk-4R3CGCI5.js.map} +0 -0
- /package/dist/{chunk-UO5ZHIIH.js.map → chunk-4WZ3J7M6.js.map} +0 -0
- /package/dist/{chunk-PD42EYQN.js.map → chunk-5OPYYPJN.js.map} +0 -0
- /package/dist/{chunk-EEJMD2L2.js.map → chunk-6CWGS2LY.js.map} +0 -0
- /package/dist/{chunk-7CTNUVHZ.js.map → chunk-6SQQOJ4U.js.map} +0 -0
- /package/dist/{chunk-A5XNBK2D.js.map → chunk-DF3I6JGP.js.map} +0 -0
- /package/dist/{chunk-GJSL4WG2.js.map → chunk-F3XZN24Z.js.map} +0 -0
- /package/dist/{chunk-J3JPB7ZI.js.map → chunk-FACTVMXQ.js.map} +0 -0
- /package/dist/{chunk-EBDA5TYA.js.map → chunk-FXCIOLRB.js.map} +0 -0
- /package/dist/{chunk-TXQCG6SE.js.map → chunk-H7HUQIY7.js.map} +0 -0
- /package/dist/{chunk-QD6QP5P6.js.map → chunk-I2HL5M6T.js.map} +0 -0
- /package/dist/{chunk-Z4RU5VPM.js.map → chunk-J334N6QV.js.map} +0 -0
- /package/dist/{chunk-NXRUUDUF.js.map → chunk-JJJCVCCQ.js.map} +0 -0
- /package/dist/{chunk-GDUOCHCD.js.map → chunk-KGZF2VO3.js.map} +0 -0
- /package/dist/{chunk-OHTOPFRF.js.map → chunk-SC2YAWRF.js.map} +0 -0
- /package/dist/{chunk-YEE5KJQQ.js.map → chunk-T55EGZSJ.js.map} +0 -0
- /package/dist/{chunk-N77ELJ2D.js.map → chunk-TT5U3UDO.js.map} +0 -0
- /package/dist/{chunk-6FAMMBQ3.js.map → chunk-UBHVAGO7.js.map} +0 -0
- /package/dist/{chunk-EDOQM4E4.js.map → chunk-WAFDHKES.js.map} +0 -0
- /package/dist/{chunk-EXUCUPV2.js.map → chunk-WM7DNKBD.js.map} +0 -0
- /package/dist/{chunk-WQYSDPMA.js.map → chunk-YQMPUYHE.js.map} +0 -0
- /package/dist/{chunk-5O42UKXI.js.map → chunk-YUYPWJD3.js.map} +0 -0
- /package/dist/{chunk-VCLGUIFM.js.map → chunk-Z3FGC3QE.js.map} +0 -0
- /package/dist/{chunk-A7TJZLPK.js.map → chunk-ZK2L72VN.js.map} +0 -0
- /package/dist/{chunk-BXEOHV7K.js.map → chunk-ZOHMPFOB.js.map} +0 -0
|
@@ -37,21 +37,10 @@ import {
|
|
|
37
37
|
type McpToolEntry,
|
|
38
38
|
} from "./access-surface-catalog.js";
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
// catalog-completeness correction: adding routes that were always live but
|
|
45
|
-
// omitted from the catalog (review-caught). Such a bump MUST be accompanied
|
|
46
|
-
// by the newly-cataloged entries; the higher count is the honest baseline.
|
|
47
|
-
// #1580 Correction Contract adds GET /engram/v1/correction/pending — a read-only
|
|
48
|
-
// list route (namespace/sessionKey query params, no body), so it stays unmigrated
|
|
49
|
-
// alongside the other GET list routes (baseline 80 from #1525 + 1 = 81). Also a
|
|
50
|
-
// catalog-completeness correction: POST /engram/v1/memories (operation memory_store)
|
|
51
|
-
// was always live but omitted from HTTP_ROUTES — it is migrated, so no count change.
|
|
52
|
-
// #1583 remnic chat adds memory_chat — an unmigrated handler dispatched to
|
|
53
|
-
// processChatMessage (carries user input; not a boundary operation) → 81 + 1 = 82.
|
|
54
|
-
const UNMIGRATED_HANDLER_BASELINE = 82;
|
|
40
|
+
// #1668: all remaining MCP tools migrated through the strict-schema boundary
|
|
41
|
+
// (recall, capsule, continuity, work, shared-context, peer, dreams, etc.).
|
|
42
|
+
// memory_chat → chat_message; GET /correction/pending → review_queue_list.
|
|
43
|
+
const UNMIGRATED_HANDLER_BASELINE = 0;
|
|
55
44
|
|
|
56
45
|
// Keep the import live — `getOperation` is the call surfaces use at dispatch
|
|
57
46
|
// time; referencing it here pins the registry's lookup contract.
|
|
@@ -49,35 +49,35 @@ export interface HttpRouteEntry {
|
|
|
49
49
|
// `tools/list` response matches this list by short name, so a new tool cannot
|
|
50
50
|
// land without either migrating it or acknowledging the unmigrated count.
|
|
51
51
|
export const MCP_TOOLS: readonly McpToolEntry[] = [
|
|
52
|
-
{ tool: "recall", operation:
|
|
53
|
-
{ tool: "recall_explain", operation:
|
|
54
|
-
{ tool: "set_coding_context", operation:
|
|
55
|
-
{ tool: "recall_tier_explain", operation:
|
|
56
|
-
{ tool: "recall_xray", operation:
|
|
57
|
-
{ tool: "wearables_status", operation:
|
|
58
|
-
{ tool: "wearables_sync", operation:
|
|
59
|
-
{ tool: "transcript_day", operation:
|
|
60
|
-
{ tool: "transcript_search", operation:
|
|
61
|
-
{ tool: "transcript_memories", operation:
|
|
62
|
-
{ tool: "action_confidence", operation:
|
|
63
|
-
{ tool: "chatgpt_memory_inspector", operation:
|
|
64
|
-
{ tool: "day_summary", operation:
|
|
65
|
-
{ tool: "capsule_export", operation:
|
|
66
|
-
{ tool: "capsule_import", operation:
|
|
67
|
-
{ tool: "capsule_list", operation:
|
|
68
|
-
{ tool: "memory_governance_run", operation:
|
|
69
|
-
{ tool: "procedure_mining_run", operation:
|
|
70
|
-
{ tool: "pattern_reinforcement_run", operation:
|
|
71
|
-
{ tool: "procedural_stats", operation:
|
|
52
|
+
{ tool: "recall", operation: "recall" },
|
|
53
|
+
{ tool: "recall_explain", operation: "recall_explain" },
|
|
54
|
+
{ tool: "set_coding_context", operation: "set_coding_context" },
|
|
55
|
+
{ tool: "recall_tier_explain", operation: "recall_tier_explain" },
|
|
56
|
+
{ tool: "recall_xray", operation: "recall_xray" },
|
|
57
|
+
{ tool: "wearables_status", operation: "wearables_status" },
|
|
58
|
+
{ tool: "wearables_sync", operation: "wearables_sync" },
|
|
59
|
+
{ tool: "transcript_day", operation: "transcript_day" },
|
|
60
|
+
{ tool: "transcript_search", operation: "transcript_search" },
|
|
61
|
+
{ tool: "transcript_memories", operation: "transcript_memories" },
|
|
62
|
+
{ tool: "action_confidence", operation: "action_confidence" },
|
|
63
|
+
{ tool: "chatgpt_memory_inspector", operation: "chatgpt_memory_inspector" },
|
|
64
|
+
{ tool: "day_summary", operation: "day_summary" },
|
|
65
|
+
{ tool: "capsule_export", operation: "capsule_export" },
|
|
66
|
+
{ tool: "capsule_import", operation: "capsule_import" },
|
|
67
|
+
{ tool: "capsule_list", operation: "capsule_list" },
|
|
68
|
+
{ tool: "memory_governance_run", operation: "memory_governance_run" },
|
|
69
|
+
{ tool: "procedure_mining_run", operation: "procedure_mining_run" },
|
|
70
|
+
{ tool: "pattern_reinforcement_run", operation: "pattern_reinforcement_run" },
|
|
71
|
+
{ tool: "procedural_stats", operation: "procedural_stats" },
|
|
72
72
|
{ tool: "memory_get", operation: "memory_get" },
|
|
73
|
-
{ tool: "memory_timeline", operation:
|
|
73
|
+
{ tool: "memory_timeline", operation: "memory_timeline" },
|
|
74
74
|
{ tool: "memory_store", operation: "memory_store" },
|
|
75
75
|
// Correction Contract (issue #1580) — one plan/apply pipeline.
|
|
76
76
|
{ tool: "memory_correct_plan", operation: "memory_correct_plan" },
|
|
77
77
|
{ tool: "memory_correct_apply", operation: "memory_correct_apply" },
|
|
78
78
|
// Conversational memory inspection + correction (issue #1583) — unmigrated
|
|
79
79
|
// handler dispatched to processChatMessage (not a boundary operation).
|
|
80
|
-
{ tool: "memory_chat", operation:
|
|
80
|
+
{ tool: "memory_chat", operation: "chat_message" },
|
|
81
81
|
{ tool: "coding_decision", operation: "coding_decision" },
|
|
82
82
|
{ tool: "coding_architecture", operation: "coding_architecture" },
|
|
83
83
|
// codegraph parity tools (issue #1554)
|
|
@@ -96,66 +96,66 @@ export const MCP_TOOLS: readonly McpToolEntry[] = [
|
|
|
96
96
|
{ tool: "codegraph_manage_adr", operation: "codegraph_manage_adr" },
|
|
97
97
|
{ tool: "codegraph_ingest_traces", operation: "codegraph_ingest_traces" },
|
|
98
98
|
{ tool: "coding_delta", operation: "coding_delta" },
|
|
99
|
-
{ tool: "suggestion_submit", operation:
|
|
100
|
-
{ tool: "entity_get", operation:
|
|
101
|
-
{ tool: "review_queue_list", operation:
|
|
102
|
-
{ tool: "observe", operation:
|
|
103
|
-
{ tool: "lcm_search", operation:
|
|
104
|
-
{ tool: "lcm_compaction_flush", operation:
|
|
105
|
-
{ tool: "lcm_compaction_record", operation:
|
|
106
|
-
{ tool: "continuity_audit_generate", operation:
|
|
107
|
-
{ tool: "continuity_incident_open", operation:
|
|
108
|
-
{ tool: "continuity_incident_close", operation:
|
|
109
|
-
{ tool: "continuity_incident_list", operation:
|
|
110
|
-
{ tool: "continuity_loop_add_or_update", operation:
|
|
111
|
-
{ tool: "continuity_loop_review", operation:
|
|
112
|
-
{ tool: "identity_anchor_get", operation:
|
|
113
|
-
{ tool: "identity_anchor_update", operation:
|
|
114
|
-
{ tool: "memory_identity", operation:
|
|
115
|
-
{ tool: "work_task", operation:
|
|
116
|
-
{ tool: "work_project", operation:
|
|
117
|
-
{ tool: "work_board", operation:
|
|
118
|
-
{ tool: "shared_context_write_output", operation:
|
|
119
|
-
{ tool: "shared_feedback_record", operation:
|
|
120
|
-
{ tool: "shared_priorities_append", operation:
|
|
121
|
-
{ tool: "shared_context_cross_signals_run", operation:
|
|
122
|
-
{ tool: "shared_context_curate_daily", operation:
|
|
123
|
-
{ tool: "compounding_weekly_synthesize", operation:
|
|
124
|
-
{ tool: "compounding_promote_candidate", operation:
|
|
125
|
-
{ tool: "compression_guidelines_optimize", operation:
|
|
126
|
-
{ tool: "compression_guidelines_activate", operation:
|
|
99
|
+
{ tool: "suggestion_submit", operation: "suggestion_submit" },
|
|
100
|
+
{ tool: "entity_get", operation: "entity_get" },
|
|
101
|
+
{ tool: "review_queue_list", operation: "review_queue_list" },
|
|
102
|
+
{ tool: "observe", operation: "observe" },
|
|
103
|
+
{ tool: "lcm_search", operation: "lcm_search" },
|
|
104
|
+
{ tool: "lcm_compaction_flush", operation: "lcm_compaction_flush" },
|
|
105
|
+
{ tool: "lcm_compaction_record", operation: "lcm_compaction_record" },
|
|
106
|
+
{ tool: "continuity_audit_generate", operation: "continuity_audit_generate" },
|
|
107
|
+
{ tool: "continuity_incident_open", operation: "continuity_incident_open" },
|
|
108
|
+
{ tool: "continuity_incident_close", operation: "continuity_incident_close" },
|
|
109
|
+
{ tool: "continuity_incident_list", operation: "continuity_incident_list" },
|
|
110
|
+
{ tool: "continuity_loop_add_or_update", operation: "continuity_loop_add_or_update" },
|
|
111
|
+
{ tool: "continuity_loop_review", operation: "continuity_loop_review" },
|
|
112
|
+
{ tool: "identity_anchor_get", operation: "identity_anchor_get" },
|
|
113
|
+
{ tool: "identity_anchor_update", operation: "identity_anchor_update" },
|
|
114
|
+
{ tool: "memory_identity", operation: "memory_identity" },
|
|
115
|
+
{ tool: "work_task", operation: "work_task" },
|
|
116
|
+
{ tool: "work_project", operation: "work_project" },
|
|
117
|
+
{ tool: "work_board", operation: "work_board" },
|
|
118
|
+
{ tool: "shared_context_write_output", operation: "shared_context_write_output" },
|
|
119
|
+
{ tool: "shared_feedback_record", operation: "shared_feedback_record" },
|
|
120
|
+
{ tool: "shared_priorities_append", operation: "shared_priorities_append" },
|
|
121
|
+
{ tool: "shared_context_cross_signals_run", operation: "shared_context_cross_signals_run" },
|
|
122
|
+
{ tool: "shared_context_curate_daily", operation: "shared_context_curate_daily" },
|
|
123
|
+
{ tool: "compounding_weekly_synthesize", operation: "compounding_weekly_synthesize" },
|
|
124
|
+
{ tool: "compounding_promote_candidate", operation: "compounding_promote_candidate" },
|
|
125
|
+
{ tool: "compression_guidelines_optimize", operation: "compression_guidelines_optimize" },
|
|
126
|
+
{ tool: "compression_guidelines_activate", operation: "compression_guidelines_activate" },
|
|
127
127
|
{ tool: "memory_search", operation: "memory_search" },
|
|
128
|
-
{ tool: "memory_profile", operation:
|
|
129
|
-
{ tool: "memory_entities_list", operation:
|
|
130
|
-
{ tool: "memory_questions", operation:
|
|
131
|
-
{ tool: "memory_last_recall", operation:
|
|
132
|
-
{ tool: "memory_intent_debug", operation:
|
|
133
|
-
{ tool: "memory_qmd_debug", operation:
|
|
134
|
-
{ tool: "memory_graph_explain", operation:
|
|
135
|
-
{ tool: "graph_snapshot", operation:
|
|
136
|
-
{ tool: "memory_feedback", operation:
|
|
137
|
-
{ tool: "memory_promote", operation:
|
|
138
|
-
{ tool: "memory_outcome", operation:
|
|
139
|
-
{ tool: "memory_action_apply", operation:
|
|
140
|
-
{ tool: "context_checkpoint", operation:
|
|
141
|
-
{ tool: "briefing", operation:
|
|
142
|
-
{ tool: "review_list", operation:
|
|
143
|
-
{ tool: "review_resolve", operation:
|
|
144
|
-
{ tool: "contradiction_scan_run", operation:
|
|
145
|
-
{ tool: "memory_summarize_hourly", operation:
|
|
146
|
-
{ tool: "conversation_index_update", operation:
|
|
147
|
-
{ tool: "profiling_report", operation:
|
|
148
|
-
{ tool: "graph_edge_decay_run", operation:
|
|
149
|
-
{ tool: "live_connectors_run", operation:
|
|
150
|
-
{ tool: "peer_list", operation:
|
|
151
|
-
{ tool: "peer_get", operation:
|
|
152
|
-
{ tool: "peer_set", operation:
|
|
153
|
-
{ tool: "peer_delete", operation:
|
|
154
|
-
{ tool: "peer_profile_get", operation:
|
|
155
|
-
{ tool: "peer_forget", operation:
|
|
156
|
-
{ tool: "console_state", operation:
|
|
157
|
-
{ tool: "dreams_status", operation:
|
|
158
|
-
{ tool: "dreams_run", operation:
|
|
128
|
+
{ tool: "memory_profile", operation: "memory_profile" },
|
|
129
|
+
{ tool: "memory_entities_list", operation: "memory_entities_list" },
|
|
130
|
+
{ tool: "memory_questions", operation: "memory_questions" },
|
|
131
|
+
{ tool: "memory_last_recall", operation: "memory_last_recall" },
|
|
132
|
+
{ tool: "memory_intent_debug", operation: "memory_intent_debug" },
|
|
133
|
+
{ tool: "memory_qmd_debug", operation: "memory_qmd_debug" },
|
|
134
|
+
{ tool: "memory_graph_explain", operation: "memory_graph_explain" },
|
|
135
|
+
{ tool: "graph_snapshot", operation: "graph_snapshot" },
|
|
136
|
+
{ tool: "memory_feedback", operation: "memory_feedback" },
|
|
137
|
+
{ tool: "memory_promote", operation: "memory_promote" },
|
|
138
|
+
{ tool: "memory_outcome", operation: "memory_outcome" },
|
|
139
|
+
{ tool: "memory_action_apply", operation: "memory_action_apply" },
|
|
140
|
+
{ tool: "context_checkpoint", operation: "context_checkpoint" },
|
|
141
|
+
{ tool: "briefing", operation: "briefing" },
|
|
142
|
+
{ tool: "review_list", operation: "review_list" },
|
|
143
|
+
{ tool: "review_resolve", operation: "review_resolve" },
|
|
144
|
+
{ tool: "contradiction_scan_run", operation: "contradiction_scan_run" },
|
|
145
|
+
{ tool: "memory_summarize_hourly", operation: "memory_summarize_hourly" },
|
|
146
|
+
{ tool: "conversation_index_update", operation: "conversation_index_update" },
|
|
147
|
+
{ tool: "profiling_report", operation: "profiling_report" },
|
|
148
|
+
{ tool: "graph_edge_decay_run", operation: "graph_edge_decay_run" },
|
|
149
|
+
{ tool: "live_connectors_run", operation: "live_connectors_run" },
|
|
150
|
+
{ tool: "peer_list", operation: "peer_list" },
|
|
151
|
+
{ tool: "peer_get", operation: "peer_get" },
|
|
152
|
+
{ tool: "peer_set", operation: "peer_set" },
|
|
153
|
+
{ tool: "peer_delete", operation: "peer_delete" },
|
|
154
|
+
{ tool: "peer_profile_get", operation: "peer_profile_get" },
|
|
155
|
+
{ tool: "peer_forget", operation: "peer_forget" },
|
|
156
|
+
{ tool: "console_state", operation: "console_state" },
|
|
157
|
+
{ tool: "dreams_status", operation: "dreams_status" },
|
|
158
|
+
{ tool: "dreams_run", operation: "dreams_run" },
|
|
159
159
|
];
|
|
160
160
|
|
|
161
161
|
// Each route below corresponds 1:1 to a service-invoking route branch in
|
|
@@ -193,7 +193,7 @@ export const HTTP_ROUTES: readonly HttpRouteEntry[] = [
|
|
|
193
193
|
// Correction Contract (issue #1580) — plan/apply/pending.
|
|
194
194
|
{ method: "POST", pathname: "/engram/v1/correction/plan", operation: "memory_correct_plan" },
|
|
195
195
|
{ method: "POST", pathname: "/engram/v1/correction/apply", operation: "memory_correct_apply" },
|
|
196
|
-
{ method: "GET", pathname: "/engram/v1/correction/pending", operation:
|
|
196
|
+
{ method: "GET", pathname: "/engram/v1/correction/pending", operation: "correction_pending" },
|
|
197
197
|
{ method: "POST", pathname: "/engram/v1/memories", operation: "memory_store" },
|
|
198
198
|
{ method: "POST", pathname: "/engram/v1/coding/decisions", operation: "coding_decision" },
|
|
199
199
|
{ method: "POST", pathname: "/engram/v1/coding/architecture", operation: "coding_architecture" },
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
import { StorageManager, ContentHashIndex } from "./storage.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Issue #1671 — backfill invalid_at/observedAt onto deduped promotion copies.
|
|
11
|
+
*
|
|
12
|
+
* When a fact is re-extracted and now carries a resolved bi-temporal end bound
|
|
13
|
+
* (e.g. "until June 2025"), but the existing copy (source-namespace or promoted)
|
|
14
|
+
* was written before this metadata existed, the dedup short-circuit must patch
|
|
15
|
+
* the existing copy's frontmatter — otherwise recall keeps surfacing an expired
|
|
16
|
+
* fact. These tests prove the storage-level backfill mechanism the orchestrator
|
|
17
|
+
* helper (`backfillTemporalBoundsOnDedupHit`) relies on:
|
|
18
|
+
*
|
|
19
|
+
* 1. writeMemoryFrontmatter patches invalid_at/observedAt/eventTimeSource.
|
|
20
|
+
* 2. The content-hash lookup (ContentHashIndex.normalizeContent) finds the
|
|
21
|
+
* existing copy by normalized content.
|
|
22
|
+
* 3. The patch only fills fields the copy LACKS — never clobbers an existing
|
|
23
|
+
* bound.
|
|
24
|
+
*/
|
|
25
|
+
async function makeStorage(prefix: string): Promise<{
|
|
26
|
+
storage: StorageManager;
|
|
27
|
+
cleanup: () => Promise<void>;
|
|
28
|
+
}> {
|
|
29
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), prefix));
|
|
30
|
+
const storage = new StorageManager(dir);
|
|
31
|
+
await storage.ensureDirectories();
|
|
32
|
+
return {
|
|
33
|
+
storage,
|
|
34
|
+
cleanup: async () => {
|
|
35
|
+
await rm(dir, { recursive: true, force: true });
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("#1671: writeMemoryFrontmatter patches invalid_at onto an existing fact that lacks it", async () => {
|
|
41
|
+
const { storage, cleanup } = await makeStorage("bitemporal-backfill-patch-");
|
|
42
|
+
try {
|
|
43
|
+
// Write a fact WITHOUT bi-temporal bounds (simulates a pre-#1578 copy).
|
|
44
|
+
const id = await storage.writeMemory("fact", "We use PostgreSQL for the main db.", {
|
|
45
|
+
confidence: 0.9,
|
|
46
|
+
});
|
|
47
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
48
|
+
assert.ok(before, "fact must exist");
|
|
49
|
+
assert.equal(before!.frontmatter.invalid_at, undefined);
|
|
50
|
+
assert.equal(before!.frontmatter.observedAt, undefined);
|
|
51
|
+
|
|
52
|
+
// Backfill: patch invalid_at + observedAt + eventTimeSource.
|
|
53
|
+
const ok = await storage.writeMemoryFrontmatter(before!, {
|
|
54
|
+
invalid_at: "2025-07-01T00:00:00.000Z",
|
|
55
|
+
observedAt: "2025-06-20T00:00:00.000Z",
|
|
56
|
+
eventTimeSource: "extracted",
|
|
57
|
+
});
|
|
58
|
+
assert.equal(ok, true);
|
|
59
|
+
|
|
60
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
61
|
+
assert.ok(after, "fact must still exist after patch");
|
|
62
|
+
assert.equal(after!.frontmatter.invalid_at, "2025-07-01T00:00:00.000Z");
|
|
63
|
+
assert.equal(after!.frontmatter.observedAt, "2025-06-20T00:00:00.000Z");
|
|
64
|
+
assert.equal(after!.frontmatter.eventTimeSource, "extracted");
|
|
65
|
+
} finally {
|
|
66
|
+
await cleanup();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("#1671: existing copy found by normalized content hash for backfill", async () => {
|
|
71
|
+
const { storage, cleanup } = await makeStorage("bitemporal-backfill-lookup-");
|
|
72
|
+
try {
|
|
73
|
+
const content = "The database is MySQL.";
|
|
74
|
+
await storage.writeMemory("fact", content, {
|
|
75
|
+
confidence: 0.9,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Simulate the orchestrator's lookup: normalize the incoming content and
|
|
79
|
+
// find the active fact whose normalized content matches.
|
|
80
|
+
const normalizedIncoming = ContentHashIndex.normalizeContent(content);
|
|
81
|
+
const all = await storage.readAllMemories();
|
|
82
|
+
const existing = all.find(
|
|
83
|
+
(m) =>
|
|
84
|
+
m.frontmatter.category === "fact" &&
|
|
85
|
+
(m.frontmatter.status ?? "active") === "active" &&
|
|
86
|
+
ContentHashIndex.normalizeContent(m.content ?? "") === normalizedIncoming,
|
|
87
|
+
);
|
|
88
|
+
assert.ok(existing, "must find the existing fact by normalized content");
|
|
89
|
+
} finally {
|
|
90
|
+
await cleanup();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("#1671: backfill does NOT clobber an existing invalid_at on the copy", async () => {
|
|
95
|
+
const { storage, cleanup } = await makeStorage("bitemporal-backfill-noclobber-");
|
|
96
|
+
try {
|
|
97
|
+
// The existing copy already has an explicit invalid_at.
|
|
98
|
+
const id = await storage.writeMemory("fact", "We use MySQL until December.", {
|
|
99
|
+
confidence: 0.9,
|
|
100
|
+
invalidAt: "2025-12-01T00:00:00.000Z",
|
|
101
|
+
observedAt: "2025-06-01T00:00:00.000Z",
|
|
102
|
+
eventTimeSource: "extracted",
|
|
103
|
+
});
|
|
104
|
+
const before = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
105
|
+
assert.ok(before);
|
|
106
|
+
|
|
107
|
+
// Attempt to backfill a DIFFERENT invalid_at — the patch should only fill
|
|
108
|
+
// MISSING fields, not overwrite. (The orchestrator helper checks before
|
|
109
|
+
// patching; here we prove the helper's gate logic is correct by simulating
|
|
110
|
+
// the same condition: only patch if the field is absent/empty.)
|
|
111
|
+
const patch: Record<string, unknown> = {};
|
|
112
|
+
const fm = before!.frontmatter;
|
|
113
|
+
if (!fm.invalid_at || fm.invalid_at.length === 0) {
|
|
114
|
+
patch.invalid_at = "2025-07-01T00:00:00.000Z";
|
|
115
|
+
}
|
|
116
|
+
// The existing copy already has invalid_at, so the patch is empty.
|
|
117
|
+
assert.equal(Object.keys(patch).length, 0,
|
|
118
|
+
"must not patch invalid_at when the copy already has it");
|
|
119
|
+
} finally {
|
|
120
|
+
await cleanup();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("#1671: re-extraction with resolved invalidAt backfills an old promoted copy (end-to-end storage)", async () => {
|
|
125
|
+
const { storage, cleanup } = await makeStorage("bitemporal-backfill-e2e-");
|
|
126
|
+
try {
|
|
127
|
+
// Step 1: write a fact WITHOUT bounds (old promoted copy).
|
|
128
|
+
const content = "The API rate limit is 100 req/min.";
|
|
129
|
+
const id = await storage.writeMemory("fact", content, {
|
|
130
|
+
confidence: 0.9,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Step 2: simulate re-extraction that now carries a resolved invalidAt.
|
|
134
|
+
// The dedup short-circuit finds the existing copy and patches it.
|
|
135
|
+
const normalizedIncoming = ContentHashIndex.normalizeContent(content);
|
|
136
|
+
const all = await storage.readAllMemories();
|
|
137
|
+
const existing = all.find(
|
|
138
|
+
(m) =>
|
|
139
|
+
m.frontmatter.category === "fact" &&
|
|
140
|
+
(m.frontmatter.status ?? "active") === "active" &&
|
|
141
|
+
ContentHashIndex.normalizeContent(m.content ?? "") === normalizedIncoming,
|
|
142
|
+
);
|
|
143
|
+
assert.ok(existing, "must find the existing copy");
|
|
144
|
+
|
|
145
|
+
const patch: Record<string, string> = {};
|
|
146
|
+
if (!existing!.frontmatter.invalid_at) {
|
|
147
|
+
patch.invalid_at = "2025-09-01T00:00:00.000Z";
|
|
148
|
+
}
|
|
149
|
+
if (!existing!.frontmatter.observedAt) {
|
|
150
|
+
patch.observedAt = "2025-06-20T00:00:00.000Z";
|
|
151
|
+
}
|
|
152
|
+
if (!existing!.frontmatter.eventTimeSource) {
|
|
153
|
+
patch.eventTimeSource = "extracted";
|
|
154
|
+
}
|
|
155
|
+
assert.ok(Object.keys(patch).length > 0, "must have fields to backfill");
|
|
156
|
+
|
|
157
|
+
const ok = await storage.writeMemoryFrontmatter(existing!, patch);
|
|
158
|
+
assert.equal(ok, true);
|
|
159
|
+
|
|
160
|
+
// Step 3: verify the patched copy now expires correctly.
|
|
161
|
+
const after = (await storage.readAllMemories()).find((m) => m.frontmatter.id === id);
|
|
162
|
+
assert.ok(after);
|
|
163
|
+
assert.equal(after!.frontmatter.invalid_at, "2025-09-01T00:00:00.000Z");
|
|
164
|
+
assert.equal(after!.frontmatter.observedAt, "2025-06-20T00:00:00.000Z");
|
|
165
|
+
assert.equal(after!.frontmatter.eventTimeSource, "extracted");
|
|
166
|
+
} finally {
|
|
167
|
+
await cleanup();
|
|
168
|
+
}
|
|
169
|
+
});
|
package/src/event-time.ts
CHANGED
|
@@ -619,3 +619,60 @@ export function resolveFactEventTime(
|
|
|
619
619
|
eventTimeSource: "assumed",
|
|
620
620
|
};
|
|
621
621
|
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Input shape for {@link pickFactEventTimeAnchor} — a structural slice of
|
|
625
|
+
* `ExtractedFact` so the helper stays decoupled from the full type.
|
|
626
|
+
*/
|
|
627
|
+
export interface FactAnchorInput {
|
|
628
|
+
/** Explicit per-fact source-turn timestamp (#1670). */
|
|
629
|
+
sourceTurnTimestamp?: string;
|
|
630
|
+
/** Claim-level provenance spans (#1575), each carrying the turn's `observedAt`. */
|
|
631
|
+
sources?: { observedAt: string }[];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Issue #1670 — select the per-fact event-time anchor.
|
|
636
|
+
*
|
|
637
|
+
* Prefers the fact's explicit `sourceTurnTimestamp` (set by extractors that
|
|
638
|
+
* know the exact source turn). Falls back to the earliest provenance span's
|
|
639
|
+
* `observedAt` (the turn where the supporting quote was located). Finally
|
|
640
|
+
* falls back to the batch-wide anchor (latest source turn timestamp) when
|
|
641
|
+
* neither per-fact signal is present — preserving legacy behaviour for
|
|
642
|
+
* extractors that do not emit provenance.
|
|
643
|
+
*
|
|
644
|
+
* The earliest provenance span wins because a fact backed by quotes from
|
|
645
|
+
* multiple turns was *first* uttered at the earliest turn: a relative
|
|
646
|
+
* expression ("yesterday") on that fact should resolve against that turn,
|
|
647
|
+
* not a later turn that merely restated the claim.
|
|
648
|
+
*
|
|
649
|
+
* Returns `undefined` only when no signal is usable AND `batchAnchor` is
|
|
650
|
+
* absent — in which case the caller skips bi-temporal resolution entirely
|
|
651
|
+
* (identical to the pre-feature pipeline when biTemporal is off).
|
|
652
|
+
*/
|
|
653
|
+
export function pickFactEventTimeAnchor(
|
|
654
|
+
fact: FactAnchorInput,
|
|
655
|
+
batchAnchor: string | undefined,
|
|
656
|
+
): string | undefined {
|
|
657
|
+
// 1. Explicit per-fact source-turn timestamp (highest priority).
|
|
658
|
+
if (
|
|
659
|
+
typeof fact.sourceTurnTimestamp === "string" &&
|
|
660
|
+
fact.sourceTurnTimestamp.trim().length > 0
|
|
661
|
+
) {
|
|
662
|
+
const ms = parseFlexibleIsoTimestamp(fact.sourceTurnTimestamp.trim());
|
|
663
|
+
if (ms !== null) return new Date(ms).toISOString();
|
|
664
|
+
}
|
|
665
|
+
// 2. Earliest provenance span turn timestamp.
|
|
666
|
+
if (fact.sources && fact.sources.length > 0) {
|
|
667
|
+
let earliestMs: number | null = null;
|
|
668
|
+
for (const src of fact.sources) {
|
|
669
|
+
if (typeof src.observedAt !== "string") continue;
|
|
670
|
+
const ms = parseFlexibleIsoTimestamp(src.observedAt.trim());
|
|
671
|
+
if (ms === null) continue;
|
|
672
|
+
if (earliestMs === null || ms < earliestMs) earliestMs = ms;
|
|
673
|
+
}
|
|
674
|
+
if (earliestMs !== null) return new Date(earliestMs).toISOString();
|
|
675
|
+
}
|
|
676
|
+
// 3. Batch-wide anchor (legacy fallback).
|
|
677
|
+
return batchAnchor;
|
|
678
|
+
}
|
|
@@ -4,7 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
6
|
|
|
7
|
-
import { resolveFactEventTime } from "./event-time.js";
|
|
7
|
+
import { pickFactEventTimeAnchor, resolveFactEventTime } from "./event-time.js";
|
|
8
8
|
import { ExtractionEngine } from "./extraction.js";
|
|
9
9
|
import { ExtractedFactSchema } from "./schemas.js";
|
|
10
10
|
import { StorageManager } from "./storage.js";
|
|
@@ -267,3 +267,103 @@ test("resolveFactEventTime: bare '2025-03' (YYYY-MM) → validFrom = first of mo
|
|
|
267
267
|
assert.equal(result.validFrom, "2025-03-01T00:00:00.000Z");
|
|
268
268
|
assert.equal(result.validUntil, undefined);
|
|
269
269
|
});
|
|
270
|
+
|
|
271
|
+
// ── #1670: per-fact source-turn anchor selection ─────────────────────────
|
|
272
|
+
|
|
273
|
+
test("pickFactEventTimeAnchor: explicit sourceTurnTimestamp wins over batch anchor", () => {
|
|
274
|
+
// A buffered batch's latest turn is 2025-04-12, but the fact was extracted
|
|
275
|
+
// from an earlier turn on 2025-04-10. The per-fact timestamp must win.
|
|
276
|
+
const anchor = pickFactEventTimeAnchor(
|
|
277
|
+
{ sourceTurnTimestamp: "2025-04-10T12:00:00.000Z" },
|
|
278
|
+
"2025-04-12T12:00:00.000Z",
|
|
279
|
+
);
|
|
280
|
+
assert.equal(anchor, "2025-04-10T12:00:00.000Z");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("pickFactEventTimeAnchor: earliest provenance span observedAt used when no sourceTurnTimestamp", () => {
|
|
284
|
+
// A fact backed by spans from two turns: the EARLIEST span's observedAt is
|
|
285
|
+
// the turn where the claim was first uttered — the correct anchor for a
|
|
286
|
+
// relative expression like "yesterday".
|
|
287
|
+
const anchor = pickFactEventTimeAnchor(
|
|
288
|
+
{
|
|
289
|
+
sources: [
|
|
290
|
+
{ observedAt: "2025-04-11T08:00:00.000Z" },
|
|
291
|
+
{ observedAt: "2025-04-10T08:00:00.000Z" },
|
|
292
|
+
{ observedAt: "2025-04-12T08:00:00.000Z" },
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
"2025-04-12T08:00:00.000Z",
|
|
296
|
+
);
|
|
297
|
+
assert.equal(anchor, "2025-04-10T08:00:00.000Z");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("pickFactEventTimeAnchor: sourceTurnTimestamp takes precedence over provenance spans", () => {
|
|
301
|
+
const anchor = pickFactEventTimeAnchor(
|
|
302
|
+
{
|
|
303
|
+
sourceTurnTimestamp: "2025-04-09T00:00:00.000Z",
|
|
304
|
+
sources: [{ observedAt: "2025-04-11T00:00:00.000Z" }],
|
|
305
|
+
},
|
|
306
|
+
"2025-04-12T00:00:00.000Z",
|
|
307
|
+
);
|
|
308
|
+
assert.equal(anchor, "2025-04-09T00:00:00.000Z");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("pickFactEventTimeAnchor: falls back to batch anchor when no per-fact signal", () => {
|
|
312
|
+
const anchor = pickFactEventTimeAnchor({}, "2025-04-12T12:00:00.000Z");
|
|
313
|
+
assert.equal(anchor, "2025-04-12T12:00:00.000Z");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("pickFactEventTimeAnchor: returns undefined when no signal and no batch anchor", () => {
|
|
317
|
+
const anchor = pickFactEventTimeAnchor({}, undefined);
|
|
318
|
+
assert.equal(anchor, undefined);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("pickFactEventTimeAnchor: ignores corrupt sourceTurnTimestamp, falls through to provenance", () => {
|
|
322
|
+
const anchor = pickFactEventTimeAnchor(
|
|
323
|
+
{
|
|
324
|
+
sourceTurnTimestamp: "not-a-timestamp",
|
|
325
|
+
sources: [{ observedAt: "2025-04-10T08:00:00.000Z" }],
|
|
326
|
+
},
|
|
327
|
+
"2025-04-12T08:00:00.000Z",
|
|
328
|
+
);
|
|
329
|
+
assert.equal(anchor, "2025-04-10T08:00:00.000Z");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("pickFactEventTimeAnchor: ignores corrupt provenance observedAt entries, falls through to batch", () => {
|
|
333
|
+
const anchor = pickFactEventTimeAnchor(
|
|
334
|
+
{ sources: [{ observedAt: "garbage" }, { observedAt: "also-bad" }] },
|
|
335
|
+
"2025-04-12T08:00:00.000Z",
|
|
336
|
+
);
|
|
337
|
+
assert.equal(anchor, "2025-04-12T08:00:00.000Z");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("#1670 integration: 'yesterday' on an early-turn fact resolves against the early turn, not the batch", () => {
|
|
341
|
+
// Simulates the exact scenario from the issue: a buffered conversation
|
|
342
|
+
// spanning a date boundary. The fact carries provenance from the early turn.
|
|
343
|
+
const earlyAnchor = pickFactEventTimeAnchor(
|
|
344
|
+
{ sources: [{ observedAt: "2025-01-15T10:00:00.000Z" }] },
|
|
345
|
+
"2025-01-17T10:00:00.000Z", // batch latest = 3 days later
|
|
346
|
+
);
|
|
347
|
+
assert.ok(earlyAnchor, "anchor must resolve from provenance");
|
|
348
|
+
const result = resolveFactEventTime("yesterday", earlyAnchor);
|
|
349
|
+
assert.ok(result.validFrom!.startsWith("2025-01-14"),
|
|
350
|
+
`yesterday relative to early turn should be 2025-01-14, got ${result.validFrom}`);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("#1670 integration: same expression, batch vs source-turn anchor → different validFrom", () => {
|
|
354
|
+
// Without per-fact provenance, "yesterday" resolves against the batch
|
|
355
|
+
// (latest turn = 2025-01-17 → 2025-01-16). With provenance from the early
|
|
356
|
+
// turn (2025-01-15), it resolves to 2025-01-14. The two must differ.
|
|
357
|
+
const batchAnchor = pickFactEventTimeAnchor({}, "2025-01-17T10:00:00.000Z");
|
|
358
|
+
assert.ok(batchAnchor);
|
|
359
|
+
const batchResult = resolveFactEventTime("yesterday", batchAnchor);
|
|
360
|
+
const provenanceAnchor = pickFactEventTimeAnchor(
|
|
361
|
+
{ sources: [{ observedAt: "2025-01-15T10:00:00.000Z" }] },
|
|
362
|
+
"2025-01-17T10:00:00.000Z",
|
|
363
|
+
);
|
|
364
|
+
assert.ok(provenanceAnchor);
|
|
365
|
+
const provenanceResult = resolveFactEventTime("yesterday", provenanceAnchor);
|
|
366
|
+
assert.notEqual(batchResult.validFrom, provenanceResult.validFrom);
|
|
367
|
+
assert.ok(batchResult.validFrom!.startsWith("2025-01-16"));
|
|
368
|
+
assert.ok(provenanceResult.validFrom!.startsWith("2025-01-14"));
|
|
369
|
+
});
|