@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.
- package/dist/access-cli.js +18 -18
- package/dist/access-http.d.ts +6 -4
- package/dist/access-http.js +7 -6
- package/dist/access-mcp.d.ts +6 -4
- package/dist/access-mcp.js +6 -5
- package/dist/{access-service-C_sfOHsX.d.ts → access-service-C4v-eFjB.d.ts} +2 -2
- package/dist/access-service.d.ts +6 -4
- package/dist/access-service.js +5 -4
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +6 -4
- package/dist/briefing.d.ts +33 -2
- package/dist/briefing.js +5 -2
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +2 -2
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +5 -5
- package/dist/{chunk-GE7Q7KXP.js → chunk-2VJ7AJFX.js} +2 -2
- package/dist/{chunk-KVFYTRMV.js → chunk-4QEUKASL.js} +2 -2
- package/dist/{chunk-KB4MFBF5.js → chunk-5S6IREG3.js} +3 -3
- package/dist/{chunk-LQYTQCXM.js → chunk-6LBQL5US.js} +2 -2
- package/dist/{chunk-KGIGRNR6.js → chunk-723OMPUI.js} +4 -4
- package/dist/{chunk-TZDSNIRO.js → chunk-ADOD7PJC.js} +5 -5
- package/dist/{chunk-SHV5Y2WU.js → chunk-BL33LBTN.js} +3 -3
- package/dist/{chunk-532VCWYW.js → chunk-BWK5EEKS.js} +2 -2
- package/dist/{chunk-KKTXCFD7.js → chunk-EORL2IDM.js} +39 -8
- package/dist/{chunk-KKTXCFD7.js.map → chunk-EORL2IDM.js.map} +1 -1
- package/dist/{chunk-F3FY3D3S.js → chunk-F6USGHMO.js} +10 -5
- package/dist/chunk-F6USGHMO.js.map +1 -0
- package/dist/{chunk-STDAAGH7.js → chunk-GXWFZYSR.js} +39 -3
- package/dist/chunk-GXWFZYSR.js.map +1 -0
- package/dist/{chunk-3VONWEQB.js → chunk-HZVIYZYN.js} +2 -2
- package/dist/{chunk-Y3TMFC6I.js → chunk-K3BTOW7N.js} +3 -3
- package/dist/{chunk-Z3CCEP6F.js → chunk-K47C6M2C.js} +5 -5
- package/dist/{chunk-N5RGXWLQ.js → chunk-MQ24KOOR.js} +2 -2
- package/dist/{chunk-3MNBW7R7.js → chunk-NRQJBK36.js} +2 -2
- package/dist/{chunk-MON3LMO7.js → chunk-NRST7W5Q.js} +5 -5
- package/dist/{chunk-3R2UZV3U.js → chunk-OOFBE62K.js} +2 -2
- package/dist/{chunk-MVQN73GT.js → chunk-OQMR2SDZ.js} +2 -2
- package/dist/{chunk-UGHUNQ74.js → chunk-RSKUUEBA.js} +73 -1
- package/dist/chunk-RSKUUEBA.js.map +1 -0
- package/dist/{chunk-QDV6VAD4.js → chunk-S5W37FPX.js} +2 -2
- package/dist/{chunk-57QXN2CS.js → chunk-SACS6KE6.js} +2 -2
- package/dist/{chunk-UELS6WWF.js → chunk-UE57H4MA.js} +2 -2
- package/dist/{chunk-2RHI3FGV.js → chunk-VUTPRX7K.js} +20 -14
- package/dist/{chunk-2RHI3FGV.js.map → chunk-VUTPRX7K.js.map} +1 -1
- package/dist/{chunk-AZ4RI3QD.js → chunk-YJOWWRRS.js} +450 -48
- package/dist/chunk-YJOWWRRS.js.map +1 -0
- package/dist/{chunk-P2D2MM47.js → chunk-ZZSXUZF3.js} +2 -2
- package/dist/{cli-EZv6YE6_.d.ts → cli-B_6EMiQc.d.ts} +3 -3
- package/dist/cli.d.ts +7 -5
- package/dist/cli.js +18 -17
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +2 -2
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +2 -2
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +2 -2
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +2 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -4
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-judge.js +3 -3
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/fallback-llm.js +2 -2
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +29 -27
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +6 -4
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +3 -3
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +2 -2
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +6 -6
- package/dist/{orchestrator-CEycaY3M.d.ts → orchestrator-Dlw3ae4B.d.ts} +111 -10
- package/dist/orchestrator.d.ts +5 -3
- package/dist/orchestrator.js +15 -14
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +24 -24
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-FbhPeJjB.d.ts → semantic-consolidation-C4sefXEI.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +3 -3
- package/dist/semantic-rule-promotion.js +2 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +38 -2
- package/dist/storage.js +5 -3
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/{types-D5VRAI04.d.ts → types-2vqxmO0j.d.ts} +39 -10
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +2 -2
- package/package.json +1 -1
- package/src/access-service.ts +7 -0
- package/src/briefing.ts +67 -1
- package/src/index.ts +2 -0
- package/src/orchestrator.ts +42 -0
- package/src/storage.ts +100 -0
- package/src/wearables/cli.ts +6 -0
- package/src/wearables/config.test.ts +33 -4
- package/src/wearables/config.ts +39 -7
- package/src/wearables/memory-gen.test.ts +416 -1
- package/src/wearables/memory-gen.ts +381 -23
- package/src/wearables/pipeline.test.ts +309 -1
- package/src/wearables/pipeline.ts +131 -9
- package/src/wearables/service.test.ts +172 -0
- package/src/wearables/service.ts +84 -3
- package/src/wearables/storage-io.test.ts +81 -0
- package/src/wearables/trust.test.ts +123 -0
- package/src/wearables/trust.ts +168 -0
- package/src/wearables/types.ts +37 -8
- package/dist/chunk-AZ4RI3QD.js.map +0 -1
- package/dist/chunk-F3FY3D3S.js.map +0 -1
- package/dist/chunk-STDAAGH7.js.map +0 -1
- package/dist/chunk-UGHUNQ74.js.map +0 -1
- /package/dist/{chunk-GE7Q7KXP.js.map → chunk-2VJ7AJFX.js.map} +0 -0
- /package/dist/{chunk-KVFYTRMV.js.map → chunk-4QEUKASL.js.map} +0 -0
- /package/dist/{chunk-KB4MFBF5.js.map → chunk-5S6IREG3.js.map} +0 -0
- /package/dist/{chunk-LQYTQCXM.js.map → chunk-6LBQL5US.js.map} +0 -0
- /package/dist/{chunk-KGIGRNR6.js.map → chunk-723OMPUI.js.map} +0 -0
- /package/dist/{chunk-TZDSNIRO.js.map → chunk-ADOD7PJC.js.map} +0 -0
- /package/dist/{chunk-SHV5Y2WU.js.map → chunk-BL33LBTN.js.map} +0 -0
- /package/dist/{chunk-532VCWYW.js.map → chunk-BWK5EEKS.js.map} +0 -0
- /package/dist/{chunk-3VONWEQB.js.map → chunk-HZVIYZYN.js.map} +0 -0
- /package/dist/{chunk-Y3TMFC6I.js.map → chunk-K3BTOW7N.js.map} +0 -0
- /package/dist/{chunk-Z3CCEP6F.js.map → chunk-K47C6M2C.js.map} +0 -0
- /package/dist/{chunk-N5RGXWLQ.js.map → chunk-MQ24KOOR.js.map} +0 -0
- /package/dist/{chunk-3MNBW7R7.js.map → chunk-NRQJBK36.js.map} +0 -0
- /package/dist/{chunk-MON3LMO7.js.map → chunk-NRST7W5Q.js.map} +0 -0
- /package/dist/{chunk-3R2UZV3U.js.map → chunk-OOFBE62K.js.map} +0 -0
- /package/dist/{chunk-MVQN73GT.js.map → chunk-OQMR2SDZ.js.map} +0 -0
- /package/dist/{chunk-QDV6VAD4.js.map → chunk-S5W37FPX.js.map} +0 -0
- /package/dist/{chunk-57QXN2CS.js.map → chunk-SACS6KE6.js.map} +0 -0
- /package/dist/{chunk-UELS6WWF.js.map → chunk-UE57H4MA.js.map} +0 -0
- /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 {
|
package/src/wearables/service.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
});
|