@remnic/core 9.3.628 → 9.3.630

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/dist/access-cli.js +18 -18
  2. package/dist/access-http.d.ts +6 -4
  3. package/dist/access-http.js +7 -6
  4. package/dist/access-mcp.d.ts +6 -4
  5. package/dist/access-mcp.js +6 -5
  6. package/dist/{access-service-C_sfOHsX.d.ts → access-service-C4v-eFjB.d.ts} +2 -2
  7. package/dist/access-service.d.ts +6 -4
  8. package/dist/access-service.js +5 -4
  9. package/dist/action-confidence.d.ts +1 -1
  10. package/dist/active-memory-bridge.d.ts +1 -1
  11. package/dist/active-recall.d.ts +1 -1
  12. package/dist/active-recall.js +1 -1
  13. package/dist/behavior-learner.d.ts +1 -1
  14. package/dist/behavior-signals.d.ts +1 -1
  15. package/dist/bootstrap.d.ts +6 -4
  16. package/dist/briefing.d.ts +33 -2
  17. package/dist/briefing.js +5 -2
  18. package/dist/buffer-surprise-report.d.ts +1 -1
  19. package/dist/buffer.d.ts +1 -1
  20. package/dist/calibration.d.ts +1 -1
  21. package/dist/calibration.js +2 -2
  22. package/dist/causal-behavior.d.ts +1 -1
  23. package/dist/causal-consolidation.d.ts +1 -1
  24. package/dist/causal-consolidation.js +5 -5
  25. package/dist/{chunk-GE7Q7KXP.js → chunk-2VJ7AJFX.js} +2 -2
  26. package/dist/{chunk-KVFYTRMV.js → chunk-4QEUKASL.js} +2 -2
  27. package/dist/{chunk-KB4MFBF5.js → chunk-5S6IREG3.js} +3 -3
  28. package/dist/{chunk-LQYTQCXM.js → chunk-6LBQL5US.js} +2 -2
  29. package/dist/{chunk-KGIGRNR6.js → chunk-723OMPUI.js} +4 -4
  30. package/dist/{chunk-TZDSNIRO.js → chunk-ADOD7PJC.js} +5 -5
  31. package/dist/{chunk-SHV5Y2WU.js → chunk-BL33LBTN.js} +3 -3
  32. package/dist/{chunk-532VCWYW.js → chunk-BWK5EEKS.js} +2 -2
  33. package/dist/{chunk-KKTXCFD7.js → chunk-EORL2IDM.js} +39 -8
  34. package/dist/{chunk-KKTXCFD7.js.map → chunk-EORL2IDM.js.map} +1 -1
  35. package/dist/{chunk-F3FY3D3S.js → chunk-F6USGHMO.js} +10 -5
  36. package/dist/chunk-F6USGHMO.js.map +1 -0
  37. package/dist/{chunk-STDAAGH7.js → chunk-GXWFZYSR.js} +39 -3
  38. package/dist/chunk-GXWFZYSR.js.map +1 -0
  39. package/dist/{chunk-3VONWEQB.js → chunk-HZVIYZYN.js} +2 -2
  40. package/dist/{chunk-Y3TMFC6I.js → chunk-K3BTOW7N.js} +3 -3
  41. package/dist/{chunk-Z3CCEP6F.js → chunk-K47C6M2C.js} +5 -5
  42. package/dist/{chunk-N5RGXWLQ.js → chunk-MQ24KOOR.js} +2 -2
  43. package/dist/{chunk-3MNBW7R7.js → chunk-NRQJBK36.js} +2 -2
  44. package/dist/{chunk-MON3LMO7.js → chunk-NRST7W5Q.js} +5 -5
  45. package/dist/{chunk-3R2UZV3U.js → chunk-OOFBE62K.js} +2 -2
  46. package/dist/{chunk-MVQN73GT.js → chunk-OQMR2SDZ.js} +2 -2
  47. package/dist/{chunk-UGHUNQ74.js → chunk-RSKUUEBA.js} +73 -1
  48. package/dist/chunk-RSKUUEBA.js.map +1 -0
  49. package/dist/{chunk-QDV6VAD4.js → chunk-S5W37FPX.js} +2 -2
  50. package/dist/{chunk-57QXN2CS.js → chunk-SACS6KE6.js} +2 -2
  51. package/dist/{chunk-UELS6WWF.js → chunk-UE57H4MA.js} +2 -2
  52. package/dist/{chunk-2RHI3FGV.js → chunk-VUTPRX7K.js} +20 -14
  53. package/dist/{chunk-2RHI3FGV.js.map → chunk-VUTPRX7K.js.map} +1 -1
  54. package/dist/{chunk-AZ4RI3QD.js → chunk-YJOWWRRS.js} +450 -48
  55. package/dist/chunk-YJOWWRRS.js.map +1 -0
  56. package/dist/{chunk-P2D2MM47.js → chunk-ZZSXUZF3.js} +2 -2
  57. package/dist/{cli-EZv6YE6_.d.ts → cli-B_6EMiQc.d.ts} +3 -3
  58. package/dist/cli.d.ts +7 -5
  59. package/dist/cli.js +18 -17
  60. package/dist/compounding/engine.d.ts +1 -1
  61. package/dist/compounding/engine.js +2 -2
  62. package/dist/compounding/preference-consolidator.d.ts +1 -1
  63. package/dist/compression-optimizer.d.ts +1 -1
  64. package/dist/config.d.ts +1 -1
  65. package/dist/config.js +1 -1
  66. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  67. package/dist/connectors/codex-materialize-runner.js +2 -2
  68. package/dist/connectors/codex-materialize.d.ts +1 -1
  69. package/dist/connectors/index.d.ts +1 -1
  70. package/dist/connectors/index.js +2 -2
  71. package/dist/consolidation-provenance-check.d.ts +1 -1
  72. package/dist/consolidation-undo.d.ts +1 -1
  73. package/dist/contradiction/index.d.ts +1 -1
  74. package/dist/conversation-index/backend.d.ts +1 -1
  75. package/dist/conversation-index/chunker.d.ts +1 -1
  76. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  77. package/dist/conversation-index/indexer.d.ts +1 -1
  78. package/dist/conversation-index/search.d.ts +1 -1
  79. package/dist/day-summary.d.ts +1 -1
  80. package/dist/delinearize.d.ts +1 -1
  81. package/dist/direct-answer-wiring.d.ts +1 -1
  82. package/dist/direct-answer.d.ts +1 -1
  83. package/dist/embedding-fallback.d.ts +1 -1
  84. package/dist/enrichment/index.d.ts +1 -1
  85. package/dist/entity-retrieval.d.ts +1 -1
  86. package/dist/entity-retrieval.js +2 -2
  87. package/dist/entity-schema.d.ts +1 -1
  88. package/dist/explicit-capture.d.ts +6 -4
  89. package/dist/extraction-judge-telemetry.d.ts +1 -1
  90. package/dist/extraction-judge-training.d.ts +1 -1
  91. package/dist/extraction-judge.d.ts +1 -1
  92. package/dist/extraction-judge.js +3 -3
  93. package/dist/extraction.d.ts +1 -1
  94. package/dist/extraction.js +3 -3
  95. package/dist/fallback-llm.d.ts +1 -1
  96. package/dist/fallback-llm.js +2 -2
  97. package/dist/identity-continuity.d.ts +1 -1
  98. package/dist/importance.d.ts +1 -1
  99. package/dist/index.d.ts +9 -9
  100. package/dist/index.js +29 -27
  101. package/dist/index.js.map +1 -1
  102. package/dist/intent.d.ts +1 -1
  103. package/dist/lcm/engine.d.ts +1 -1
  104. package/dist/lcm/index.d.ts +1 -1
  105. package/dist/lcm/tools.d.ts +1 -1
  106. package/dist/lifecycle.d.ts +1 -1
  107. package/dist/live-connectors-runner.d.ts +1 -1
  108. package/dist/local-llm.d.ts +1 -1
  109. package/dist/maintenance/memory-governance.d.ts +1 -1
  110. package/dist/maintenance/memory-governance.js +2 -2
  111. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
  112. package/dist/maintenance/rebuild-memory-projection.js +3 -3
  113. package/dist/mcp-memory-inspector-app.d.ts +6 -4
  114. package/dist/memory-action-policy.d.ts +1 -1
  115. package/dist/memory-cache.d.ts +1 -1
  116. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  117. package/dist/memory-projection-store.d.ts +1 -1
  118. package/dist/memory-provenance.d.ts +1 -1
  119. package/dist/memory-worth-outcomes.d.ts +1 -1
  120. package/dist/models-json.d.ts +1 -1
  121. package/dist/namespaces/migrate.d.ts +1 -1
  122. package/dist/namespaces/migrate.js +3 -3
  123. package/dist/namespaces/principal.d.ts +1 -1
  124. package/dist/namespaces/search.d.ts +1 -1
  125. package/dist/namespaces/storage.d.ts +1 -1
  126. package/dist/namespaces/storage.js +2 -2
  127. package/dist/native-knowledge.d.ts +1 -1
  128. package/dist/operator-toolkit.d.ts +1 -1
  129. package/dist/operator-toolkit.js +6 -6
  130. package/dist/{orchestrator-CEycaY3M.d.ts → orchestrator-Dlw3ae4B.d.ts} +111 -10
  131. package/dist/orchestrator.d.ts +5 -3
  132. package/dist/orchestrator.js +15 -14
  133. package/dist/patterns-cli.d.ts +1 -1
  134. package/dist/policy-runtime.d.ts +1 -1
  135. package/dist/qmd-recall-cache.d.ts +1 -1
  136. package/dist/qmd.d.ts +1 -1
  137. package/dist/recall-disclosure-escalation.d.ts +1 -1
  138. package/dist/recall-explain-renderer.d.ts +1 -1
  139. package/dist/recall-planner-llm.d.ts +1 -1
  140. package/dist/recall-planner-llm.js +2 -2
  141. package/dist/recall-state.d.ts +1 -1
  142. package/dist/recall-tag-filter.d.ts +1 -1
  143. package/dist/recall-xray-cli.d.ts +1 -1
  144. package/dist/recall-xray-renderer.d.ts +1 -1
  145. package/dist/recall-xray.d.ts +1 -1
  146. package/dist/resolve-auth-token.d.ts +1 -1
  147. package/dist/resume-bundles.js +2 -2
  148. package/dist/retrieval-agents.d.ts +1 -1
  149. package/dist/retrieval-tiers.d.ts +1 -1
  150. package/dist/routing/engine.d.ts +1 -1
  151. package/dist/routing/store.d.ts +1 -1
  152. package/dist/schemas.d.ts +24 -24
  153. package/dist/search/embed-helper.d.ts +1 -1
  154. package/dist/search/factory.d.ts +1 -1
  155. package/dist/search/index.d.ts +1 -1
  156. package/dist/search/lancedb-backend.d.ts +1 -1
  157. package/dist/search/meilisearch-backend.d.ts +1 -1
  158. package/dist/search/noop-backend.d.ts +1 -1
  159. package/dist/search/orama-backend.d.ts +1 -1
  160. package/dist/search/port.d.ts +1 -1
  161. package/dist/search/remote-backend.d.ts +1 -1
  162. package/dist/{semantic-consolidation-FbhPeJjB.d.ts → semantic-consolidation-C4sefXEI.d.ts} +1 -1
  163. package/dist/semantic-consolidation.d.ts +2 -2
  164. package/dist/semantic-consolidation.js +3 -3
  165. package/dist/semantic-rule-promotion.js +2 -2
  166. package/dist/semantic-rule-verifier.d.ts +1 -1
  167. package/dist/semantic-rule-verifier.js +2 -2
  168. package/dist/session-observer-bands.d.ts +1 -1
  169. package/dist/session-observer-state.d.ts +1 -1
  170. package/dist/shared-context/manager.d.ts +1 -1
  171. package/dist/signal.d.ts +1 -1
  172. package/dist/storage.d.ts +38 -2
  173. package/dist/storage.js +5 -3
  174. package/dist/summarizer.d.ts +1 -1
  175. package/dist/summarizer.js +3 -3
  176. package/dist/summary-snapshot.d.ts +1 -1
  177. package/dist/temporal-supersession.d.ts +1 -1
  178. package/dist/temporal-validity.d.ts +1 -1
  179. package/dist/threading.d.ts +1 -1
  180. package/dist/tier-migration.d.ts +1 -1
  181. package/dist/tier-routing.d.ts +1 -1
  182. package/dist/topics.d.ts +1 -1
  183. package/dist/transcript.d.ts +1 -1
  184. package/dist/transfer/types.d.ts +12 -12
  185. package/dist/{types-D5VRAI04.d.ts → types-2vqxmO0j.d.ts} +39 -10
  186. package/dist/types.d.ts +1 -1
  187. package/dist/utility-runtime.d.ts +1 -1
  188. package/dist/verified-recall.js +2 -2
  189. package/package.json +1 -1
  190. package/src/access-service.ts +7 -0
  191. package/src/briefing.ts +67 -1
  192. package/src/index.ts +2 -0
  193. package/src/orchestrator.ts +42 -0
  194. package/src/storage.ts +100 -0
  195. package/src/wearables/cli.ts +6 -0
  196. package/src/wearables/config.test.ts +33 -4
  197. package/src/wearables/config.ts +39 -7
  198. package/src/wearables/memory-gen.test.ts +416 -1
  199. package/src/wearables/memory-gen.ts +381 -23
  200. package/src/wearables/pipeline.test.ts +309 -1
  201. package/src/wearables/pipeline.ts +131 -9
  202. package/src/wearables/service.test.ts +172 -0
  203. package/src/wearables/service.ts +84 -3
  204. package/src/wearables/storage-io.test.ts +81 -0
  205. package/src/wearables/trust.test.ts +123 -0
  206. package/src/wearables/trust.ts +168 -0
  207. package/src/wearables/types.ts +37 -8
  208. package/dist/chunk-AZ4RI3QD.js.map +0 -1
  209. package/dist/chunk-F3FY3D3S.js.map +0 -1
  210. package/dist/chunk-STDAAGH7.js.map +0 -1
  211. package/dist/chunk-UGHUNQ74.js.map +0 -1
  212. /package/dist/{chunk-GE7Q7KXP.js.map → chunk-2VJ7AJFX.js.map} +0 -0
  213. /package/dist/{chunk-KVFYTRMV.js.map → chunk-4QEUKASL.js.map} +0 -0
  214. /package/dist/{chunk-KB4MFBF5.js.map → chunk-5S6IREG3.js.map} +0 -0
  215. /package/dist/{chunk-LQYTQCXM.js.map → chunk-6LBQL5US.js.map} +0 -0
  216. /package/dist/{chunk-KGIGRNR6.js.map → chunk-723OMPUI.js.map} +0 -0
  217. /package/dist/{chunk-TZDSNIRO.js.map → chunk-ADOD7PJC.js.map} +0 -0
  218. /package/dist/{chunk-SHV5Y2WU.js.map → chunk-BL33LBTN.js.map} +0 -0
  219. /package/dist/{chunk-532VCWYW.js.map → chunk-BWK5EEKS.js.map} +0 -0
  220. /package/dist/{chunk-3VONWEQB.js.map → chunk-HZVIYZYN.js.map} +0 -0
  221. /package/dist/{chunk-Y3TMFC6I.js.map → chunk-K3BTOW7N.js.map} +0 -0
  222. /package/dist/{chunk-Z3CCEP6F.js.map → chunk-K47C6M2C.js.map} +0 -0
  223. /package/dist/{chunk-N5RGXWLQ.js.map → chunk-MQ24KOOR.js.map} +0 -0
  224. /package/dist/{chunk-3MNBW7R7.js.map → chunk-NRQJBK36.js.map} +0 -0
  225. /package/dist/{chunk-MON3LMO7.js.map → chunk-NRST7W5Q.js.map} +0 -0
  226. /package/dist/{chunk-3R2UZV3U.js.map → chunk-OOFBE62K.js.map} +0 -0
  227. /package/dist/{chunk-MVQN73GT.js.map → chunk-OQMR2SDZ.js.map} +0 -0
  228. /package/dist/{chunk-QDV6VAD4.js.map → chunk-S5W37FPX.js.map} +0 -0
  229. /package/dist/{chunk-57QXN2CS.js.map → chunk-SACS6KE6.js.map} +0 -0
  230. /package/dist/{chunk-UELS6WWF.js.map → chunk-UE57H4MA.js.map} +0 -0
  231. /package/dist/{chunk-P2D2MM47.js.map → chunk-ZZSXUZF3.js.map} +0 -0
@@ -34,6 +34,10 @@ function config(overrides: Partial<WearablesConfig> = {}): WearablesConfig {
34
34
  ...defaultWearablesConfig(),
35
35
  enabled: true,
36
36
  timezone: "UTC",
37
+ // Pinned off so per-stage tests stay focused; defaults are covered
38
+ // in config.test.ts and the dedicated digest test below.
39
+ digestEnabled: false,
40
+ offTheRecordEnabled: false,
37
41
  ...overrides,
38
42
  };
39
43
  }
@@ -119,7 +123,7 @@ function makeDeps(memoryDir: string): {
119
123
  files.set(`${sourceId}/${date}`, serialized);
120
124
  written.push({ source: sourceId, date, serialized });
121
125
  },
122
- async afterTranscriptsWritten() {
126
+ async afterWrites() {
123
127
  reindexes.count += 1;
124
128
  },
125
129
  memoryGen: {
@@ -576,6 +580,310 @@ test("page-capped days carry a visible partial marker and keep warning", async (
576
580
  }
577
581
  });
578
582
 
583
+ test("a judge outage does not re-run the memory pass forever", async () => {
584
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
585
+ try {
586
+ const byDate = {
587
+ "2026-06-11": [
588
+ makeConversation("c1", "2026-06-11", [
589
+ { speaker: "user", isWearer: true, text: "The vendor contract was renewed for another year today." },
590
+ { speaker: "Speaker 2", text: "Renewal confirmed, the paperwork went through this afternoon." },
591
+ ]),
592
+ ],
593
+ };
594
+ const { deps, memoryWrites } = makeDeps(memoryDir);
595
+ assert.ok(deps.memoryGen);
596
+ let extractCalls = 0;
597
+ const baseExtract = deps.memoryGen.extract;
598
+ deps.memoryGen.extract = async (turns) => {
599
+ extractCalls += 1;
600
+ return baseExtract(turns);
601
+ };
602
+ deps.memoryGen.judgeFacts = async () => {
603
+ throw new Error("judge backend down");
604
+ };
605
+
606
+ const first = await syncWearableSource(
607
+ fakeConnector(byDate),
608
+ settings({ memoryMode: "smart" }),
609
+ config(),
610
+ { days: 1 },
611
+ deps,
612
+ );
613
+ assert.ok(first.warnings.some((warning) => warning.includes("judge unavailable")));
614
+ assert.equal(first.memoriesCreated, 1, "degraded pass still writes");
615
+ const callsAfterFirst = extractCalls;
616
+
617
+ // Unchanged day, judge still down: the degraded-but-complete pass
618
+ // must have recorded completion — no re-extraction.
619
+ const second = await syncWearableSource(
620
+ fakeConnector(byDate),
621
+ settings({ memoryMode: "smart" }),
622
+ config(),
623
+ { days: 1 },
624
+ deps,
625
+ );
626
+ assert.equal(extractCalls, callsAfterFirst, "no repeat extraction for an unchanged day");
627
+ assert.equal(second.memoriesCreated, 0);
628
+ assert.equal(memoryWrites.length, 1);
629
+ } finally {
630
+ rmSync(memoryDir, { recursive: true, force: true });
631
+ }
632
+ });
633
+
634
+ test("smart mode: another device's stored day transcript corroborates borderline facts", async () => {
635
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
636
+ try {
637
+ const byDate = {
638
+ "2026-06-11": [
639
+ makeConversation("c1", "2026-06-11", [
640
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call." },
641
+ { speaker: "Speaker 2", text: "September twelfth works for everyone on the vendor side too." },
642
+ ]),
643
+ ],
644
+ };
645
+ const { deps, memoryWrites } = makeDeps(memoryDir);
646
+ assert.ok(deps.memoryGen);
647
+ // Borderline extraction: 0.75 * 0.8 = 0.6 — review band unless corroborated.
648
+ deps.memoryGen.extract = async () => ({
649
+ facts: [
650
+ {
651
+ category: "fact",
652
+ content: "The launch moved to September twelfth after the vendor call.",
653
+ confidence: 0.75,
654
+ tags: [],
655
+ },
656
+ ],
657
+ profileUpdates: [],
658
+ entities: [],
659
+ questions: [],
660
+ });
661
+ // A second device already stored a transcript covering the same day.
662
+ deps.readOtherSourceDayBodies = async (date, excludeSource) => {
663
+ assert.equal(date, "2026-06-11");
664
+ assert.equal(excludeSource, "testsource");
665
+ return new Map([
666
+ ["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."],
667
+ ]);
668
+ };
669
+ deps.listSupportMemories = async () => [];
670
+
671
+ const summary = await syncWearableSource(
672
+ fakeConnector(byDate),
673
+ settings({ memoryMode: "smart" }),
674
+ config(),
675
+ { days: 1 },
676
+ deps,
677
+ );
678
+ assert.equal(summary.memoriesCreated, 1);
679
+ assert.equal(memoryWrites[0].options.status, "active", "corroboration lifts review -> active");
680
+ const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
681
+ assert.equal(attrs.corroboratedBySources, "bee");
682
+ assert.equal(attrs.trustDecision, "auto-approved");
683
+ } finally {
684
+ rmSync(memoryDir, { recursive: true, force: true });
685
+ }
686
+ });
687
+
688
+ test("smart native import never uses day-scoped cross-source corroboration", async () => {
689
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
690
+ try {
691
+ const { deps, memoryWrites } = makeDeps(memoryDir);
692
+ assert.ok(deps.memoryGen);
693
+ let dayBodiesRequested = 0;
694
+ deps.readOtherSourceDayBodies = async () => {
695
+ dayBodiesRequested += 1;
696
+ return new Map([["bee", "the launch moves to september twelfth after that vendor call"]]);
697
+ };
698
+ deps.listSupportMemories = async () => [
699
+ { id: "fact-9", content: "User volunteers at the food bank every month with the team." },
700
+ ];
701
+ const summary = await syncWearableSource(
702
+ fakeConnector({}, [
703
+ // No day attached — must not be scored against any day's tokens,
704
+ // but corpus support still applies: 0.7*(0.8*0.9)+0.10 = 0.604 -> review.
705
+ { id: "nat-1", content: "User volunteers at the food bank every month with the team." },
706
+ ]),
707
+ settings({ memoryMode: "smart", importNativeMemories: "smart" }),
708
+ config(),
709
+ { days: 1 },
710
+ deps,
711
+ );
712
+ assert.equal(dayBodiesRequested, 0, "native import must not read day bodies");
713
+ assert.equal(summary.nativeMemoriesImported, 1);
714
+ const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
715
+ assert.equal(attrs.corroboratedBySources, undefined, "no cross-source evidence without a day");
716
+ assert.equal(attrs.supportingMemoryId, "fact-9", "corpus support still applies");
717
+ } finally {
718
+ rmSync(memoryDir, { recursive: true, force: true });
719
+ }
720
+ });
721
+
722
+ test("facts written earlier in a multi-day backfill support later days in the same run", async () => {
723
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
724
+ try {
725
+ const byDate = {
726
+ "2026-06-10": [
727
+ makeConversation("c1", "2026-06-10", [
728
+ { speaker: "user", isWearer: true, text: "The launch moved to September twelfth after the vendor call." },
729
+ { speaker: "Speaker 2", text: "September twelfth confirmed with the vendor on the call." },
730
+ ]),
731
+ ],
732
+ "2026-06-11": [
733
+ makeConversation("c2", "2026-06-11", [
734
+ { speaker: "user", isWearer: true, text: "Reminder that the launch moved to September twelfth after the vendor call." },
735
+ { speaker: "Speaker 2", text: "Yes, the September date is locked in now." },
736
+ ]),
737
+ ],
738
+ };
739
+ const { deps, memoryWrites } = makeDeps(memoryDir);
740
+ assert.ok(deps.memoryGen);
741
+ // Borderline confidence so day 2 only crosses the auto threshold
742
+ // with the +0.10 corpus-support boost from day 1's write.
743
+ let call = 0;
744
+ deps.memoryGen.extract = async () => {
745
+ call += 1;
746
+ return {
747
+ facts: [
748
+ {
749
+ category: "fact",
750
+ content:
751
+ call === 1
752
+ ? "The launch moved to September twelfth after the vendor call."
753
+ : "Launch moved to September twelfth after the vendor call.",
754
+ confidence: 0.75,
755
+ tags: [],
756
+ },
757
+ ],
758
+ profileUpdates: [],
759
+ entities: [],
760
+ questions: [],
761
+ };
762
+ };
763
+ // listSupportMemories reflects what this run has written so far —
764
+ // mirroring storage, whose readAllMemories cache invalidates on
765
+ // every write.
766
+ deps.listSupportMemories = async () =>
767
+ memoryWrites.map((write, index) => ({ id: `mem-${index + 1}`, content: write.content }));
768
+
769
+ const summary = await syncWearableSource(
770
+ fakeConnector(byDate),
771
+ settings({ memoryMode: "smart" }),
772
+ config(),
773
+ { days: 2 },
774
+ deps,
775
+ );
776
+ assert.equal(summary.memoriesCreated, 2);
777
+ assert.equal(memoryWrites[0].options.status, "pending_review", "day 1 borderline stays in review");
778
+ assert.equal(memoryWrites[1].options.status, "active", "day 2 lifted by day 1's write");
779
+ const attrs = memoryWrites[1].options.structuredAttributes as Record<string, string>;
780
+ assert.equal(attrs.supportingMemoryId, "mem-1");
781
+ } finally {
782
+ rmSync(memoryDir, { recursive: true, force: true });
783
+ }
784
+ });
785
+
786
+ test("a second device's day write invalidates the first device's memory-pass completion", async () => {
787
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
788
+ try {
789
+ const day = "2026-06-11";
790
+ const conversationFor = (source: string) =>
791
+ makeConversation(`${source}-c1`, day, [
792
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
793
+ { speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
794
+ ]);
795
+ const { deps } = makeDeps(memoryDir);
796
+
797
+ // Device A syncs first and completes its memory pass for the day.
798
+ const connectorA = { ...fakeConnector({ [day]: [conversationFor("a")] }), id: "sourcea" };
799
+ await syncWearableSource(connectorA, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
800
+ let state = await loadSyncState(memoryDir);
801
+ assert.ok(state.sources.sourcea.memoryDayHashes?.[day], "A's pass recorded");
802
+
803
+ // Device B then writes a transcript for the same day: A's
804
+ // completion record for that day must be invalidated so A's next
805
+ // sync re-scores with B's evidence available.
806
+ const connectorB = { ...fakeConnector({ [day]: [conversationFor("b")] }), id: "sourceb" };
807
+ await syncWearableSource(connectorB, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
808
+ state = await loadSyncState(memoryDir);
809
+ assert.equal(
810
+ state.sources.sourcea.memoryDayHashes?.[day],
811
+ undefined,
812
+ "A's completion cleared by B's new same-day evidence",
813
+ );
814
+ assert.ok(state.sources.sourceb.memoryDayHashes?.[day], "B's own pass recorded");
815
+ } finally {
816
+ rmSync(memoryDir, { recursive: true, force: true });
817
+ }
818
+ });
819
+
820
+ test("a promotion-only re-pass still fires the reindex hook", async () => {
821
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
822
+ try {
823
+ const day = "2026-06-11";
824
+ const byDate = {
825
+ [day]: [
826
+ makeConversation("c1", day, [
827
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
828
+ { speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
829
+ ]),
830
+ ],
831
+ };
832
+ const { deps, reindexes, memoryWrites } = makeDeps(memoryDir);
833
+ assert.ok(deps.memoryGen);
834
+ const borderline = "The launch moved to September twelfth after the vendor call.";
835
+ deps.memoryGen.extract = async () => ({
836
+ facts: [{ category: "fact", content: borderline, confidence: 0.75, tags: [] }],
837
+ profileUpdates: [],
838
+ entities: [],
839
+ questions: [],
840
+ });
841
+
842
+ // Sync 1: borderline fact lands in review. Reindex fires (a memory
843
+ // was created).
844
+ await syncWearableSource(fakeConnector(byDate), settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
845
+ assert.equal(memoryWrites[0].options.status, "pending_review");
846
+ const reindexesAfterFirst = reindexes.count;
847
+
848
+ // Another source's same-day transcript arrives, then sync 1's
849
+ // source re-passes: unchanged transcript, duplicate fact — but now
850
+ // corroborated, so it PROMOTES. The reindex hook must still fire.
851
+ const written: string[] = [];
852
+ deps.memoryGen.writer.findWearableMemoryByContent = async (content) =>
853
+ content.trim() === borderline ? { id: "mem-1", status: "pending_review" } : null;
854
+ deps.memoryGen.writer.promoteWearableMemory = async (id) => {
855
+ written.push(id);
856
+ return true;
857
+ };
858
+ deps.memoryGen.writer.hasFactContentHash = async (content) =>
859
+ content.trim() === borderline;
860
+ deps.readOtherSourceDayBodies = async () =>
861
+ new Map([["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."]]);
862
+ // Clear the completion record the way a sibling-source write would.
863
+ const { loadSyncState: load, saveSyncState: save } = await import("./sync-state.js");
864
+ const state = await load(memoryDir);
865
+ delete state.sources.testsource.memoryDayHashes?.[day];
866
+ await save(memoryDir, state);
867
+
868
+ const second = await syncWearableSource(
869
+ fakeConnector(byDate),
870
+ settings({ memoryMode: "smart" }),
871
+ config(),
872
+ { days: 1 },
873
+ deps,
874
+ );
875
+ assert.equal(second.transcriptsWritten.length, 0, "no transcript write");
876
+ assert.equal(second.memoriesPromoted, 1, "promotion happened");
877
+ assert.deepEqual(written, ["mem-1"]);
878
+ assert.ok(
879
+ reindexes.count > reindexesAfterFirst,
880
+ "reindex fired for the promotion-only run",
881
+ );
882
+ } finally {
883
+ rmSync(memoryDir, { recursive: true, force: true });
884
+ }
885
+ });
886
+
579
887
  test("a transcript write failure prevents the sync watermark from advancing", async () => {
580
888
  const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
581
889
  try {
@@ -36,6 +36,7 @@ import {
36
36
  writeDailyDigestMemory,
37
37
  type WearableMemoryGenDeps,
38
38
  } from "./memory-gen.js";
39
+ import { tokenizeDayBody, type CorroborationContext } from "./trust.js";
39
40
  import { applyOffTheRecord, compileRedactionPatterns, redactText } from "./redaction.js";
40
41
  import { loadSpeakerRegistry } from "./speakers.js";
41
42
  import {
@@ -80,14 +81,36 @@ export interface WearableSyncDeps {
80
81
  date: string,
81
82
  serialized: string,
82
83
  ): Promise<void>;
83
- /** Optional hook fired once after any day files changed (reindex). */
84
- afterTranscriptsWritten?(): Promise<void>;
84
+ /**
85
+ * Optional hook fired once after the sync wrote ANYTHING the search
86
+ * index should see: day transcripts, created memories, in-place
87
+ * promotions, or native imports. (A cross-source invalidation can
88
+ * promote memories on a run with zero transcript writes — the index
89
+ * must still refresh.)
90
+ */
91
+ afterWrites?(): Promise<void>;
85
92
  /**
86
93
  * Memory-generation dependencies, or null when no extraction engine
87
94
  * is available in this context (transcripts still sync; memory
88
95
  * creation is skipped with a warning when the mode wanted it).
89
96
  */
90
97
  memoryGen: WearableMemoryGenDeps | null;
98
+ /**
99
+ * Same-day transcript bodies from OTHER sources, for cross-device
100
+ * corroboration in smart mode. Absent disables the boost.
101
+ */
102
+ readOtherSourceDayBodies?(
103
+ date: string,
104
+ excludeSource: string,
105
+ ): Promise<Map<string, string>>;
106
+ /**
107
+ * Existing memories usable as support evidence: status "active" or
108
+ * "pending_review" (explicit allow-list — a borderline fact observed
109
+ * again on a later day is repetition signal, and the +0.10 boost is
110
+ * how it earns promotion). Rejected/quarantined/superseded/archived/
111
+ * forgotten rows are never support evidence.
112
+ */
113
+ listSupportMemories?(): Promise<Array<{ id: string; content: string }>>;
91
114
  /** Clock injection for tests. */
92
115
  now?: () => Date;
93
116
  }
@@ -276,6 +299,8 @@ export async function syncWearableSource(
276
299
  correctionsApplied: 0,
277
300
  transcriptsWritten: [],
278
301
  memoriesCreated: 0,
302
+ memoriesPromoted: 0,
303
+ memoriesDemoted: 0,
279
304
  memoriesSkipped: 0,
280
305
  nativeMemoriesImported: 0,
281
306
  warnings: [],
@@ -380,6 +405,9 @@ export async function syncWearableSource(
380
405
 
381
406
  if (allElided) continue;
382
407
 
408
+ const needsSmartContext =
409
+ settings.memoryMode === "smart" || settings.importNativeMemories === "smart";
410
+
383
411
  // The memory pass runs when the day changed, when forced, or when
384
412
  // the last pass for this exact content didn't complete cleanly —
385
413
  // a sync that stored the transcript but failed mid-memory-write
@@ -406,18 +434,31 @@ export async function syncWearableSource(
406
434
  // re-runs the day.
407
435
  let passClean = false;
408
436
  try {
437
+ const corroboration = needsSmartContext
438
+ ? await buildCorroborationContext(connector.id, date, deps)
439
+ : undefined;
440
+ const dayMemoryGen: WearableMemoryGenDeps = {
441
+ ...deps.memoryGen,
442
+ ...(corroboration !== undefined ? { corroboration } : {}),
443
+ };
409
444
  const generated = await generateWearableMemories(
410
445
  connector.id,
411
446
  date,
412
447
  cleaned.conversations,
413
448
  settings,
414
449
  registry,
415
- deps.memoryGen,
450
+ dayMemoryGen,
416
451
  );
417
452
  summary.memoriesCreated += generated.created;
453
+ summary.memoriesPromoted += generated.promoted;
454
+ summary.memoriesDemoted += generated.demoted;
418
455
  summary.memoriesSkipped += generated.skipped;
419
456
  summary.warnings.push(...generated.warnings);
420
- passClean = generated.warnings.length === 0;
457
+ // Degraded-but-complete passes (e.g. judge unavailable) still
458
+ // record completion — only an aborted extraction should force
459
+ // the day to re-run on the next sync (Cursor review on PR
460
+ // #1462).
461
+ passClean = generated.completed;
421
462
  if (config.digestEnabled) {
422
463
  const wrote = await writeDailyDigestMemory(
423
464
  connector.id,
@@ -448,7 +489,7 @@ export async function syncWearableSource(
448
489
  }
449
490
 
450
491
  if (
451
- settings.importNativeMemories === "review" &&
492
+ settings.importNativeMemories !== "off" &&
452
493
  typeof connector.fetchNativeMemories === "function"
453
494
  ) {
454
495
  if (!deps.memoryGen) {
@@ -459,6 +500,26 @@ export async function syncWearableSource(
459
500
  const alreadyImported = new Set(
460
501
  previousState?.importedNativeMemoryIds ?? [],
461
502
  );
503
+ // Native memories carry no day, so same-day cross-device
504
+ // corroboration does not apply to them — scoring a provider fact
505
+ // against an arbitrary day's tokens would be wrong-day evidence
506
+ // (Cursor review on PR #1462). They keep only the day-independent
507
+ // existing-memory support boost.
508
+ const nativeCorroboration =
509
+ settings.importNativeMemories === "smart"
510
+ ? {
511
+ otherSourceDayTokens: new Map<string, Set<string>>(),
512
+ existingMemories: deps.listSupportMemories
513
+ ? await deps.listSupportMemories()
514
+ : [],
515
+ }
516
+ : undefined;
517
+ const nativeMemoryGen: WearableMemoryGenDeps = {
518
+ ...deps.memoryGen,
519
+ ...(nativeCorroboration !== undefined
520
+ ? { corroboration: nativeCorroboration }
521
+ : {}),
522
+ };
462
523
  let cursor: string | null | undefined = undefined;
463
524
  for (let page = 0; page < MAX_NATIVE_PAGES; page++) {
464
525
  const result = await connector.fetchNativeMemories({
@@ -469,8 +530,10 @@ export async function syncWearableSource(
469
530
  connector.id,
470
531
  result.memories,
471
532
  alreadyImported,
472
- deps.memoryGen.writer,
533
+ settings,
534
+ nativeMemoryGen,
473
535
  );
536
+ summary.warnings.push(...imported.warnings);
474
537
  summary.nativeMemoriesImported += imported.imported;
475
538
  importedNativeIds.push(...imported.importedIds);
476
539
  for (const id of imported.importedIds) alreadyImported.add(id);
@@ -485,12 +548,18 @@ export async function syncWearableSource(
485
548
  }
486
549
  }
487
550
 
488
- if (summary.transcriptsWritten.length > 0 && deps.afterTranscriptsWritten) {
551
+ const wroteAnything =
552
+ summary.transcriptsWritten.length > 0 ||
553
+ summary.memoriesCreated > 0 ||
554
+ summary.memoriesPromoted > 0 ||
555
+ summary.memoriesDemoted > 0 ||
556
+ summary.nativeMemoriesImported > 0;
557
+ if (wroteAnything && deps.afterWrites) {
489
558
  try {
490
- await deps.afterTranscriptsWritten();
559
+ await deps.afterWrites();
491
560
  } catch (err) {
492
561
  summary.warnings.push(
493
- `search reindex failed (transcripts are stored and will index on the next update): ${describeErrorForOperator(err)}`,
562
+ `search reindex failed (writes are stored and will index on the next update): ${describeErrorForOperator(err)}`,
494
563
  );
495
564
  }
496
565
  }
@@ -505,11 +574,64 @@ export async function syncWearableSource(
505
574
  clearMemoryDays: failedMemoryDays,
506
575
  importedNativeMemoryIds: importedNativeIds,
507
576
  });
577
+ // New same-day evidence invalidates OTHER sources' memory-pass
578
+ // completion for the days this source just (re)wrote: their next
579
+ // sync re-scores with this transcript available, and the promotion
580
+ // path upgrades earlier borderline writes in place (Cursor review on
581
+ // PR #1462).
582
+ if (summary.transcriptsWritten.length > 0) {
583
+ const cleared: typeof syncState.sources = {};
584
+ for (const [otherId, otherState] of Object.entries(syncState.sources)) {
585
+ if (otherId === connector.id || otherState.memoryDayHashes === undefined) {
586
+ cleared[otherId] = otherState;
587
+ continue;
588
+ }
589
+ const memoryDays = { ...otherState.memoryDayHashes };
590
+ let touched = false;
591
+ for (const date of summary.transcriptsWritten) {
592
+ if (date in memoryDays) {
593
+ delete memoryDays[date];
594
+ touched = true;
595
+ }
596
+ }
597
+ cleared[otherId] = touched
598
+ ? { ...otherState, memoryDayHashes: memoryDays }
599
+ : otherState;
600
+ }
601
+ syncState = { version: 1, sources: cleared };
602
+ }
508
603
  await saveSyncState(deps.memoryDir, syncState);
509
604
 
510
605
  return summary;
511
606
  }
512
607
 
608
+ /**
609
+ * Assemble smart-mode corroboration evidence: other sources' same-day
610
+ * transcript tokens + existing active memories. The memory list loads
611
+ * fresh per day (not per run) so facts written on earlier days of a
612
+ * multi-day backfill are visible as support evidence on later days —
613
+ * the underlying readAllMemories is cached in storage and invalidated
614
+ * by writes, so the per-day refresh is cheap (Cursor review on PR
615
+ * #1462).
616
+ */
617
+ async function buildCorroborationContext(
618
+ sourceId: string,
619
+ date: string,
620
+ deps: WearableSyncDeps,
621
+ ): Promise<CorroborationContext> {
622
+ const otherSourceDayTokens = new Map<string, Set<string>>();
623
+ if (deps.readOtherSourceDayBodies) {
624
+ const bodies = await deps.readOtherSourceDayBodies(date, sourceId);
625
+ for (const [otherSource, body] of bodies) {
626
+ otherSourceDayTokens.set(otherSource, tokenizeDayBody(body));
627
+ }
628
+ }
629
+ const existingMemories = deps.listSupportMemories
630
+ ? await deps.listSupportMemories()
631
+ : [];
632
+ return { otherSourceDayTokens, existingMemories };
633
+ }
634
+
513
635
  export function defaultTimezone(): string {
514
636
  try {
515
637
  return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";