@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
@@ -74,6 +74,33 @@ function makeStorage(memoryDir: string): WearableStorageIo & {
74
74
  async hasFactContentHash() {
75
75
  return false;
76
76
  },
77
+ async findWearableMemoryByContent(content: string) {
78
+ const needle = content.trim();
79
+ const match = storage.memories.find(
80
+ (memory) =>
81
+ memory.frontmatter.source.startsWith("wearable:") &&
82
+ memory.content.trim() === needle,
83
+ );
84
+ return match
85
+ ? { id: match.frontmatter.id, status: match.frontmatter.status }
86
+ : null;
87
+ },
88
+ async promoteWearableMemory(id: string) {
89
+ const match = storage.memories.find((memory) => memory.frontmatter.id === id);
90
+ if (!match || match.frontmatter.status !== "pending_review") return false;
91
+ match.frontmatter.status = "active";
92
+ return true;
93
+ },
94
+ async demoteWearableMemory(id: string, attrs: Record<string, string>) {
95
+ const match = storage.memories.find((memory) => memory.frontmatter.id === id);
96
+ if (!match || match.frontmatter.status !== "pending_review") return false;
97
+ match.frontmatter.status = "rejected";
98
+ match.frontmatter.structuredAttributes = {
99
+ ...(match.frontmatter.structuredAttributes ?? {}),
100
+ ...attrs,
101
+ };
102
+ return true;
103
+ },
77
104
  };
78
105
  return storage;
79
106
  }
@@ -313,6 +340,151 @@ test("transcriptMemories filters by wearable source and day", async () => {
313
340
  }
314
341
  });
315
342
 
343
+ test("support corpus includes pending_review rows and excludes terminal statuses", async () => {
344
+ const { registerWearableConnector, clearWearableConnectors } = await import("./registry.js");
345
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
346
+ const borderlineFact =
347
+ "The launch moved to September twelfth after the vendor call.";
348
+ const makeRow = (
349
+ id: string,
350
+ status: string | undefined,
351
+ content: string,
352
+ archivedAt?: string,
353
+ ) => ({
354
+ path: `facts/${id}.md`,
355
+ frontmatter: {
356
+ id,
357
+ source: "wearable:limitless",
358
+ created: "2026-06-09T16:00:00.000Z",
359
+ tags: ["wearable"],
360
+ ...(status !== undefined ? { status } : {}),
361
+ ...(archivedAt !== undefined ? { archivedAt } : {}),
362
+ structuredAttributes: { wearableSource: "limitless" },
363
+ },
364
+ content,
365
+ });
366
+ const runSmartSync = async (
367
+ rows: ReturnType<typeof makeRow>[],
368
+ ): Promise<Record<string, unknown>> => {
369
+ const storage = makeStorage(mkdtempSync(path.join(tmpdir(), "remnic-service-mem-")));
370
+ storage.memories.push(...rows);
371
+ const writes: Array<{ options: Record<string, unknown> }> = [];
372
+ storage.writeMemory = (async (
373
+ _category: string,
374
+ _content: string,
375
+ options: Record<string, unknown>,
376
+ ) => {
377
+ writes.push({ options });
378
+ return `mem-${writes.length}`;
379
+ }) as WearableStorageIo["writeMemory"];
380
+ try {
381
+ registerWearableConnector({
382
+ id: "testsource",
383
+ displayName: "Test Source",
384
+ factory: () => ({
385
+ id: "testsource",
386
+ displayName: "Test Source",
387
+ verifyAuth: async () => ({ ok: true }),
388
+ fetchConversations: async () => ({
389
+ conversations: [
390
+ {
391
+ id: "c1",
392
+ source: "testsource",
393
+ startIso: "2026-06-10T15:00:00.000Z",
394
+ endIso: "2026-06-10T15:30:00.000Z",
395
+ segments: [
396
+ { speakerKey: "user", isWearer: true, text: "We are moving the launch to September twelfth after that vendor call wrapped up." },
397
+ { speakerKey: "guest", speakerName: "guest", text: "Confirmed, the vendor is aligned on the September date for the launch." },
398
+ ],
399
+ },
400
+ ],
401
+ nextCursor: null,
402
+ }),
403
+ }),
404
+ });
405
+ const service = new WearablesService({
406
+ config: {
407
+ ...defaultWearablesConfig(),
408
+ enabled: true,
409
+ digestEnabled: false,
410
+ sources: {
411
+ testsource: { ...defaultWearableSourceSettings(), enabled: true, memoryMode: "smart" },
412
+ },
413
+ },
414
+ getStorage: async () => storage,
415
+ // Borderline: 0.75 * 0.8 = 0.6 — active only with +0.10 support.
416
+ extract: async () => ({
417
+ facts: [{ category: "fact", content: borderlineFact, confidence: 0.75, tags: [] }],
418
+ profileUpdates: [],
419
+ entities: [],
420
+ questions: [],
421
+ }),
422
+ searchBackend: null,
423
+ });
424
+ await service.sync({ date: "2026-06-10" });
425
+ assert.equal(writes.length, 1);
426
+ return writes[0].options;
427
+ } finally {
428
+ clearWearableConnectors();
429
+ }
430
+ };
431
+
432
+ try {
433
+ // A pending_review row with matching content IS support evidence.
434
+ // (Similar wording, not identical — identical content would be
435
+ // consumed by the duplicate-existing dedup before scoring.)
436
+ const supported = await runSmartSync([
437
+ makeRow(
438
+ "pending-1",
439
+ "pending_review",
440
+ "The launch moved to September twelfth after the vendor call, noted earlier.",
441
+ ),
442
+ ]);
443
+ assert.equal(supported.status, "active");
444
+ assert.equal(
445
+ (supported.structuredAttributes as Record<string, string>).supportingMemoryId,
446
+ "pending-1",
447
+ );
448
+
449
+ // Terminal statuses with the same content are NOT support evidence.
450
+ const similar =
451
+ "The launch moved to September twelfth after the vendor call, noted earlier.";
452
+ const unsupported = await runSmartSync([
453
+ makeRow("rejected-1", "rejected", similar),
454
+ makeRow("quarantined-1", "quarantined", similar),
455
+ makeRow("superseded-1", "superseded", similar),
456
+ makeRow("archived-1", "archived", similar),
457
+ makeRow("forgotten-1", "forgotten", similar),
458
+ // Archived via archivedAt with NO explicit status — the
459
+ // canonical inferMemoryStatus must resolve this to archived.
460
+ makeRow("archived-implicit-1", undefined, similar, "2026-06-09T00:00:00.000Z"),
461
+ ]);
462
+ assert.equal(unsupported.status, "pending_review");
463
+ assert.equal(
464
+ (unsupported.structuredAttributes as Record<string, string>).supportingMemoryId,
465
+ undefined,
466
+ );
467
+
468
+ // Content matching ONLY through the "[Attributes: ...]" enrichment
469
+ // suffix is not corroboration — the suffix is stripped before
470
+ // token matching, so attribute metadata never grants the boost.
471
+ const suffixOnly = await runSmartSync([
472
+ makeRow(
473
+ "pending-2",
474
+ "pending_review",
475
+ "Unrelated note about quarterly budget planning.\n[Attributes: context: launch moved to September twelfth after the vendor call]",
476
+ ),
477
+ ]);
478
+ assert.equal(suffixOnly.status, "pending_review");
479
+ assert.equal(
480
+ (suffixOnly.structuredAttributes as Record<string, string>).supportingMemoryId,
481
+ undefined,
482
+ );
483
+ } finally {
484
+ rmSync(dir, { recursive: true, force: true });
485
+ }
486
+ });
487
+
316
488
  test("the wearable memory writer dedups non-fact categories by content scan", async () => {
317
489
  const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
318
490
  try {
@@ -12,7 +12,10 @@ import {
12
12
  compileCorrectionRule,
13
13
  } from "./corrections.js";
14
14
  import { describeErrorForOperator, WearablesInputError } from "./errors.js";
15
+ import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
15
16
  import { isValidTranscriptDate, parseDayTranscript } from "./day-store.js";
17
+ import { stripAttributesSuffix } from "../storage.js";
18
+ import type { MemoryFrontmatter } from "../types.js";
16
19
  import type { WearableMemoryGenDeps } from "./memory-gen.js";
17
20
  import { WEARABLE_SOURCE_PREFIX, wearableSourceLabel } from "./memory-gen.js";
18
21
  import {
@@ -65,6 +68,8 @@ export interface WearableStorageIo {
65
68
  created: string;
66
69
  tags: string[];
67
70
  status?: string;
71
+ /** Archival timestamp — rows with this set are not support. */
72
+ archivedAt?: string;
68
73
  structuredAttributes?: Record<string, string>;
69
74
  };
70
75
  content: string;
@@ -72,6 +77,18 @@ export interface WearableStorageIo {
72
77
  >;
73
78
  writeMemory: WearableMemoryGenDeps["writer"]["writeMemory"];
74
79
  hasFactContentHash(content: string): Promise<boolean>;
80
+ findWearableMemoryByContent(
81
+ content: string,
82
+ ): Promise<{ id: string; status: string | undefined } | null>;
83
+ promoteWearableMemory(
84
+ id: string,
85
+ attributeUpdates: Record<string, string>,
86
+ confidence?: number,
87
+ ): Promise<boolean>;
88
+ demoteWearableMemory(
89
+ id: string,
90
+ attributeUpdates: Record<string, string>,
91
+ ): Promise<boolean>;
75
92
  }
76
93
 
77
94
  export interface WearableSearchBackend {
@@ -87,6 +104,12 @@ export interface WearablesServiceDeps {
87
104
  getStorage(): Promise<WearableStorageIo>;
88
105
  /** Extraction hook; null when no engine is available. */
89
106
  extract: WearableMemoryGenDeps["extract"] | null;
107
+ /**
108
+ * LLM-as-judge hook for smart memoryMode (the orchestrator wires the
109
+ * existing extraction judge here). Absent degrades smart mode to
110
+ * confidence x sourceTrust + corroboration scoring.
111
+ */
112
+ judgeFacts?: WearableMemoryGenDeps["judgeFacts"];
90
113
  /** Search backend (QMD); null disables indexed search. */
91
114
  searchBackend: WearableSearchBackend | null;
92
115
  /** Fired after transcript writes so the search index refreshes. */
@@ -136,15 +159,25 @@ export function createWearableMemoryWriter(
136
159
  ): WearableMemoryGenDeps["writer"] {
137
160
  return {
138
161
  writeMemory: storage.writeMemory.bind(storage),
162
+ findWearableMemoryByContent: async (content: string) =>
163
+ (await storage.findWearableMemoryByContent(content)) as
164
+ | { id: string; status: import("../types.js").MemoryStatus | undefined }
165
+ | null,
166
+ promoteWearableMemory: storage.promoteWearableMemory.bind(storage),
167
+ demoteWearableMemory: storage.demoteWearableMemory.bind(storage),
139
168
  hasFactContentHash: async (content: string) => {
140
169
  if (await storage.hasFactContentHash(content)) return true;
141
- const needle = content.trim();
170
+ // Compare with the "[Attributes: ...]" enrichment suffix removed
171
+ // on BOTH sides — stored wearable bodies carry it, callers pass
172
+ // raw fact text. Without the strip, digest/candidate dedup never
173
+ // matched attribute-bearing memories.
174
+ const needle = stripAttributesSuffix(content);
142
175
  const memories = await storage.readAllMemories();
143
176
  return memories.some(
144
177
  (memory) =>
145
178
  typeof memory.frontmatter.source === "string" &&
146
179
  memory.frontmatter.source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`) &&
147
- memory.content.trim() === needle,
180
+ stripAttributesSuffix(memory.content) === needle,
148
181
  );
149
182
  },
150
183
  };
@@ -263,6 +296,9 @@ export class WearablesService {
263
296
  ? {
264
297
  extract: this.deps.extract,
265
298
  writer: createWearableMemoryWriter(storage),
299
+ ...(this.deps.judgeFacts !== undefined
300
+ ? { judgeFacts: this.deps.judgeFacts }
301
+ : {}),
266
302
  }
267
303
  : null;
268
304
 
@@ -293,8 +329,53 @@ export class WearablesService {
293
329
  },
294
330
  writeDayTranscript: (source, date, serialized) =>
295
331
  storage.writeWearableDayTranscript(source, date, serialized),
296
- afterTranscriptsWritten: this.deps.reindexSearch,
332
+ afterWrites: this.deps.reindexSearch,
297
333
  memoryGen,
334
+ // Cross-device corroboration evidence (smart mode): other
335
+ // sources' stored transcripts for the same day...
336
+ readOtherSourceDayBodies: async (date, excludeSource) => {
337
+ const bodies = new Map<string, string>();
338
+ const days = await storage.listWearableTranscriptDays();
339
+ for (const entry of days) {
340
+ if (entry.date !== date || entry.source === excludeSource) continue;
341
+ if (bodies.size >= 4) break;
342
+ const raw = await storage.readWearableDayTranscript(entry.source, entry.date);
343
+ if (raw === null) continue;
344
+ bodies.set(entry.source, parseDayTranscript(raw)?.body ?? raw);
345
+ }
346
+ return bodies;
347
+ },
348
+ // ...and existing memories for the support boost. Status
349
+ // resolves through the canonical inferMemoryStatus so rows
350
+ // archived via `archivedAt` (or an archive/ path) without an
351
+ // explicit status never count. Explicit allow-list: active
352
+ // rows AND pending_review rows — a borderline fact observed
353
+ // again on a later day is repetition signal and the support
354
+ // boost is how it earns promotion. Rejected/quarantined/
355
+ // superseded/archived/forgotten rows never count (CLAUDE.md
356
+ // rule 53). Bodies feed token matching with the
357
+ // "[Attributes: ...]" enrichment suffix stripped — attribute
358
+ // metadata must never grant corroboration.
359
+ listSupportMemories: async () => {
360
+ const memories = await storage.readAllMemories();
361
+ const support: Array<{ id: string; content: string }> = [];
362
+ for (const memory of memories) {
363
+ // WearableStorageIo narrows MemoryFrontmatter for
364
+ // testability; production hands us the real thing.
365
+ const status = inferMemoryStatus(
366
+ memory.frontmatter as MemoryFrontmatter,
367
+ memory.path,
368
+ );
369
+ if (status !== "active" && status !== "pending_review") {
370
+ continue;
371
+ }
372
+ support.push({
373
+ id: memory.frontmatter.id,
374
+ content: stripAttributesSuffix(memory.content),
375
+ });
376
+ }
377
+ return support;
378
+ },
298
379
  },
299
380
  );
300
381
  summaries.push(summary);
@@ -90,6 +90,87 @@ test("transcript files never surface from readAllMemories", async () => {
90
90
  }
91
91
  });
92
92
 
93
+ test("promoteWearableMemory flips status, merges evidence, and updates confidence", async () => {
94
+ const { storage, dir } = makeStorage();
95
+ try {
96
+ const id = await storage.writeMemory("fact", "Launch moved to September twelfth.", {
97
+ confidence: 0.6,
98
+ source: "wearable:limitless",
99
+ status: "pending_review",
100
+ structuredAttributes: { wearableSource: "limitless", trustScore: "0.600" },
101
+ });
102
+ const promoted = await storage.promoteWearableMemory(
103
+ id,
104
+ { trustScore: "0.750", trustDecision: "promoted-by-corroboration" },
105
+ 0.75,
106
+ );
107
+ assert.equal(promoted, true);
108
+ const memory = (await storage.readAllMemories()).find(
109
+ (entry) => entry.frontmatter.id === id,
110
+ );
111
+ assert.ok(memory);
112
+ assert.equal(memory.frontmatter.status, "active");
113
+ assert.equal(memory.frontmatter.confidence, 0.75);
114
+ assert.equal(memory.frontmatter.structuredAttributes?.trustScore, "0.750");
115
+ assert.equal(
116
+ memory.frontmatter.structuredAttributes?.trustDecision,
117
+ "promoted-by-corroboration",
118
+ );
119
+
120
+ // Already-active rows are not re-promoted (operator decisions win).
121
+ assert.equal(await storage.promoteWearableMemory(id, {}, 0.9), false);
122
+ assert.equal(await storage.promoteWearableMemory("missing-id", {}), false);
123
+
124
+ const found = await storage.findWearableMemoryByContent(
125
+ "Launch moved to September twelfth.",
126
+ );
127
+ assert.equal(found?.id, id);
128
+ } finally {
129
+ rmSync(dir, { recursive: true, force: true });
130
+ }
131
+ });
132
+
133
+ test("demoteWearableMemory rejects only pending rows and merges evidence", async () => {
134
+ const { storage, dir } = makeStorage();
135
+ try {
136
+ const id = await storage.writeMemory("fact", "Vendor call moved the launch again.", {
137
+ confidence: 0.5,
138
+ source: "wearable:limitless",
139
+ status: "pending_review",
140
+ structuredAttributes: { wearableSource: "limitless", trustScore: "0.500" },
141
+ });
142
+ const demoted = await storage.demoteWearableMemory(id, {
143
+ trustScore: "0.310",
144
+ trustDecision: "demoted-by-rejection",
145
+ judgeVerdict: "reject",
146
+ });
147
+ assert.equal(demoted, true);
148
+ const memory = (await storage.readAllMemories()).find(
149
+ (entry) => entry.frontmatter.id === id,
150
+ );
151
+ assert.ok(memory);
152
+ assert.equal(memory.frontmatter.status, "rejected");
153
+ assert.equal(memory.frontmatter.structuredAttributes?.trustDecision, "demoted-by-rejection");
154
+ assert.equal(memory.frontmatter.structuredAttributes?.wearableSource, "limitless");
155
+
156
+ // Rejected rows are terminal for the wearable pipeline: no
157
+ // re-demote, no promote (operator surfaces own them from here).
158
+ assert.equal(await storage.demoteWearableMemory(id, {}), false);
159
+ assert.equal(await storage.promoteWearableMemory(id, {}, 0.9), false);
160
+ assert.equal(await storage.demoteWearableMemory("missing-id", {}), false);
161
+
162
+ // Active rows are never auto-demoted.
163
+ const activeId = await storage.writeMemory("fact", "Approved active row.", {
164
+ confidence: 0.9,
165
+ source: "wearable:limitless",
166
+ status: "active",
167
+ });
168
+ assert.equal(await storage.demoteWearableMemory(activeId, {}), false);
169
+ } finally {
170
+ rmSync(dir, { recursive: true, force: true });
171
+ }
172
+ });
173
+
93
174
  test("non-transcript files in the wearables tree are ignored by listing", async () => {
94
175
  const { storage, dir } = makeStorage();
95
176
  try {
@@ -0,0 +1,123 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ computeTrustScore,
6
+ decideSmart,
7
+ findCorroboration,
8
+ tokenizeDayBody,
9
+ TRUST_CROSS_SOURCE_BOOST,
10
+ TRUST_JUDGE_ACCEPT_BOOST,
11
+ TRUST_SUPPORTING_MEMORY_BOOST,
12
+ } from "./trust.js";
13
+
14
+ const NO_EVIDENCE = { corroboratedBySources: [] };
15
+
16
+ test("trust = confidence x sourceTrust, clamped to [0,1]", () => {
17
+ assert.ok(
18
+ Math.abs(
19
+ computeTrustScore({ extractionConfidence: 0.9, sourceTrust: 0.8, evidence: NO_EVIDENCE }) -
20
+ 0.72,
21
+ ) < 1e-9,
22
+ );
23
+ // Missing confidence defaults to 0.7.
24
+ assert.equal(
25
+ computeTrustScore({ extractionConfidence: undefined, sourceTrust: 1, evidence: NO_EVIDENCE }),
26
+ 0.7,
27
+ );
28
+ // Boosts never push past 1.
29
+ assert.equal(
30
+ computeTrustScore({
31
+ extractionConfidence: 1,
32
+ sourceTrust: 1,
33
+ judgeVerdict: "accept",
34
+ evidence: { corroboratedBySources: ["bee"], supportingMemoryId: "m1" },
35
+ }),
36
+ 1,
37
+ );
38
+ });
39
+
40
+ test("judge accept and corroboration boosts stack as documented", () => {
41
+ const base = computeTrustScore({
42
+ extractionConfidence: 0.5,
43
+ sourceTrust: 0.8,
44
+ evidence: NO_EVIDENCE,
45
+ });
46
+ const judged = computeTrustScore({
47
+ extractionConfidence: 0.5,
48
+ sourceTrust: 0.8,
49
+ judgeVerdict: "accept",
50
+ evidence: NO_EVIDENCE,
51
+ });
52
+ const corroborated = computeTrustScore({
53
+ extractionConfidence: 0.5,
54
+ sourceTrust: 0.8,
55
+ judgeVerdict: "accept",
56
+ evidence: { corroboratedBySources: ["omi"], supportingMemoryId: "m1" },
57
+ });
58
+ assert.ok(Math.abs(judged - base - TRUST_JUDGE_ACCEPT_BOOST) < 1e-9);
59
+ assert.ok(
60
+ Math.abs(
61
+ corroborated -
62
+ judged -
63
+ TRUST_CROSS_SOURCE_BOOST -
64
+ TRUST_SUPPORTING_MEMORY_BOOST,
65
+ ) < 1e-9,
66
+ );
67
+ });
68
+
69
+ test("decideSmart: judge verdicts short-circuit; trust bands otherwise", () => {
70
+ const thresholds = { autoApproveTrust: 0.7, reviewTrust: 0.45 };
71
+ assert.equal(decideSmart(0.99, "reject", thresholds).outcome, "drop");
72
+ assert.equal(decideSmart(0.99, "defer", thresholds).outcome, "review");
73
+ assert.equal(decideSmart(0.7, "accept", thresholds).outcome, "active");
74
+ assert.equal(decideSmart(0.7, undefined, thresholds).outcome, "active");
75
+ assert.equal(decideSmart(0.5, undefined, thresholds).outcome, "review");
76
+ assert.equal(decideSmart(0.2, undefined, thresholds).outcome, "drop");
77
+ });
78
+
79
+ test("cross-source corroboration requires high token coverage", () => {
80
+ const beeDay = tokenizeDayBody(
81
+ "We agreed the product launch moves to September twelfth after the vendor call.",
82
+ );
83
+ const context = {
84
+ otherSourceDayTokens: new Map([["bee", beeDay]]),
85
+ existingMemories: [],
86
+ };
87
+ const corroborated = findCorroboration(
88
+ "Launch moved to September twelfth after vendor call.",
89
+ context,
90
+ );
91
+ assert.deepEqual(corroborated.corroboratedBySources, ["bee"]);
92
+
93
+ const unrelated = findCorroboration(
94
+ "Dentist appointment is on Thursday afternoon downtown.",
95
+ context,
96
+ );
97
+ assert.deepEqual(unrelated.corroboratedBySources, []);
98
+ });
99
+
100
+ test("existing-memory support sets supportingMemoryId", () => {
101
+ const context = {
102
+ otherSourceDayTokens: new Map<string, Set<string>>(),
103
+ existingMemories: [
104
+ { id: "fact-1", content: "User prefers the aisle seat on long flights." },
105
+ { id: "fact-2", content: "The launch moved to September twelfth, vendor informed." },
106
+ ],
107
+ };
108
+ const supported = findCorroboration(
109
+ "Launch moved to September twelfth and the vendor was informed.",
110
+ context,
111
+ );
112
+ assert.equal(supported.supportingMemoryId, "fact-2");
113
+ });
114
+
115
+ test("very short facts never corroborate (too little signal)", () => {
116
+ const context = {
117
+ otherSourceDayTokens: new Map([["bee", tokenizeDayBody("yes ok sure fine")]]),
118
+ existingMemories: [{ id: "m", content: "yes ok sure fine" }],
119
+ };
120
+ const evidence = findCorroboration("yes ok", context);
121
+ assert.deepEqual(evidence.corroboratedBySources, []);
122
+ assert.equal(evidence.supportingMemoryId, undefined);
123
+ });