@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
|
@@ -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
|
|
126
|
+
async afterWrites() {
|
|
123
127
|
reindexes.count += 1;
|
|
124
128
|
},
|
|
125
129
|
memoryGen: {
|
|
@@ -576,6 +580,310 @@ test("page-capped days carry a visible partial marker and keep warning", async (
|
|
|
576
580
|
}
|
|
577
581
|
});
|
|
578
582
|
|
|
583
|
+
test("a judge outage does not re-run the memory pass forever", async () => {
|
|
584
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
585
|
+
try {
|
|
586
|
+
const byDate = {
|
|
587
|
+
"2026-06-11": [
|
|
588
|
+
makeConversation("c1", "2026-06-11", [
|
|
589
|
+
{ speaker: "user", isWearer: true, text: "The vendor contract was renewed for another year today." },
|
|
590
|
+
{ speaker: "Speaker 2", text: "Renewal confirmed, the paperwork went through this afternoon." },
|
|
591
|
+
]),
|
|
592
|
+
],
|
|
593
|
+
};
|
|
594
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
595
|
+
assert.ok(deps.memoryGen);
|
|
596
|
+
let extractCalls = 0;
|
|
597
|
+
const baseExtract = deps.memoryGen.extract;
|
|
598
|
+
deps.memoryGen.extract = async (turns) => {
|
|
599
|
+
extractCalls += 1;
|
|
600
|
+
return baseExtract(turns);
|
|
601
|
+
};
|
|
602
|
+
deps.memoryGen.judgeFacts = async () => {
|
|
603
|
+
throw new Error("judge backend down");
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const first = await syncWearableSource(
|
|
607
|
+
fakeConnector(byDate),
|
|
608
|
+
settings({ memoryMode: "smart" }),
|
|
609
|
+
config(),
|
|
610
|
+
{ days: 1 },
|
|
611
|
+
deps,
|
|
612
|
+
);
|
|
613
|
+
assert.ok(first.warnings.some((warning) => warning.includes("judge unavailable")));
|
|
614
|
+
assert.equal(first.memoriesCreated, 1, "degraded pass still writes");
|
|
615
|
+
const callsAfterFirst = extractCalls;
|
|
616
|
+
|
|
617
|
+
// Unchanged day, judge still down: the degraded-but-complete pass
|
|
618
|
+
// must have recorded completion — no re-extraction.
|
|
619
|
+
const second = await syncWearableSource(
|
|
620
|
+
fakeConnector(byDate),
|
|
621
|
+
settings({ memoryMode: "smart" }),
|
|
622
|
+
config(),
|
|
623
|
+
{ days: 1 },
|
|
624
|
+
deps,
|
|
625
|
+
);
|
|
626
|
+
assert.equal(extractCalls, callsAfterFirst, "no repeat extraction for an unchanged day");
|
|
627
|
+
assert.equal(second.memoriesCreated, 0);
|
|
628
|
+
assert.equal(memoryWrites.length, 1);
|
|
629
|
+
} finally {
|
|
630
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("smart mode: another device's stored day transcript corroborates borderline facts", async () => {
|
|
635
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
636
|
+
try {
|
|
637
|
+
const byDate = {
|
|
638
|
+
"2026-06-11": [
|
|
639
|
+
makeConversation("c1", "2026-06-11", [
|
|
640
|
+
{ speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call." },
|
|
641
|
+
{ speaker: "Speaker 2", text: "September twelfth works for everyone on the vendor side too." },
|
|
642
|
+
]),
|
|
643
|
+
],
|
|
644
|
+
};
|
|
645
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
646
|
+
assert.ok(deps.memoryGen);
|
|
647
|
+
// Borderline extraction: 0.75 * 0.8 = 0.6 — review band unless corroborated.
|
|
648
|
+
deps.memoryGen.extract = async () => ({
|
|
649
|
+
facts: [
|
|
650
|
+
{
|
|
651
|
+
category: "fact",
|
|
652
|
+
content: "The launch moved to September twelfth after the vendor call.",
|
|
653
|
+
confidence: 0.75,
|
|
654
|
+
tags: [],
|
|
655
|
+
},
|
|
656
|
+
],
|
|
657
|
+
profileUpdates: [],
|
|
658
|
+
entities: [],
|
|
659
|
+
questions: [],
|
|
660
|
+
});
|
|
661
|
+
// A second device already stored a transcript covering the same day.
|
|
662
|
+
deps.readOtherSourceDayBodies = async (date, excludeSource) => {
|
|
663
|
+
assert.equal(date, "2026-06-11");
|
|
664
|
+
assert.equal(excludeSource, "testsource");
|
|
665
|
+
return new Map([
|
|
666
|
+
["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."],
|
|
667
|
+
]);
|
|
668
|
+
};
|
|
669
|
+
deps.listSupportMemories = async () => [];
|
|
670
|
+
|
|
671
|
+
const summary = await syncWearableSource(
|
|
672
|
+
fakeConnector(byDate),
|
|
673
|
+
settings({ memoryMode: "smart" }),
|
|
674
|
+
config(),
|
|
675
|
+
{ days: 1 },
|
|
676
|
+
deps,
|
|
677
|
+
);
|
|
678
|
+
assert.equal(summary.memoriesCreated, 1);
|
|
679
|
+
assert.equal(memoryWrites[0].options.status, "active", "corroboration lifts review -> active");
|
|
680
|
+
const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
|
|
681
|
+
assert.equal(attrs.corroboratedBySources, "bee");
|
|
682
|
+
assert.equal(attrs.trustDecision, "auto-approved");
|
|
683
|
+
} finally {
|
|
684
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("smart native import never uses day-scoped cross-source corroboration", async () => {
|
|
689
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
690
|
+
try {
|
|
691
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
692
|
+
assert.ok(deps.memoryGen);
|
|
693
|
+
let dayBodiesRequested = 0;
|
|
694
|
+
deps.readOtherSourceDayBodies = async () => {
|
|
695
|
+
dayBodiesRequested += 1;
|
|
696
|
+
return new Map([["bee", "the launch moves to september twelfth after that vendor call"]]);
|
|
697
|
+
};
|
|
698
|
+
deps.listSupportMemories = async () => [
|
|
699
|
+
{ id: "fact-9", content: "User volunteers at the food bank every month with the team." },
|
|
700
|
+
];
|
|
701
|
+
const summary = await syncWearableSource(
|
|
702
|
+
fakeConnector({}, [
|
|
703
|
+
// No day attached — must not be scored against any day's tokens,
|
|
704
|
+
// but corpus support still applies: 0.7*(0.8*0.9)+0.10 = 0.604 -> review.
|
|
705
|
+
{ id: "nat-1", content: "User volunteers at the food bank every month with the team." },
|
|
706
|
+
]),
|
|
707
|
+
settings({ memoryMode: "smart", importNativeMemories: "smart" }),
|
|
708
|
+
config(),
|
|
709
|
+
{ days: 1 },
|
|
710
|
+
deps,
|
|
711
|
+
);
|
|
712
|
+
assert.equal(dayBodiesRequested, 0, "native import must not read day bodies");
|
|
713
|
+
assert.equal(summary.nativeMemoriesImported, 1);
|
|
714
|
+
const attrs = memoryWrites[0].options.structuredAttributes as Record<string, string>;
|
|
715
|
+
assert.equal(attrs.corroboratedBySources, undefined, "no cross-source evidence without a day");
|
|
716
|
+
assert.equal(attrs.supportingMemoryId, "fact-9", "corpus support still applies");
|
|
717
|
+
} finally {
|
|
718
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("facts written earlier in a multi-day backfill support later days in the same run", async () => {
|
|
723
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
724
|
+
try {
|
|
725
|
+
const byDate = {
|
|
726
|
+
"2026-06-10": [
|
|
727
|
+
makeConversation("c1", "2026-06-10", [
|
|
728
|
+
{ speaker: "user", isWearer: true, text: "The launch moved to September twelfth after the vendor call." },
|
|
729
|
+
{ speaker: "Speaker 2", text: "September twelfth confirmed with the vendor on the call." },
|
|
730
|
+
]),
|
|
731
|
+
],
|
|
732
|
+
"2026-06-11": [
|
|
733
|
+
makeConversation("c2", "2026-06-11", [
|
|
734
|
+
{ speaker: "user", isWearer: true, text: "Reminder that the launch moved to September twelfth after the vendor call." },
|
|
735
|
+
{ speaker: "Speaker 2", text: "Yes, the September date is locked in now." },
|
|
736
|
+
]),
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
740
|
+
assert.ok(deps.memoryGen);
|
|
741
|
+
// Borderline confidence so day 2 only crosses the auto threshold
|
|
742
|
+
// with the +0.10 corpus-support boost from day 1's write.
|
|
743
|
+
let call = 0;
|
|
744
|
+
deps.memoryGen.extract = async () => {
|
|
745
|
+
call += 1;
|
|
746
|
+
return {
|
|
747
|
+
facts: [
|
|
748
|
+
{
|
|
749
|
+
category: "fact",
|
|
750
|
+
content:
|
|
751
|
+
call === 1
|
|
752
|
+
? "The launch moved to September twelfth after the vendor call."
|
|
753
|
+
: "Launch moved to September twelfth after the vendor call.",
|
|
754
|
+
confidence: 0.75,
|
|
755
|
+
tags: [],
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
profileUpdates: [],
|
|
759
|
+
entities: [],
|
|
760
|
+
questions: [],
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
// listSupportMemories reflects what this run has written so far —
|
|
764
|
+
// mirroring storage, whose readAllMemories cache invalidates on
|
|
765
|
+
// every write.
|
|
766
|
+
deps.listSupportMemories = async () =>
|
|
767
|
+
memoryWrites.map((write, index) => ({ id: `mem-${index + 1}`, content: write.content }));
|
|
768
|
+
|
|
769
|
+
const summary = await syncWearableSource(
|
|
770
|
+
fakeConnector(byDate),
|
|
771
|
+
settings({ memoryMode: "smart" }),
|
|
772
|
+
config(),
|
|
773
|
+
{ days: 2 },
|
|
774
|
+
deps,
|
|
775
|
+
);
|
|
776
|
+
assert.equal(summary.memoriesCreated, 2);
|
|
777
|
+
assert.equal(memoryWrites[0].options.status, "pending_review", "day 1 borderline stays in review");
|
|
778
|
+
assert.equal(memoryWrites[1].options.status, "active", "day 2 lifted by day 1's write");
|
|
779
|
+
const attrs = memoryWrites[1].options.structuredAttributes as Record<string, string>;
|
|
780
|
+
assert.equal(attrs.supportingMemoryId, "mem-1");
|
|
781
|
+
} finally {
|
|
782
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("a second device's day write invalidates the first device's memory-pass completion", async () => {
|
|
787
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
788
|
+
try {
|
|
789
|
+
const day = "2026-06-11";
|
|
790
|
+
const conversationFor = (source: string) =>
|
|
791
|
+
makeConversation(`${source}-c1`, day, [
|
|
792
|
+
{ speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
|
|
793
|
+
{ speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
|
|
794
|
+
]);
|
|
795
|
+
const { deps } = makeDeps(memoryDir);
|
|
796
|
+
|
|
797
|
+
// Device A syncs first and completes its memory pass for the day.
|
|
798
|
+
const connectorA = { ...fakeConnector({ [day]: [conversationFor("a")] }), id: "sourcea" };
|
|
799
|
+
await syncWearableSource(connectorA, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
|
|
800
|
+
let state = await loadSyncState(memoryDir);
|
|
801
|
+
assert.ok(state.sources.sourcea.memoryDayHashes?.[day], "A's pass recorded");
|
|
802
|
+
|
|
803
|
+
// Device B then writes a transcript for the same day: A's
|
|
804
|
+
// completion record for that day must be invalidated so A's next
|
|
805
|
+
// sync re-scores with B's evidence available.
|
|
806
|
+
const connectorB = { ...fakeConnector({ [day]: [conversationFor("b")] }), id: "sourceb" };
|
|
807
|
+
await syncWearableSource(connectorB, settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
|
|
808
|
+
state = await loadSyncState(memoryDir);
|
|
809
|
+
assert.equal(
|
|
810
|
+
state.sources.sourcea.memoryDayHashes?.[day],
|
|
811
|
+
undefined,
|
|
812
|
+
"A's completion cleared by B's new same-day evidence",
|
|
813
|
+
);
|
|
814
|
+
assert.ok(state.sources.sourceb.memoryDayHashes?.[day], "B's own pass recorded");
|
|
815
|
+
} finally {
|
|
816
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("a promotion-only re-pass still fires the reindex hook", async () => {
|
|
821
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
822
|
+
try {
|
|
823
|
+
const day = "2026-06-11";
|
|
824
|
+
const byDate = {
|
|
825
|
+
[day]: [
|
|
826
|
+
makeConversation("c1", day, [
|
|
827
|
+
{ speaker: "user", isWearer: true, text: "We are moving the launch to September twelfth after the vendor call today." },
|
|
828
|
+
{ speaker: "Speaker 2", text: "The vendor confirmed the September launch date on the call." },
|
|
829
|
+
]),
|
|
830
|
+
],
|
|
831
|
+
};
|
|
832
|
+
const { deps, reindexes, memoryWrites } = makeDeps(memoryDir);
|
|
833
|
+
assert.ok(deps.memoryGen);
|
|
834
|
+
const borderline = "The launch moved to September twelfth after the vendor call.";
|
|
835
|
+
deps.memoryGen.extract = async () => ({
|
|
836
|
+
facts: [{ category: "fact", content: borderline, confidence: 0.75, tags: [] }],
|
|
837
|
+
profileUpdates: [],
|
|
838
|
+
entities: [],
|
|
839
|
+
questions: [],
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Sync 1: borderline fact lands in review. Reindex fires (a memory
|
|
843
|
+
// was created).
|
|
844
|
+
await syncWearableSource(fakeConnector(byDate), settings({ memoryMode: "smart" }), config(), { days: 1 }, deps);
|
|
845
|
+
assert.equal(memoryWrites[0].options.status, "pending_review");
|
|
846
|
+
const reindexesAfterFirst = reindexes.count;
|
|
847
|
+
|
|
848
|
+
// Another source's same-day transcript arrives, then sync 1's
|
|
849
|
+
// source re-passes: unchanged transcript, duplicate fact — but now
|
|
850
|
+
// corroborated, so it PROMOTES. The reindex hook must still fire.
|
|
851
|
+
const written: string[] = [];
|
|
852
|
+
deps.memoryGen.writer.findWearableMemoryByContent = async (content) =>
|
|
853
|
+
content.trim() === borderline ? { id: "mem-1", status: "pending_review" } : null;
|
|
854
|
+
deps.memoryGen.writer.promoteWearableMemory = async (id) => {
|
|
855
|
+
written.push(id);
|
|
856
|
+
return true;
|
|
857
|
+
};
|
|
858
|
+
deps.memoryGen.writer.hasFactContentHash = async (content) =>
|
|
859
|
+
content.trim() === borderline;
|
|
860
|
+
deps.readOtherSourceDayBodies = async () =>
|
|
861
|
+
new Map([["bee", "They said the launch moves to September twelfth right after that vendor call wrapped."]]);
|
|
862
|
+
// Clear the completion record the way a sibling-source write would.
|
|
863
|
+
const { loadSyncState: load, saveSyncState: save } = await import("./sync-state.js");
|
|
864
|
+
const state = await load(memoryDir);
|
|
865
|
+
delete state.sources.testsource.memoryDayHashes?.[day];
|
|
866
|
+
await save(memoryDir, state);
|
|
867
|
+
|
|
868
|
+
const second = await syncWearableSource(
|
|
869
|
+
fakeConnector(byDate),
|
|
870
|
+
settings({ memoryMode: "smart" }),
|
|
871
|
+
config(),
|
|
872
|
+
{ days: 1 },
|
|
873
|
+
deps,
|
|
874
|
+
);
|
|
875
|
+
assert.equal(second.transcriptsWritten.length, 0, "no transcript write");
|
|
876
|
+
assert.equal(second.memoriesPromoted, 1, "promotion happened");
|
|
877
|
+
assert.deepEqual(written, ["mem-1"]);
|
|
878
|
+
assert.ok(
|
|
879
|
+
reindexes.count > reindexesAfterFirst,
|
|
880
|
+
"reindex fired for the promotion-only run",
|
|
881
|
+
);
|
|
882
|
+
} finally {
|
|
883
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
579
887
|
test("a transcript write failure prevents the sync watermark from advancing", async () => {
|
|
580
888
|
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
581
889
|
try {
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
writeDailyDigestMemory,
|
|
37
37
|
type WearableMemoryGenDeps,
|
|
38
38
|
} from "./memory-gen.js";
|
|
39
|
+
import { tokenizeDayBody, type CorroborationContext } from "./trust.js";
|
|
39
40
|
import { applyOffTheRecord, compileRedactionPatterns, redactText } from "./redaction.js";
|
|
40
41
|
import { loadSpeakerRegistry } from "./speakers.js";
|
|
41
42
|
import {
|
|
@@ -80,14 +81,36 @@ export interface WearableSyncDeps {
|
|
|
80
81
|
date: string,
|
|
81
82
|
serialized: string,
|
|
82
83
|
): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Optional hook fired once after the sync wrote ANYTHING the search
|
|
86
|
+
* index should see: day transcripts, created memories, in-place
|
|
87
|
+
* promotions, or native imports. (A cross-source invalidation can
|
|
88
|
+
* promote memories on a run with zero transcript writes — the index
|
|
89
|
+
* must still refresh.)
|
|
90
|
+
*/
|
|
91
|
+
afterWrites?(): Promise<void>;
|
|
85
92
|
/**
|
|
86
93
|
* Memory-generation dependencies, or null when no extraction engine
|
|
87
94
|
* is available in this context (transcripts still sync; memory
|
|
88
95
|
* creation is skipped with a warning when the mode wanted it).
|
|
89
96
|
*/
|
|
90
97
|
memoryGen: WearableMemoryGenDeps | null;
|
|
98
|
+
/**
|
|
99
|
+
* Same-day transcript bodies from OTHER sources, for cross-device
|
|
100
|
+
* corroboration in smart mode. Absent disables the boost.
|
|
101
|
+
*/
|
|
102
|
+
readOtherSourceDayBodies?(
|
|
103
|
+
date: string,
|
|
104
|
+
excludeSource: string,
|
|
105
|
+
): Promise<Map<string, string>>;
|
|
106
|
+
/**
|
|
107
|
+
* Existing memories usable as support evidence: status "active" or
|
|
108
|
+
* "pending_review" (explicit allow-list — a borderline fact observed
|
|
109
|
+
* again on a later day is repetition signal, and the +0.10 boost is
|
|
110
|
+
* how it earns promotion). Rejected/quarantined/superseded/archived/
|
|
111
|
+
* forgotten rows are never support evidence.
|
|
112
|
+
*/
|
|
113
|
+
listSupportMemories?(): Promise<Array<{ id: string; content: string }>>;
|
|
91
114
|
/** Clock injection for tests. */
|
|
92
115
|
now?: () => Date;
|
|
93
116
|
}
|
|
@@ -276,6 +299,8 @@ export async function syncWearableSource(
|
|
|
276
299
|
correctionsApplied: 0,
|
|
277
300
|
transcriptsWritten: [],
|
|
278
301
|
memoriesCreated: 0,
|
|
302
|
+
memoriesPromoted: 0,
|
|
303
|
+
memoriesDemoted: 0,
|
|
279
304
|
memoriesSkipped: 0,
|
|
280
305
|
nativeMemoriesImported: 0,
|
|
281
306
|
warnings: [],
|
|
@@ -380,6 +405,9 @@ export async function syncWearableSource(
|
|
|
380
405
|
|
|
381
406
|
if (allElided) continue;
|
|
382
407
|
|
|
408
|
+
const needsSmartContext =
|
|
409
|
+
settings.memoryMode === "smart" || settings.importNativeMemories === "smart";
|
|
410
|
+
|
|
383
411
|
// The memory pass runs when the day changed, when forced, or when
|
|
384
412
|
// the last pass for this exact content didn't complete cleanly —
|
|
385
413
|
// a sync that stored the transcript but failed mid-memory-write
|
|
@@ -406,18 +434,31 @@ export async function syncWearableSource(
|
|
|
406
434
|
// re-runs the day.
|
|
407
435
|
let passClean = false;
|
|
408
436
|
try {
|
|
437
|
+
const corroboration = needsSmartContext
|
|
438
|
+
? await buildCorroborationContext(connector.id, date, deps)
|
|
439
|
+
: undefined;
|
|
440
|
+
const dayMemoryGen: WearableMemoryGenDeps = {
|
|
441
|
+
...deps.memoryGen,
|
|
442
|
+
...(corroboration !== undefined ? { corroboration } : {}),
|
|
443
|
+
};
|
|
409
444
|
const generated = await generateWearableMemories(
|
|
410
445
|
connector.id,
|
|
411
446
|
date,
|
|
412
447
|
cleaned.conversations,
|
|
413
448
|
settings,
|
|
414
449
|
registry,
|
|
415
|
-
|
|
450
|
+
dayMemoryGen,
|
|
416
451
|
);
|
|
417
452
|
summary.memoriesCreated += generated.created;
|
|
453
|
+
summary.memoriesPromoted += generated.promoted;
|
|
454
|
+
summary.memoriesDemoted += generated.demoted;
|
|
418
455
|
summary.memoriesSkipped += generated.skipped;
|
|
419
456
|
summary.warnings.push(...generated.warnings);
|
|
420
|
-
|
|
457
|
+
// Degraded-but-complete passes (e.g. judge unavailable) still
|
|
458
|
+
// record completion — only an aborted extraction should force
|
|
459
|
+
// the day to re-run on the next sync (Cursor review on PR
|
|
460
|
+
// #1462).
|
|
461
|
+
passClean = generated.completed;
|
|
421
462
|
if (config.digestEnabled) {
|
|
422
463
|
const wrote = await writeDailyDigestMemory(
|
|
423
464
|
connector.id,
|
|
@@ -448,7 +489,7 @@ export async function syncWearableSource(
|
|
|
448
489
|
}
|
|
449
490
|
|
|
450
491
|
if (
|
|
451
|
-
settings.importNativeMemories
|
|
492
|
+
settings.importNativeMemories !== "off" &&
|
|
452
493
|
typeof connector.fetchNativeMemories === "function"
|
|
453
494
|
) {
|
|
454
495
|
if (!deps.memoryGen) {
|
|
@@ -459,6 +500,26 @@ export async function syncWearableSource(
|
|
|
459
500
|
const alreadyImported = new Set(
|
|
460
501
|
previousState?.importedNativeMemoryIds ?? [],
|
|
461
502
|
);
|
|
503
|
+
// Native memories carry no day, so same-day cross-device
|
|
504
|
+
// corroboration does not apply to them — scoring a provider fact
|
|
505
|
+
// against an arbitrary day's tokens would be wrong-day evidence
|
|
506
|
+
// (Cursor review on PR #1462). They keep only the day-independent
|
|
507
|
+
// existing-memory support boost.
|
|
508
|
+
const nativeCorroboration =
|
|
509
|
+
settings.importNativeMemories === "smart"
|
|
510
|
+
? {
|
|
511
|
+
otherSourceDayTokens: new Map<string, Set<string>>(),
|
|
512
|
+
existingMemories: deps.listSupportMemories
|
|
513
|
+
? await deps.listSupportMemories()
|
|
514
|
+
: [],
|
|
515
|
+
}
|
|
516
|
+
: undefined;
|
|
517
|
+
const nativeMemoryGen: WearableMemoryGenDeps = {
|
|
518
|
+
...deps.memoryGen,
|
|
519
|
+
...(nativeCorroboration !== undefined
|
|
520
|
+
? { corroboration: nativeCorroboration }
|
|
521
|
+
: {}),
|
|
522
|
+
};
|
|
462
523
|
let cursor: string | null | undefined = undefined;
|
|
463
524
|
for (let page = 0; page < MAX_NATIVE_PAGES; page++) {
|
|
464
525
|
const result = await connector.fetchNativeMemories({
|
|
@@ -469,8 +530,10 @@ export async function syncWearableSource(
|
|
|
469
530
|
connector.id,
|
|
470
531
|
result.memories,
|
|
471
532
|
alreadyImported,
|
|
472
|
-
|
|
533
|
+
settings,
|
|
534
|
+
nativeMemoryGen,
|
|
473
535
|
);
|
|
536
|
+
summary.warnings.push(...imported.warnings);
|
|
474
537
|
summary.nativeMemoriesImported += imported.imported;
|
|
475
538
|
importedNativeIds.push(...imported.importedIds);
|
|
476
539
|
for (const id of imported.importedIds) alreadyImported.add(id);
|
|
@@ -485,12 +548,18 @@ export async function syncWearableSource(
|
|
|
485
548
|
}
|
|
486
549
|
}
|
|
487
550
|
|
|
488
|
-
|
|
551
|
+
const wroteAnything =
|
|
552
|
+
summary.transcriptsWritten.length > 0 ||
|
|
553
|
+
summary.memoriesCreated > 0 ||
|
|
554
|
+
summary.memoriesPromoted > 0 ||
|
|
555
|
+
summary.memoriesDemoted > 0 ||
|
|
556
|
+
summary.nativeMemoriesImported > 0;
|
|
557
|
+
if (wroteAnything && deps.afterWrites) {
|
|
489
558
|
try {
|
|
490
|
-
await deps.
|
|
559
|
+
await deps.afterWrites();
|
|
491
560
|
} catch (err) {
|
|
492
561
|
summary.warnings.push(
|
|
493
|
-
`search reindex failed (
|
|
562
|
+
`search reindex failed (writes are stored and will index on the next update): ${describeErrorForOperator(err)}`,
|
|
494
563
|
);
|
|
495
564
|
}
|
|
496
565
|
}
|
|
@@ -505,11 +574,64 @@ export async function syncWearableSource(
|
|
|
505
574
|
clearMemoryDays: failedMemoryDays,
|
|
506
575
|
importedNativeMemoryIds: importedNativeIds,
|
|
507
576
|
});
|
|
577
|
+
// New same-day evidence invalidates OTHER sources' memory-pass
|
|
578
|
+
// completion for the days this source just (re)wrote: their next
|
|
579
|
+
// sync re-scores with this transcript available, and the promotion
|
|
580
|
+
// path upgrades earlier borderline writes in place (Cursor review on
|
|
581
|
+
// PR #1462).
|
|
582
|
+
if (summary.transcriptsWritten.length > 0) {
|
|
583
|
+
const cleared: typeof syncState.sources = {};
|
|
584
|
+
for (const [otherId, otherState] of Object.entries(syncState.sources)) {
|
|
585
|
+
if (otherId === connector.id || otherState.memoryDayHashes === undefined) {
|
|
586
|
+
cleared[otherId] = otherState;
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const memoryDays = { ...otherState.memoryDayHashes };
|
|
590
|
+
let touched = false;
|
|
591
|
+
for (const date of summary.transcriptsWritten) {
|
|
592
|
+
if (date in memoryDays) {
|
|
593
|
+
delete memoryDays[date];
|
|
594
|
+
touched = true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
cleared[otherId] = touched
|
|
598
|
+
? { ...otherState, memoryDayHashes: memoryDays }
|
|
599
|
+
: otherState;
|
|
600
|
+
}
|
|
601
|
+
syncState = { version: 1, sources: cleared };
|
|
602
|
+
}
|
|
508
603
|
await saveSyncState(deps.memoryDir, syncState);
|
|
509
604
|
|
|
510
605
|
return summary;
|
|
511
606
|
}
|
|
512
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Assemble smart-mode corroboration evidence: other sources' same-day
|
|
610
|
+
* transcript tokens + existing active memories. The memory list loads
|
|
611
|
+
* fresh per day (not per run) so facts written on earlier days of a
|
|
612
|
+
* multi-day backfill are visible as support evidence on later days —
|
|
613
|
+
* the underlying readAllMemories is cached in storage and invalidated
|
|
614
|
+
* by writes, so the per-day refresh is cheap (Cursor review on PR
|
|
615
|
+
* #1462).
|
|
616
|
+
*/
|
|
617
|
+
async function buildCorroborationContext(
|
|
618
|
+
sourceId: string,
|
|
619
|
+
date: string,
|
|
620
|
+
deps: WearableSyncDeps,
|
|
621
|
+
): Promise<CorroborationContext> {
|
|
622
|
+
const otherSourceDayTokens = new Map<string, Set<string>>();
|
|
623
|
+
if (deps.readOtherSourceDayBodies) {
|
|
624
|
+
const bodies = await deps.readOtherSourceDayBodies(date, sourceId);
|
|
625
|
+
for (const [otherSource, body] of bodies) {
|
|
626
|
+
otherSourceDayTokens.set(otherSource, tokenizeDayBody(body));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const existingMemories = deps.listSupportMemories
|
|
630
|
+
? await deps.listSupportMemories()
|
|
631
|
+
: [];
|
|
632
|
+
return { otherSourceDayTokens, existingMemories };
|
|
633
|
+
}
|
|
634
|
+
|
|
513
635
|
export function defaultTimezone(): string {
|
|
514
636
|
try {
|
|
515
637
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|