@remnic/core 9.3.629 → 9.3.631

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 (221) hide show
  1. package/dist/access-cli.js +15 -13
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +5 -4
  4. package/dist/access-http.js +7 -6
  5. package/dist/access-mcp.d.ts +5 -4
  6. package/dist/access-mcp.js +6 -5
  7. package/dist/{access-service-BdThkfIE.d.ts → access-service-C9_EpVHd.d.ts} +2 -2
  8. package/dist/access-service.d.ts +5 -4
  9. package/dist/access-service.js +5 -4
  10. package/dist/action-confidence.d.ts +1 -1
  11. package/dist/active-memory-bridge.d.ts +1 -1
  12. package/dist/active-recall.d.ts +1 -1
  13. package/dist/active-recall.js +1 -1
  14. package/dist/auto-sync-RFADEHIQ.js +75 -0
  15. package/dist/auto-sync-RFADEHIQ.js.map +1 -0
  16. package/dist/behavior-learner.d.ts +1 -1
  17. package/dist/behavior-signals.d.ts +1 -1
  18. package/dist/bootstrap.d.ts +4 -3
  19. package/dist/briefing.d.ts +1 -1
  20. package/dist/briefing.js +3 -2
  21. package/dist/buffer-surprise-report.d.ts +1 -1
  22. package/dist/buffer.d.ts +1 -1
  23. package/dist/calibration.d.ts +1 -1
  24. package/dist/causal-behavior.d.ts +1 -1
  25. package/dist/causal-consolidation.d.ts +1 -1
  26. package/dist/causal-consolidation.js +4 -3
  27. package/dist/causal-consolidation.js.map +1 -1
  28. package/dist/{chunk-532VCWYW.js → chunk-242XFZ36.js} +2 -2
  29. package/dist/{chunk-XXO5TI3B.js → chunk-32U3N7H5.js} +3 -3
  30. package/dist/{chunk-KB4MFBF5.js → chunk-3RDYU3JS.js} +3 -3
  31. package/dist/{chunk-57QXN2CS.js → chunk-4S3N6HFG.js} +2 -2
  32. package/dist/{chunk-OLNNOHBC.js → chunk-5PT5I6JQ.js} +20 -14
  33. package/dist/{chunk-OLNNOHBC.js.map → chunk-5PT5I6JQ.js.map} +1 -1
  34. package/dist/{chunk-GE7Q7KXP.js → chunk-7A2QKUUA.js} +2 -2
  35. package/dist/{chunk-KKTXCFD7.js → chunk-7H5WCPBS.js} +95 -11
  36. package/dist/{chunk-KKTXCFD7.js.map → chunk-7H5WCPBS.js.map} +1 -1
  37. package/dist/{chunk-3MNBW7R7.js → chunk-C4KKM62E.js} +2 -2
  38. package/dist/{chunk-NKCW223V.js → chunk-CMN5AWAZ.js} +2 -2
  39. package/dist/{chunk-JXHMAQYT.js → chunk-DOBJH4I6.js} +4 -4
  40. package/dist/{chunk-TZDSNIRO.js → chunk-IFVFQRZ2.js} +5 -5
  41. package/dist/{chunk-LQYTQCXM.js → chunk-JCLECECB.js} +2 -2
  42. package/dist/chunk-KVDUDYEN.js +1164 -0
  43. package/dist/chunk-KVDUDYEN.js.map +1 -0
  44. package/dist/{chunk-QDV6VAD4.js → chunk-LEG7XWS2.js} +2 -2
  45. package/dist/chunk-M7XQSUBB.js +280 -0
  46. package/dist/chunk-M7XQSUBB.js.map +1 -0
  47. package/dist/{chunk-N5RGXWLQ.js → chunk-PUEAEQSN.js} +2 -2
  48. package/dist/{chunk-UGHUNQ74.js → chunk-QYGIQ5NM.js} +212 -417
  49. package/dist/chunk-QYGIQ5NM.js.map +1 -0
  50. package/dist/{chunk-JKCDQBDW.js → chunk-UXFOGILU.js} +2 -2
  51. package/dist/{chunk-MVQN73GT.js → chunk-VTR3MNYF.js} +2 -2
  52. package/dist/{chunk-KVFYTRMV.js → chunk-W25I7G6U.js} +2 -2
  53. package/dist/{chunk-3GLCUPXP.js → chunk-WLZBVYC6.js} +192 -889
  54. package/dist/chunk-WLZBVYC6.js.map +1 -0
  55. package/dist/{chunk-3R2UZV3U.js → chunk-X7EJF46S.js} +2 -2
  56. package/dist/{chunk-54KDA6UK.js → chunk-XG4NAWAV.js} +3 -3
  57. package/dist/{chunk-P2D2MM47.js → chunk-YROCXMCK.js} +2 -2
  58. package/dist/{cli-DAsHklrf.d.ts → cli-CuVEQWKr.d.ts} +3 -3
  59. package/dist/cli.d.ts +6 -5
  60. package/dist/cli.js +18 -17
  61. package/dist/compounding/engine.d.ts +1 -1
  62. package/dist/compounding/engine.js +3 -2
  63. package/dist/compounding/preference-consolidator.d.ts +1 -1
  64. package/dist/compression-optimizer.d.ts +1 -1
  65. package/dist/config.d.ts +1 -1
  66. package/dist/config.js +1 -1
  67. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  68. package/dist/connectors/codex-materialize-runner.js +3 -2
  69. package/dist/connectors/codex-materialize.d.ts +1 -1
  70. package/dist/connectors/index.d.ts +1 -1
  71. package/dist/connectors/index.js +3 -2
  72. package/dist/consolidation-provenance-check.d.ts +1 -1
  73. package/dist/consolidation-undo.d.ts +1 -1
  74. package/dist/contradiction/index.d.ts +1 -1
  75. package/dist/contradiction/index.js +4 -4
  76. package/dist/conversation-index/backend.d.ts +1 -1
  77. package/dist/conversation-index/chunker.d.ts +1 -1
  78. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  79. package/dist/conversation-index/indexer.d.ts +1 -1
  80. package/dist/conversation-index/search.d.ts +1 -1
  81. package/dist/day-summary.d.ts +1 -1
  82. package/dist/delinearize.d.ts +1 -1
  83. package/dist/direct-answer-wiring.d.ts +1 -1
  84. package/dist/direct-answer.d.ts +1 -1
  85. package/dist/embedding-fallback.d.ts +1 -1
  86. package/dist/enrichment/index.d.ts +1 -1
  87. package/dist/entity-retrieval.d.ts +1 -1
  88. package/dist/entity-retrieval.js +3 -2
  89. package/dist/entity-schema.d.ts +1 -1
  90. package/dist/explicit-capture.d.ts +4 -3
  91. package/dist/extraction-judge-telemetry.d.ts +1 -1
  92. package/dist/extraction-judge-training.d.ts +1 -1
  93. package/dist/extraction-judge.d.ts +1 -1
  94. package/dist/extraction.d.ts +1 -1
  95. package/dist/fallback-llm.d.ts +1 -1
  96. package/dist/identity-continuity.d.ts +1 -1
  97. package/dist/importance.d.ts +1 -1
  98. package/dist/index.d.ts +8 -8
  99. package/dist/index.js +49 -45
  100. package/dist/index.js.map +1 -1
  101. package/dist/intent.d.ts +1 -1
  102. package/dist/lcm/engine.d.ts +1 -1
  103. package/dist/lcm/index.d.ts +1 -1
  104. package/dist/lcm/tools.d.ts +1 -1
  105. package/dist/lifecycle.d.ts +1 -1
  106. package/dist/live-connectors-runner.d.ts +1 -1
  107. package/dist/local-llm.d.ts +1 -1
  108. package/dist/maintenance/memory-governance.d.ts +1 -1
  109. package/dist/maintenance/memory-governance.js +3 -2
  110. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
  111. package/dist/maintenance/rebuild-memory-projection.js +4 -3
  112. package/dist/mcp-memory-inspector-app.d.ts +5 -4
  113. package/dist/memory-action-policy.d.ts +1 -1
  114. package/dist/memory-cache.d.ts +1 -1
  115. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  116. package/dist/memory-projection-store.d.ts +1 -1
  117. package/dist/memory-provenance.d.ts +1 -1
  118. package/dist/memory-worth-outcomes.d.ts +1 -1
  119. package/dist/models-json.d.ts +1 -1
  120. package/dist/namespaces/migrate.d.ts +1 -1
  121. package/dist/namespaces/migrate.js +4 -3
  122. package/dist/namespaces/principal.d.ts +1 -1
  123. package/dist/namespaces/search.d.ts +1 -1
  124. package/dist/namespaces/storage.d.ts +1 -1
  125. package/dist/namespaces/storage.js +3 -2
  126. package/dist/native-knowledge.d.ts +1 -1
  127. package/dist/operator-toolkit.d.ts +1 -1
  128. package/dist/operator-toolkit.js +7 -6
  129. package/dist/{orchestrator-BexeSJ2j.d.ts → orchestrator-CoqytbK_.d.ts} +102 -10
  130. package/dist/orchestrator.d.ts +4 -3
  131. package/dist/orchestrator.js +12 -10
  132. package/dist/patterns-cli.d.ts +1 -1
  133. package/dist/policy-runtime.d.ts +1 -1
  134. package/dist/qmd-recall-cache.d.ts +1 -1
  135. package/dist/qmd.d.ts +1 -1
  136. package/dist/recall-disclosure-escalation.d.ts +1 -1
  137. package/dist/recall-explain-renderer.d.ts +1 -1
  138. package/dist/recall-planner-llm.d.ts +1 -1
  139. package/dist/recall-state.d.ts +1 -1
  140. package/dist/recall-tag-filter.d.ts +1 -1
  141. package/dist/recall-xray-cli.d.ts +1 -1
  142. package/dist/recall-xray-renderer.d.ts +1 -1
  143. package/dist/recall-xray.d.ts +1 -1
  144. package/dist/resolve-auth-token.d.ts +1 -1
  145. package/dist/resume-bundles.js +2 -2
  146. package/dist/retrieval-agents.d.ts +1 -1
  147. package/dist/retrieval-tiers.d.ts +1 -1
  148. package/dist/routing/engine.d.ts +1 -1
  149. package/dist/routing/store.d.ts +1 -1
  150. package/dist/search/embed-helper.d.ts +1 -1
  151. package/dist/search/factory.d.ts +1 -1
  152. package/dist/search/index.d.ts +1 -1
  153. package/dist/search/lancedb-backend.d.ts +1 -1
  154. package/dist/search/meilisearch-backend.d.ts +1 -1
  155. package/dist/search/noop-backend.d.ts +1 -1
  156. package/dist/search/orama-backend.d.ts +1 -1
  157. package/dist/search/port.d.ts +1 -1
  158. package/dist/search/remote-backend.d.ts +1 -1
  159. package/dist/{semantic-consolidation-PwkzNfdK.d.ts → semantic-consolidation-BPs6BURk.d.ts} +1 -1
  160. package/dist/semantic-consolidation.d.ts +2 -2
  161. package/dist/semantic-consolidation.js +4 -3
  162. package/dist/semantic-rule-promotion.js +3 -2
  163. package/dist/semantic-rule-verifier.d.ts +1 -1
  164. package/dist/semantic-rule-verifier.js +3 -2
  165. package/dist/session-observer-bands.d.ts +1 -1
  166. package/dist/session-observer-state.d.ts +1 -1
  167. package/dist/shared-context/manager.d.ts +1 -1
  168. package/dist/signal.d.ts +1 -1
  169. package/dist/storage.d.ts +38 -2
  170. package/dist/storage.js +6 -3
  171. package/dist/summarizer.d.ts +1 -1
  172. package/dist/summary-snapshot.d.ts +1 -1
  173. package/dist/temporal-supersession.d.ts +1 -1
  174. package/dist/temporal-validity.d.ts +1 -1
  175. package/dist/threading.d.ts +1 -1
  176. package/dist/tier-migration.d.ts +1 -1
  177. package/dist/tier-routing.d.ts +1 -1
  178. package/dist/topics.d.ts +1 -1
  179. package/dist/transcript.d.ts +1 -1
  180. package/dist/{types-BCF2wqKa.d.ts → types-CpMPD8xl.d.ts} +59 -11
  181. package/dist/types.d.ts +1 -1
  182. package/dist/utility-runtime.d.ts +1 -1
  183. package/dist/verified-recall.js +3 -2
  184. package/package.json +1 -1
  185. package/src/orchestrator.ts +74 -0
  186. package/src/storage.ts +100 -0
  187. package/src/wearables/auto-sync.test.ts +181 -0
  188. package/src/wearables/auto-sync.ts +129 -0
  189. package/src/wearables/cli.ts +6 -0
  190. package/src/wearables/config.test.ts +90 -11
  191. package/src/wearables/config.ts +113 -11
  192. package/src/wearables/memory-gen.test.ts +416 -1
  193. package/src/wearables/memory-gen.ts +381 -23
  194. package/src/wearables/pipeline.test.ts +396 -5
  195. package/src/wearables/pipeline.ts +174 -22
  196. package/src/wearables/service.test.ts +172 -0
  197. package/src/wearables/service.ts +84 -3
  198. package/src/wearables/storage-io.test.ts +81 -0
  199. package/src/wearables/trust.test.ts +123 -0
  200. package/src/wearables/trust.ts +168 -0
  201. package/src/wearables/types.ts +57 -9
  202. package/dist/chunk-3GLCUPXP.js.map +0 -1
  203. package/dist/chunk-UGHUNQ74.js.map +0 -1
  204. /package/dist/{chunk-532VCWYW.js.map → chunk-242XFZ36.js.map} +0 -0
  205. /package/dist/{chunk-XXO5TI3B.js.map → chunk-32U3N7H5.js.map} +0 -0
  206. /package/dist/{chunk-KB4MFBF5.js.map → chunk-3RDYU3JS.js.map} +0 -0
  207. /package/dist/{chunk-57QXN2CS.js.map → chunk-4S3N6HFG.js.map} +0 -0
  208. /package/dist/{chunk-GE7Q7KXP.js.map → chunk-7A2QKUUA.js.map} +0 -0
  209. /package/dist/{chunk-3MNBW7R7.js.map → chunk-C4KKM62E.js.map} +0 -0
  210. /package/dist/{chunk-NKCW223V.js.map → chunk-CMN5AWAZ.js.map} +0 -0
  211. /package/dist/{chunk-JXHMAQYT.js.map → chunk-DOBJH4I6.js.map} +0 -0
  212. /package/dist/{chunk-TZDSNIRO.js.map → chunk-IFVFQRZ2.js.map} +0 -0
  213. /package/dist/{chunk-LQYTQCXM.js.map → chunk-JCLECECB.js.map} +0 -0
  214. /package/dist/{chunk-QDV6VAD4.js.map → chunk-LEG7XWS2.js.map} +0 -0
  215. /package/dist/{chunk-N5RGXWLQ.js.map → chunk-PUEAEQSN.js.map} +0 -0
  216. /package/dist/{chunk-JKCDQBDW.js.map → chunk-UXFOGILU.js.map} +0 -0
  217. /package/dist/{chunk-MVQN73GT.js.map → chunk-VTR3MNYF.js.map} +0 -0
  218. /package/dist/{chunk-KVFYTRMV.js.map → chunk-W25I7G6U.js.map} +0 -0
  219. /package/dist/{chunk-3R2UZV3U.js.map → chunk-X7EJF46S.js.map} +0 -0
  220. /package/dist/{chunk-54KDA6UK.js.map → chunk-XG4NAWAV.js.map} +0 -0
  221. /package/dist/{chunk-P2D2MM47.js.map → chunk-YROCXMCK.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: {
@@ -540,14 +544,15 @@ test("zero provider conversations never clobber an existing transcript", async (
540
544
  }
541
545
  });
542
546
 
543
- test("page-capped days carry a visible partial marker and keep warning", async () => {
547
+ test("looping pagination cursors stop with a partial marker and keep warning", async () => {
544
548
  const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
545
549
  try {
546
550
  const conversation = makeConversation("c1", "2026-06-11", [
547
551
  { speaker: "user", isWearer: true, text: "First chunk of a very long recorded day of conversations." },
548
552
  { speaker: "Speaker 2", text: "Indeed, the recordings just keep going on and on today." },
549
553
  ]);
550
- // A connector that always reports another page (pathological).
554
+ // A connector whose cursor loops forever (pathological) — cycle
555
+ // detection must stop it; no page-count cap truncates real data.
551
556
  const endlessConnector = {
552
557
  id: "testsource",
553
558
  displayName: "Test Source",
@@ -563,14 +568,400 @@ test("page-capped days carry a visible partial marker and keep warning", async (
563
568
  };
564
569
  const { deps, written } = makeDeps(memoryDir);
565
570
  const first = await syncWearableSource(endlessConnector, settings(), config(), { days: 1 }, deps);
566
- assert.ok(first.warnings.some((warning) => warning.includes("stopped paginating")));
571
+ assert.ok(first.warnings.some((warning) => warning.includes("repeated cursor")));
567
572
  assert.equal(written.length, 1);
568
573
  assert.match(written[0].serialized, /pagination safety cap reached/);
569
574
 
570
575
  // Identical second sync: file unchanged (no rewrite), warning persists.
571
576
  const second = await syncWearableSource(endlessConnector, settings(), config(), { days: 1 }, deps);
572
577
  assert.equal(second.transcriptsWritten.length, 0);
573
- assert.ok(second.warnings.some((warning) => warning.includes("stopped paginating")));
578
+ assert.ok(second.warnings.some((warning) => warning.includes("repeated cursor")));
579
+ } finally {
580
+ rmSync(memoryDir, { recursive: true, force: true });
581
+ }
582
+ });
583
+
584
+ test("looping providers that re-serve rows never duplicate conversations", async () => {
585
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
586
+ try {
587
+ const conversation = makeConversation("c1", "2026-06-11", [
588
+ { speaker: "user", isWearer: true, text: "A conversation the provider keeps re-serving on every page." },
589
+ { speaker: "Speaker 2", text: "The looping pagination should still store this exactly once." },
590
+ ]);
591
+ // Pathological: every page returns the SAME row and loops the cursor.
592
+ const reServingConnector = {
593
+ id: "testsource",
594
+ displayName: "Test Source",
595
+ async verifyAuth() {
596
+ return { ok: true };
597
+ },
598
+ async fetchConversations() {
599
+ return { conversations: [conversation], nextCursor: "loop" };
600
+ },
601
+ };
602
+ const { deps, written } = makeDeps(memoryDir);
603
+ const summary = await syncWearableSource(
604
+ reServingConnector,
605
+ settings(),
606
+ config(),
607
+ { days: 1 },
608
+ deps,
609
+ );
610
+ assert.equal(summary.conversations, 1, "re-served rows collapse by conversation id");
611
+ assert.ok(summary.warnings.some((warning) => warning.includes("repeated cursor")));
612
+ assert.equal(written.length, 1);
613
+ const occurrences = written[0].serialized.split("conversation c1").length - 1;
614
+ assert.equal(occurrences, 1, "day file stores the conversation exactly once");
615
+ } finally {
616
+ rmSync(memoryDir, { recursive: true, force: true });
617
+ }
618
+ });
619
+
620
+ test("long provider days paginate fully — no page-count truncation", async () => {
621
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
622
+ try {
623
+ // 60 pages (beyond the old 50-page cap), each with one conversation.
624
+ const PAGES = 60;
625
+ const longDayConnector = {
626
+ id: "testsource",
627
+ displayName: "Test Source",
628
+ async verifyAuth() {
629
+ return { ok: true };
630
+ },
631
+ async fetchConversations(opts: { cursor?: string | null }) {
632
+ const page = opts.cursor ? Number(opts.cursor) : 0;
633
+ return {
634
+ conversations: [
635
+ makeConversation(`c${page + 1}`, "2026-06-11", [
636
+ { speaker: "user", isWearer: true, text: `Recorded conversation segment number ${page + 1} from the long day.` },
637
+ { speaker: "Speaker 2", text: "Acknowledged, that part of the day was captured as well." },
638
+ ]),
639
+ ],
640
+ nextCursor: page + 1 < PAGES ? String(page + 1) : null,
641
+ };
642
+ },
643
+ };
644
+ const { deps } = makeDeps(memoryDir);
645
+ const summary = await syncWearableSource(
646
+ longDayConnector,
647
+ settings(),
648
+ config(),
649
+ { days: 1 },
650
+ deps,
651
+ );
652
+ assert.equal(summary.conversations, PAGES, "every page's conversation synced");
653
+ assert.equal(
654
+ summary.warnings.filter(
655
+ (warning) =>
656
+ warning.includes("stopped paginating") || warning.includes("repeated cursor"),
657
+ ).length,
658
+ 0,
659
+ "a real long day is never truncated or warned",
660
+ );
661
+ } finally {
662
+ rmSync(memoryDir, { recursive: true, force: true });
663
+ }
664
+ });
665
+
666
+ test("a judge outage does not re-run the memory pass forever", async () => {
667
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
668
+ try {
669
+ const byDate = {
670
+ "2026-06-11": [
671
+ makeConversation("c1", "2026-06-11", [
672
+ { speaker: "user", isWearer: true, text: "The vendor contract was renewed for another year today." },
673
+ { speaker: "Speaker 2", text: "Renewal confirmed, the paperwork went through this afternoon." },
674
+ ]),
675
+ ],
676
+ };
677
+ const { deps, memoryWrites } = makeDeps(memoryDir);
678
+ assert.ok(deps.memoryGen);
679
+ let extractCalls = 0;
680
+ const baseExtract = deps.memoryGen.extract;
681
+ deps.memoryGen.extract = async (turns) => {
682
+ extractCalls += 1;
683
+ return baseExtract(turns);
684
+ };
685
+ deps.memoryGen.judgeFacts = async () => {
686
+ throw new Error("judge backend down");
687
+ };
688
+
689
+ const first = await syncWearableSource(
690
+ fakeConnector(byDate),
691
+ settings({ memoryMode: "smart" }),
692
+ config(),
693
+ { days: 1 },
694
+ deps,
695
+ );
696
+ assert.ok(first.warnings.some((warning) => warning.includes("judge unavailable")));
697
+ assert.equal(first.memoriesCreated, 1, "degraded pass still writes");
698
+ const callsAfterFirst = extractCalls;
699
+
700
+ // Unchanged day, judge still down: the degraded-but-complete pass
701
+ // must have recorded completion — no re-extraction.
702
+ const second = await syncWearableSource(
703
+ fakeConnector(byDate),
704
+ settings({ memoryMode: "smart" }),
705
+ config(),
706
+ { days: 1 },
707
+ deps,
708
+ );
709
+ assert.equal(extractCalls, callsAfterFirst, "no repeat extraction for an unchanged day");
710
+ assert.equal(second.memoriesCreated, 0);
711
+ assert.equal(memoryWrites.length, 1);
712
+ } finally {
713
+ rmSync(memoryDir, { recursive: true, force: true });
714
+ }
715
+ });
716
+
717
+ test("smart mode: another device's stored day transcript corroborates borderline facts", async () => {
718
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
719
+ try {
720
+ const byDate = {
721
+ "2026-06-11": [
722
+ makeConversation("c1", "2026-06-11", [
723
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call." },
724
+ { speaker: "Speaker 2", text: "September twelfth works for everyone on the vendor side too." },
725
+ ]),
726
+ ],
727
+ };
728
+ const { deps, memoryWrites } = makeDeps(memoryDir);
729
+ assert.ok(deps.memoryGen);
730
+ // Borderline extraction: 0.75 * 0.8 = 0.6 — review band unless corroborated.
731
+ deps.memoryGen.extract = async () => ({
732
+ facts: [
733
+ {
734
+ category: "fact",
735
+ content: "The launch moved to September twelfth after the vendor call.",
736
+ confidence: 0.75,
737
+ tags: [],
738
+ },
739
+ ],
740
+ profileUpdates: [],
741
+ entities: [],
742
+ questions: [],
743
+ });
744
+ // A second device already stored a transcript covering the same day.
745
+ deps.readOtherSourceDayBodies = async (date, excludeSource) => {
746
+ assert.equal(date, "2026-06-11");
747
+ assert.equal(excludeSource, "testsource");
748
+ return new Map([
749
+ ["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."],
750
+ ]);
751
+ };
752
+ deps.listSupportMemories = async () => [];
753
+
754
+ const summary = await syncWearableSource(
755
+ fakeConnector(byDate),
756
+ settings({ memoryMode: "smart" }),
757
+ config(),
758
+ { days: 1 },
759
+ deps,
760
+ );
761
+ assert.equal(summary.memoriesCreated, 1);
762
+ assert.equal(memoryWrites[0].options.status, "active", "corroboration lifts review -> active");
763
+ const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
764
+ assert.equal(attrs.corroboratedBySources, "bee");
765
+ assert.equal(attrs.trustDecision, "auto-approved");
766
+ } finally {
767
+ rmSync(memoryDir, { recursive: true, force: true });
768
+ }
769
+ });
770
+
771
+ test("smart native import never uses day-scoped cross-source corroboration", async () => {
772
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
773
+ try {
774
+ const { deps, memoryWrites } = makeDeps(memoryDir);
775
+ assert.ok(deps.memoryGen);
776
+ let dayBodiesRequested = 0;
777
+ deps.readOtherSourceDayBodies = async () => {
778
+ dayBodiesRequested += 1;
779
+ return new Map([["bee", "the launch moves to september twelfth after that vendor call"]]);
780
+ };
781
+ deps.listSupportMemories = async () => [
782
+ { id: "fact-9", content: "User volunteers at the food bank every month with the team." },
783
+ ];
784
+ const summary = await syncWearableSource(
785
+ fakeConnector({}, [
786
+ // No day attached — must not be scored against any day's tokens,
787
+ // but corpus support still applies: 0.7*(0.8*0.9)+0.10 = 0.604 -> review.
788
+ { id: "nat-1", content: "User volunteers at the food bank every month with the team." },
789
+ ]),
790
+ settings({ memoryMode: "smart", importNativeMemories: "smart" }),
791
+ config(),
792
+ { days: 1 },
793
+ deps,
794
+ );
795
+ assert.equal(dayBodiesRequested, 0, "native import must not read day bodies");
796
+ assert.equal(summary.nativeMemoriesImported, 1);
797
+ const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
798
+ assert.equal(attrs.corroboratedBySources, undefined, "no cross-source evidence without a day");
799
+ assert.equal(attrs.supportingMemoryId, "fact-9", "corpus support still applies");
800
+ } finally {
801
+ rmSync(memoryDir, { recursive: true, force: true });
802
+ }
803
+ });
804
+
805
+ test("facts written earlier in a multi-day backfill support later days in the same run", async () => {
806
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
807
+ try {
808
+ const byDate = {
809
+ "2026-06-10": [
810
+ makeConversation("c1", "2026-06-10", [
811
+ { speaker: "user", isWearer: true, text: "The launch moved to September twelfth after the vendor call." },
812
+ { speaker: "Speaker 2", text: "September twelfth confirmed with the vendor on the call." },
813
+ ]),
814
+ ],
815
+ "2026-06-11": [
816
+ makeConversation("c2", "2026-06-11", [
817
+ { speaker: "user", isWearer: true, text: "Reminder that the launch moved to September twelfth after the vendor call." },
818
+ { speaker: "Speaker 2", text: "Yes, the September date is locked in now." },
819
+ ]),
820
+ ],
821
+ };
822
+ const { deps, memoryWrites } = makeDeps(memoryDir);
823
+ assert.ok(deps.memoryGen);
824
+ // Borderline confidence so day 2 only crosses the auto threshold
825
+ // with the +0.10 corpus-support boost from day 1's write.
826
+ let call = 0;
827
+ deps.memoryGen.extract = async () => {
828
+ call += 1;
829
+ return {
830
+ facts: [
831
+ {
832
+ category: "fact",
833
+ content:
834
+ call === 1
835
+ ? "The launch moved to September twelfth after the vendor call."
836
+ : "Launch moved to September twelfth after the vendor call.",
837
+ confidence: 0.75,
838
+ tags: [],
839
+ },
840
+ ],
841
+ profileUpdates: [],
842
+ entities: [],
843
+ questions: [],
844
+ };
845
+ };
846
+ // listSupportMemories reflects what this run has written so far —
847
+ // mirroring storage, whose readAllMemories cache invalidates on
848
+ // every write.
849
+ deps.listSupportMemories = async () =>
850
+ memoryWrites.map((write, index) => ({ id: `mem-${index + 1}`, content: write.content }));
851
+
852
+ const summary = await syncWearableSource(
853
+ fakeConnector(byDate),
854
+ settings({ memoryMode: "smart" }),
855
+ config(),
856
+ { days: 2 },
857
+ deps,
858
+ );
859
+ assert.equal(summary.memoriesCreated, 2);
860
+ assert.equal(memoryWrites[0].options.status, "pending_review", "day 1 borderline stays in review");
861
+ assert.equal(memoryWrites[1].options.status, "active", "day 2 lifted by day 1's write");
862
+ const attrs = memoryWrites[1].options.structuredAttributes as Record<string, string>;
863
+ assert.equal(attrs.supportingMemoryId, "mem-1");
864
+ } finally {
865
+ rmSync(memoryDir, { recursive: true, force: true });
866
+ }
867
+ });
868
+
869
+ test("a second device's day write invalidates the first device's memory-pass completion", async () => {
870
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
871
+ try {
872
+ const day = "2026-06-11";
873
+ const conversationFor = (source: string) =>
874
+ makeConversation(`${source}-c1`, day, [
875
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
876
+ { speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
877
+ ]);
878
+ const { deps } = makeDeps(memoryDir);
879
+
880
+ // Device A syncs first and completes its memory pass for the day.
881
+ const connectorA = { ...fakeConnector({ [day]: [conversationFor("a")] }), id: "sourcea" };
882
+ await syncWearableSource(connectorA, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
883
+ let state = await loadSyncState(memoryDir);
884
+ assert.ok(state.sources.sourcea.memoryDayHashes?.[day], "A's pass recorded");
885
+
886
+ // Device B then writes a transcript for the same day: A's
887
+ // completion record for that day must be invalidated so A's next
888
+ // sync re-scores with B's evidence available.
889
+ const connectorB = { ...fakeConnector({ [day]: [conversationFor("b")] }), id: "sourceb" };
890
+ await syncWearableSource(connectorB, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
891
+ state = await loadSyncState(memoryDir);
892
+ assert.equal(
893
+ state.sources.sourcea.memoryDayHashes?.[day],
894
+ undefined,
895
+ "A's completion cleared by B's new same-day evidence",
896
+ );
897
+ assert.ok(state.sources.sourceb.memoryDayHashes?.[day], "B's own pass recorded");
898
+ } finally {
899
+ rmSync(memoryDir, { recursive: true, force: true });
900
+ }
901
+ });
902
+
903
+ test("a promotion-only re-pass still fires the reindex hook", async () => {
904
+ const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
905
+ try {
906
+ const day = "2026-06-11";
907
+ const byDate = {
908
+ [day]: [
909
+ makeConversation("c1", day, [
910
+ { speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
911
+ { speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
912
+ ]),
913
+ ],
914
+ };
915
+ const { deps, reindexes, memoryWrites } = makeDeps(memoryDir);
916
+ assert.ok(deps.memoryGen);
917
+ const borderline = "The launch moved to September twelfth after the vendor call.";
918
+ deps.memoryGen.extract = async () => ({
919
+ facts: [{ category: "fact", content: borderline, confidence: 0.75, tags: [] }],
920
+ profileUpdates: [],
921
+ entities: [],
922
+ questions: [],
923
+ });
924
+
925
+ // Sync 1: borderline fact lands in review. Reindex fires (a memory
926
+ // was created).
927
+ await syncWearableSource(fakeConnector(byDate), settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
928
+ assert.equal(memoryWrites[0].options.status, "pending_review");
929
+ const reindexesAfterFirst = reindexes.count;
930
+
931
+ // Another source's same-day transcript arrives, then sync 1's
932
+ // source re-passes: unchanged transcript, duplicate fact — but now
933
+ // corroborated, so it PROMOTES. The reindex hook must still fire.
934
+ const written: string[] = [];
935
+ deps.memoryGen.writer.findWearableMemoryByContent = async (content) =>
936
+ content.trim() === borderline ? { id: "mem-1", status: "pending_review" } : null;
937
+ deps.memoryGen.writer.promoteWearableMemory = async (id) => {
938
+ written.push(id);
939
+ return true;
940
+ };
941
+ deps.memoryGen.writer.hasFactContentHash = async (content) =>
942
+ content.trim() === borderline;
943
+ deps.readOtherSourceDayBodies = async () =>
944
+ new Map([["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."]]);
945
+ // Clear the completion record the way a sibling-source write would.
946
+ const { loadSyncState: load, saveSyncState: save } = await import("./sync-state.js");
947
+ const state = await load(memoryDir);
948
+ delete state.sources.testsource.memoryDayHashes?.[day];
949
+ await save(memoryDir, state);
950
+
951
+ const second = await syncWearableSource(
952
+ fakeConnector(byDate),
953
+ settings({ memoryMode: "smart" }),
954
+ config(),
955
+ { days: 1 },
956
+ deps,
957
+ );
958
+ assert.equal(second.transcriptsWritten.length, 0, "no transcript write");
959
+ assert.equal(second.memoriesPromoted, 1, "promotion happened");
960
+ assert.deepEqual(written, ["mem-1"]);
961
+ assert.ok(
962
+ reindexes.count > reindexesAfterFirst,
963
+ "reindex fired for the promotion-only run",
964
+ );
574
965
  } finally {
575
966
  rmSync(memoryDir, { recursive: true, force: true });
576
967
  }