@remnic/core 9.3.623 → 9.3.625
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 -16
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +12 -5
- package/dist/access-http.js +10 -9
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +8 -8
- package/dist/access-schema.d.ts +5 -5
- package/dist/{access-service-CBNEKjzN.d.ts → access-service-C_sfOHsX.d.ts} +26 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +7 -7
- 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 +2 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- 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/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-C4PZTWTG.js → chunk-2RHI3FGV.js} +540 -17
- package/dist/chunk-2RHI3FGV.js.map +1 -0
- package/dist/{chunk-GYTVOLNX.js → chunk-3MNBW7R7.js} +2 -2
- package/dist/{chunk-QFQQFX2H.js → chunk-3R2UZV3U.js} +2 -2
- package/dist/{chunk-O4UNM6OR.js → chunk-532VCWYW.js} +2 -2
- package/dist/{chunk-2UFQYU5F.js → chunk-57QXN2CS.js} +2 -2
- package/dist/chunk-7WV3F5DQ.js +22 -0
- package/dist/chunk-7WV3F5DQ.js.map +1 -0
- package/dist/{chunk-RKW6QR7W.js → chunk-AZ4RI3QD.js} +1461 -78
- package/dist/chunk-AZ4RI3QD.js.map +1 -0
- package/dist/{chunk-KQFQ3IS5.js → chunk-F3FY3D3S.js} +43 -7
- package/dist/chunk-F3FY3D3S.js.map +1 -0
- package/dist/{chunk-4R4KTDIE.js → chunk-FPNQF475.js} +1 -1
- package/dist/chunk-FPNQF475.js.map +1 -0
- package/dist/{chunk-UGEBPVNI.js → chunk-GE7Q7KXP.js} +2 -2
- package/dist/{chunk-GLWW3EJQ.js → chunk-KB4MFBF5.js} +3 -3
- package/dist/{chunk-5GOMXHLC.js → chunk-KKTXCFD7.js} +255 -1
- package/dist/chunk-KKTXCFD7.js.map +1 -0
- package/dist/{chunk-FH3PPO42.js → chunk-KVFYTRMV.js} +2 -2
- package/dist/{chunk-BNW5NJJH.js → chunk-LQYTQCXM.js} +2 -2
- package/dist/{chunk-AYHXQR53.js → chunk-MVQN73GT.js} +2 -2
- package/dist/{chunk-ZZPIJPPD.js → chunk-N5RGXWLQ.js} +2 -2
- package/dist/chunk-NDAH7BJ5.js +213 -0
- package/dist/chunk-NDAH7BJ5.js.map +1 -0
- package/dist/{chunk-R3OQGYOU.js → chunk-P2D2MM47.js} +2 -2
- package/dist/{chunk-PSUB67YB.js → chunk-PW6GURU3.js} +2 -2
- package/dist/{chunk-W3BKVM64.js → chunk-QDV6VAD4.js} +2 -2
- package/dist/{chunk-3QSU4NFF.js → chunk-QHXW3LZV.js} +3 -3
- package/dist/{chunk-I6UCUHLK.js → chunk-SHV5Y2WU.js} +182 -3
- package/dist/chunk-SHV5Y2WU.js.map +1 -0
- package/dist/{chunk-OZXVGYGZ.js → chunk-STDAAGH7.js} +2 -2
- package/dist/{chunk-FMGWXIES.js → chunk-TZDSNIRO.js} +5 -5
- package/dist/{chunk-2L54V4ZO.js → chunk-UELS6WWF.js} +2 -2
- package/dist/{chunk-PJGB7XRR.js → chunk-UGHUNQ74.js} +502 -134
- package/dist/chunk-UGHUNQ74.js.map +1 -0
- package/dist/{chunk-ZJSZNTEI.js → chunk-Y3TMFC6I.js} +140 -10
- package/dist/chunk-Y3TMFC6I.js.map +1 -0
- package/dist/{chunk-BPSGLMQ4.js → chunk-YQNADJCT.js} +2 -2
- package/dist/{cli-Cw729yLf.d.ts → cli-EZv6YE6_.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +23 -21
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- 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 +2 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- 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 +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -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.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +307 -9
- package/dist/index.js +155 -29
- 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 +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- 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 +4 -4
- 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 +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +8 -7
- package/dist/{orchestrator-CqWOjfgl.d.ts → orchestrator-CEycaY3M.d.ts} +361 -4
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +13 -11
- 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-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- 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-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -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 +32 -32
- 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-SLAa_prH.d.ts → semantic-DJR8_DMQ.d.ts} +1 -1
- package/dist/{semantic-consolidation-4HkHWgeI.d.ts → semantic-consolidation-FbhPeJjB.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- 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 +5 -5
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +19 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- 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 +3134 -0
- package/dist/types.d.ts +3 -2862
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +1 -1
- package/src/access-http.ts +182 -8
- package/src/access-mcp.ts +198 -0
- package/src/access-service.ts +65 -0
- package/src/cli.ts +187 -0
- package/src/config.ts +7 -0
- package/src/index.ts +7 -0
- package/src/orchestrator.ts +42 -0
- package/src/storage.ts +106 -0
- package/src/types.ts +5 -0
- package/src/wearables/cleanup.test.ts +134 -0
- package/src/wearables/cleanup.ts +188 -0
- package/src/wearables/cli.test.ts +170 -0
- package/src/wearables/cli.ts +441 -0
- package/src/wearables/config.test.ts +143 -0
- package/src/wearables/config.ts +332 -0
- package/src/wearables/corrections.test.ts +118 -0
- package/src/wearables/corrections.ts +211 -0
- package/src/wearables/day-store.test.ts +143 -0
- package/src/wearables/day-store.ts +238 -0
- package/src/wearables/errors.test.ts +32 -0
- package/src/wearables/errors.ts +29 -0
- package/src/wearables/index.ts +114 -0
- package/src/wearables/memory-gen.test.ts +342 -0
- package/src/wearables/memory-gen.ts +413 -0
- package/src/wearables/pipeline.test.ts +608 -0
- package/src/wearables/pipeline.ts +519 -0
- package/src/wearables/redaction.test.ts +94 -0
- package/src/wearables/redaction.ts +156 -0
- package/src/wearables/registry.test.ts +62 -0
- package/src/wearables/registry.ts +133 -0
- package/src/wearables/service.test.ts +425 -0
- package/src/wearables/service.ts +691 -0
- package/src/wearables/speakers.test.ts +110 -0
- package/src/wearables/speakers.ts +174 -0
- package/src/wearables/storage-io.test.ts +105 -0
- package/src/wearables/sync-state.test.ts +134 -0
- package/src/wearables/sync-state.ts +186 -0
- package/src/wearables/types.ts +285 -0
- package/dist/chunk-4R4KTDIE.js.map +0 -1
- package/dist/chunk-5GOMXHLC.js.map +0 -1
- package/dist/chunk-C4PZTWTG.js.map +0 -1
- package/dist/chunk-I6UCUHLK.js.map +0 -1
- package/dist/chunk-KQFQ3IS5.js.map +0 -1
- package/dist/chunk-PJGB7XRR.js.map +0 -1
- package/dist/chunk-RKW6QR7W.js.map +0 -1
- package/dist/chunk-ZJSZNTEI.js.map +0 -1
- /package/dist/{chunk-GYTVOLNX.js.map → chunk-3MNBW7R7.js.map} +0 -0
- /package/dist/{chunk-QFQQFX2H.js.map → chunk-3R2UZV3U.js.map} +0 -0
- /package/dist/{chunk-O4UNM6OR.js.map → chunk-532VCWYW.js.map} +0 -0
- /package/dist/{chunk-2UFQYU5F.js.map → chunk-57QXN2CS.js.map} +0 -0
- /package/dist/{chunk-UGEBPVNI.js.map → chunk-GE7Q7KXP.js.map} +0 -0
- /package/dist/{chunk-GLWW3EJQ.js.map → chunk-KB4MFBF5.js.map} +0 -0
- /package/dist/{chunk-FH3PPO42.js.map → chunk-KVFYTRMV.js.map} +0 -0
- /package/dist/{chunk-BNW5NJJH.js.map → chunk-LQYTQCXM.js.map} +0 -0
- /package/dist/{chunk-AYHXQR53.js.map → chunk-MVQN73GT.js.map} +0 -0
- /package/dist/{chunk-ZZPIJPPD.js.map → chunk-N5RGXWLQ.js.map} +0 -0
- /package/dist/{chunk-R3OQGYOU.js.map → chunk-P2D2MM47.js.map} +0 -0
- /package/dist/{chunk-PSUB67YB.js.map → chunk-PW6GURU3.js.map} +0 -0
- /package/dist/{chunk-W3BKVM64.js.map → chunk-QDV6VAD4.js.map} +0 -0
- /package/dist/{chunk-3QSU4NFF.js.map → chunk-QHXW3LZV.js.map} +0 -0
- /package/dist/{chunk-OZXVGYGZ.js.map → chunk-STDAAGH7.js.map} +0 -0
- /package/dist/{chunk-FMGWXIES.js.map → chunk-TZDSNIRO.js.map} +0 -0
- /package/dist/{chunk-2L54V4ZO.js.map → chunk-UELS6WWF.js.map} +0 -0
- /package/dist/{chunk-BPSGLMQ4.js.map → chunk-YQNADJCT.js.map} +0 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
|
|
7
|
+
import type { ExtractionResult } from "../types.js";
|
|
8
|
+
import { hashTranscriptBody, parseDayTranscript } from "./day-store.js";
|
|
9
|
+
import { saveCorrectionsFile } from "./corrections.js";
|
|
10
|
+
import type { WearableMemoryWriter } from "./memory-gen.js";
|
|
11
|
+
import {
|
|
12
|
+
dateInTimezone,
|
|
13
|
+
resolveSyncDates,
|
|
14
|
+
syncWearableSource,
|
|
15
|
+
type WearableSyncDeps,
|
|
16
|
+
} from "./pipeline.js";
|
|
17
|
+
import { loadSyncState } from "./sync-state.js";
|
|
18
|
+
import type {
|
|
19
|
+
WearableConversation,
|
|
20
|
+
WearableSourceConnector,
|
|
21
|
+
WearableSourceSettings,
|
|
22
|
+
WearablesConfig,
|
|
23
|
+
} from "./types.js";
|
|
24
|
+
import { defaultWearableSourceSettings, defaultWearablesConfig } from "./config.js";
|
|
25
|
+
|
|
26
|
+
const NOW = new Date("2026-06-11T03:00:00.000Z");
|
|
27
|
+
|
|
28
|
+
function settings(overrides: Partial<WearableSourceSettings> = {}): WearableSourceSettings {
|
|
29
|
+
return { ...defaultWearableSourceSettings(), enabled: true, ...overrides };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function config(overrides: Partial<WearablesConfig> = {}): WearablesConfig {
|
|
33
|
+
return {
|
|
34
|
+
...defaultWearablesConfig(),
|
|
35
|
+
enabled: true,
|
|
36
|
+
timezone: "UTC",
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeConversation(
|
|
42
|
+
id: string,
|
|
43
|
+
date: string,
|
|
44
|
+
texts: Array<{ speaker: string; text: string; isWearer?: boolean }>,
|
|
45
|
+
): WearableConversation {
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
source: "testsource",
|
|
49
|
+
title: `Conversation ${id}`,
|
|
50
|
+
startIso: `${date}T15:00:00.000Z`,
|
|
51
|
+
endIso: `${date}T15:30:00.000Z`,
|
|
52
|
+
segments: texts.map((entry) => ({
|
|
53
|
+
speakerKey: entry.speaker,
|
|
54
|
+
speakerName: entry.speaker,
|
|
55
|
+
isWearer: entry.isWearer,
|
|
56
|
+
text: entry.text,
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fakeConnector(
|
|
62
|
+
byDate: Record<string, WearableConversation[]>,
|
|
63
|
+
nativeMemories: Array<{ id: string; content: string }> = [],
|
|
64
|
+
): WearableSourceConnector & { fetchCount: number } {
|
|
65
|
+
const connector = {
|
|
66
|
+
id: "testsource",
|
|
67
|
+
displayName: "Test Source",
|
|
68
|
+
fetchCount: 0,
|
|
69
|
+
async verifyAuth() {
|
|
70
|
+
return { ok: true };
|
|
71
|
+
},
|
|
72
|
+
async fetchConversations(opts: { date: string }) {
|
|
73
|
+
connector.fetchCount += 1;
|
|
74
|
+
return {
|
|
75
|
+
conversations: byDate[opts.date] ?? [],
|
|
76
|
+
nextCursor: null,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
async fetchNativeMemories() {
|
|
80
|
+
return { memories: nativeMemories, nextCursor: null };
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return connector;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface DayWrite {
|
|
87
|
+
source: string;
|
|
88
|
+
date: string;
|
|
89
|
+
serialized: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function makeDeps(memoryDir: string): {
|
|
93
|
+
deps: WearableSyncDeps;
|
|
94
|
+
written: DayWrite[];
|
|
95
|
+
reindexes: { count: number };
|
|
96
|
+
memoryWrites: Array<{ category: string; content: string; options: Record<string, unknown> }>;
|
|
97
|
+
} {
|
|
98
|
+
const written: DayWrite[] = [];
|
|
99
|
+
const reindexes = { count: 0 };
|
|
100
|
+
const memoryWrites: Array<{ category: string; content: string; options: Record<string, unknown> }> = [];
|
|
101
|
+
const files = new Map<string, string>();
|
|
102
|
+
const writer: WearableMemoryWriter = {
|
|
103
|
+
async writeMemory(category, content, options) {
|
|
104
|
+
memoryWrites.push({ category, content, options: options as Record<string, unknown> });
|
|
105
|
+
return `mem-${memoryWrites.length}`;
|
|
106
|
+
},
|
|
107
|
+
async hasFactContentHash() {
|
|
108
|
+
return false;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const deps: WearableSyncDeps = {
|
|
112
|
+
memoryDir,
|
|
113
|
+
async readDayContentHash(sourceId, date) {
|
|
114
|
+
const raw = files.get(`${sourceId}/${date}`);
|
|
115
|
+
if (raw === undefined) return null;
|
|
116
|
+
return parseDayTranscript(raw)?.meta.contentHash ?? null;
|
|
117
|
+
},
|
|
118
|
+
async writeDayTranscript(sourceId, date, serialized) {
|
|
119
|
+
files.set(`${sourceId}/${date}`, serialized);
|
|
120
|
+
written.push({ source: sourceId, date, serialized });
|
|
121
|
+
},
|
|
122
|
+
async afterTranscriptsWritten() {
|
|
123
|
+
reindexes.count += 1;
|
|
124
|
+
},
|
|
125
|
+
memoryGen: {
|
|
126
|
+
extract: async (): Promise<ExtractionResult> => ({
|
|
127
|
+
facts: [
|
|
128
|
+
{
|
|
129
|
+
category: "fact",
|
|
130
|
+
content: "The launch moved to September twelfth per the planning chat.",
|
|
131
|
+
confidence: 0.9,
|
|
132
|
+
tags: [],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
profileUpdates: [],
|
|
136
|
+
entities: [],
|
|
137
|
+
questions: [],
|
|
138
|
+
}),
|
|
139
|
+
writer,
|
|
140
|
+
},
|
|
141
|
+
now: () => NOW,
|
|
142
|
+
};
|
|
143
|
+
return { deps, written, reindexes, memoryWrites };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
test("dateInTimezone formats correctly across timezones", () => {
|
|
147
|
+
const instant = new Date("2026-06-11T03:00:00.000Z");
|
|
148
|
+
assert.equal(dateInTimezone(instant, "UTC"), "2026-06-11");
|
|
149
|
+
// 03:00 UTC is still the previous day in Chicago (UTC-5 in June).
|
|
150
|
+
assert.equal(dateInTimezone(instant, "America/Chicago"), "2026-06-10");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("resolveSyncDates validates input and builds lookback windows", () => {
|
|
154
|
+
assert.deepEqual(resolveSyncDates({ date: "2026-06-01" }, "UTC", NOW), ["2026-06-01"]);
|
|
155
|
+
assert.deepEqual(resolveSyncDates({}, "UTC", NOW), ["2026-06-10", "2026-06-11"]);
|
|
156
|
+
assert.deepEqual(resolveSyncDates({ days: 1 }, "UTC", NOW), ["2026-06-11"]);
|
|
157
|
+
assert.throws(() => resolveSyncDates({ date: "junk" }, "UTC", NOW), /invalid date/);
|
|
158
|
+
assert.throws(() => resolveSyncDates({ days: 0 }, "UTC", NOW), /invalid days/);
|
|
159
|
+
assert.throws(() => resolveSyncDates({ days: 9000 }, "UTC", NOW), /invalid days/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("sync windows walk back by local calendar days across DST transitions", () => {
|
|
163
|
+
// 2026-03-09T07:30Z is 00:30 PDT on Mar 9, just after spring-forward
|
|
164
|
+
// (Mar 8). Fixed 24h subtraction would land on Mar 7 and skip Mar 8.
|
|
165
|
+
const springForward = new Date("2026-03-09T07:30:00.000Z");
|
|
166
|
+
assert.deepEqual(
|
|
167
|
+
resolveSyncDates({ days: 3 }, "America/Los_Angeles", springForward),
|
|
168
|
+
["2026-03-07", "2026-03-08", "2026-03-09"],
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a fact-write failure warns and retries instead of aborting the sync", async () => {
|
|
173
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
174
|
+
try {
|
|
175
|
+
const byDate = {
|
|
176
|
+
"2026-06-11": [
|
|
177
|
+
makeConversation("c1", "2026-06-11", [
|
|
178
|
+
{ speaker: "user", isWearer: true, text: "A durable fact about the new vendor agreement emerged." },
|
|
179
|
+
{ speaker: "Speaker 2", text: "The vendor agreement now covers support through next year." },
|
|
180
|
+
]),
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
184
|
+
assert.ok(deps.memoryGen);
|
|
185
|
+
const healthyWrite = deps.memoryGen.writer.writeMemory;
|
|
186
|
+
deps.memoryGen.writer.writeMemory = async () => {
|
|
187
|
+
throw new Error("storage write exploded");
|
|
188
|
+
};
|
|
189
|
+
const first = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
190
|
+
assert.equal(first.transcriptsWritten.length, 1, "transcript still stored");
|
|
191
|
+
assert.ok(
|
|
192
|
+
first.warnings.some((warning) => warning.includes("memory pass failed")),
|
|
193
|
+
`expected memory-pass warning, got: ${first.warnings.join(" | ")}`,
|
|
194
|
+
);
|
|
195
|
+
const state1 = await loadSyncState(memoryDir);
|
|
196
|
+
assert.equal(
|
|
197
|
+
state1.sources.testsource.memoryDayHashes?.["2026-06-11"],
|
|
198
|
+
undefined,
|
|
199
|
+
"no completion record for a failed pass",
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
deps.memoryGen.writer.writeMemory = healthyWrite;
|
|
203
|
+
const second = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
204
|
+
assert.equal(second.memoriesCreated, 1, "retried on the next sync");
|
|
205
|
+
assert.equal(memoryWrites.length, 1);
|
|
206
|
+
} finally {
|
|
207
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("end-to-end sync: cleans, redacts, corrects, stores, extracts, reindexes, records state", async () => {
|
|
212
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
213
|
+
try {
|
|
214
|
+
await saveCorrectionsFile(memoryDir, [{ match: "remnick", replace: "Remnic" }]);
|
|
215
|
+
const connector = fakeConnector({
|
|
216
|
+
"2026-06-11": [
|
|
217
|
+
makeConversation("c1", "2026-06-11", [
|
|
218
|
+
{ speaker: "user", isWearer: true, text: "Um, I I told remnick my card is 4111 1111 1111 1111." },
|
|
219
|
+
{ speaker: "Speaker 2", text: "Got it, noted for the project plan we discussed." },
|
|
220
|
+
{ speaker: "Speaker 2", text: "zzzzzzzzzz" },
|
|
221
|
+
]),
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
const { deps, written, reindexes, memoryWrites } = makeDeps(memoryDir);
|
|
225
|
+
const summary = await syncWearableSource(
|
|
226
|
+
connector,
|
|
227
|
+
settings({ memoryMode: "review" }),
|
|
228
|
+
config(),
|
|
229
|
+
{ days: 1 },
|
|
230
|
+
deps,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
assert.equal(summary.conversations, 1);
|
|
234
|
+
assert.equal(summary.segmentsKept, 2);
|
|
235
|
+
assert.equal(summary.segmentsDropped, 1);
|
|
236
|
+
assert.equal(summary.redactions, 1);
|
|
237
|
+
assert.equal(summary.correctionsApplied, 1);
|
|
238
|
+
assert.deepEqual(summary.transcriptsWritten, ["2026-06-11"]);
|
|
239
|
+
assert.equal(reindexes.count, 1);
|
|
240
|
+
|
|
241
|
+
assert.equal(written.length, 1);
|
|
242
|
+
const parsed = parseDayTranscript(written[0].serialized);
|
|
243
|
+
assert.ok(parsed);
|
|
244
|
+
assert.match(parsed.body, /I told Remnic my card is \[redacted\]\./);
|
|
245
|
+
assert.ok(!parsed.body.includes("4111"), "card number must not be stored");
|
|
246
|
+
assert.ok(!parsed.body.includes("Um,"), "fillers are stripped");
|
|
247
|
+
assert.ok(!parsed.body.includes("I I "), "stutters are collapsed");
|
|
248
|
+
|
|
249
|
+
assert.equal(summary.memoriesCreated, 1);
|
|
250
|
+
assert.equal(memoryWrites[0].options.status, "pending_review");
|
|
251
|
+
|
|
252
|
+
const state = await loadSyncState(memoryDir);
|
|
253
|
+
assert.equal(state.sources.testsource.lastDateSynced, "2026-06-11");
|
|
254
|
+
assert.equal(
|
|
255
|
+
state.sources.testsource.dayHashes["2026-06-11"],
|
|
256
|
+
hashTranscriptBody(parsed.body),
|
|
257
|
+
);
|
|
258
|
+
} finally {
|
|
259
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("unchanged days skip rewrite, reindex, and re-extraction; forceMemories re-extracts", async () => {
|
|
264
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
265
|
+
try {
|
|
266
|
+
const byDate = {
|
|
267
|
+
"2026-06-11": [
|
|
268
|
+
makeConversation("c1", "2026-06-11", [
|
|
269
|
+
{ speaker: "user", isWearer: true, text: "We agreed the offsite happens in Austin this October." },
|
|
270
|
+
{ speaker: "Speaker 2", text: "Austin in October works for the whole team I think." },
|
|
271
|
+
]),
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
const { deps, written, reindexes, memoryWrites } = makeDeps(memoryDir);
|
|
275
|
+
const run = () =>
|
|
276
|
+
syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
277
|
+
|
|
278
|
+
const first = await run();
|
|
279
|
+
assert.equal(first.transcriptsWritten.length, 1);
|
|
280
|
+
assert.equal(first.memoriesCreated, 1);
|
|
281
|
+
|
|
282
|
+
const second = await run();
|
|
283
|
+
assert.equal(second.transcriptsWritten.length, 0, "unchanged day must not rewrite");
|
|
284
|
+
assert.equal(second.memoriesCreated, 0, "unchanged day must not re-extract");
|
|
285
|
+
assert.equal(written.length, 1);
|
|
286
|
+
assert.equal(reindexes.count, 1);
|
|
287
|
+
|
|
288
|
+
const third = await syncWearableSource(
|
|
289
|
+
fakeConnector(byDate),
|
|
290
|
+
settings(),
|
|
291
|
+
config(),
|
|
292
|
+
{ days: 1, forceMemories: true },
|
|
293
|
+
deps,
|
|
294
|
+
);
|
|
295
|
+
assert.equal(third.memoriesCreated, 1, "forceMemories re-runs extraction");
|
|
296
|
+
assert.equal(memoryWrites.length, 2);
|
|
297
|
+
} finally {
|
|
298
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("a deleted day file is recreated even when sync state remembers its hash", async () => {
|
|
303
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
304
|
+
try {
|
|
305
|
+
const byDate = {
|
|
306
|
+
"2026-06-11": [
|
|
307
|
+
makeConversation("c1", "2026-06-11", [
|
|
308
|
+
{ speaker: "user", isWearer: true, text: "A conversation worth keeping on disk for sure." },
|
|
309
|
+
{ speaker: "Speaker 2", text: "Agreed, this transcript should survive resyncs." },
|
|
310
|
+
]),
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
const { deps, written } = makeDeps(memoryDir);
|
|
314
|
+
const filesRef = deps as unknown as { readDayContentHash: WearableSyncDeps["readDayContentHash"] };
|
|
315
|
+
await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
316
|
+
assert.equal(written.length, 1);
|
|
317
|
+
|
|
318
|
+
// Simulate the day file being deleted while sync state still
|
|
319
|
+
// remembers its hash — the file is authoritative, so the next sync
|
|
320
|
+
// must rewrite it (Cursor review on PR #1458).
|
|
321
|
+
filesRef.readDayContentHash = async () => null;
|
|
322
|
+
const summary = await syncWearableSource(
|
|
323
|
+
fakeConnector(byDate),
|
|
324
|
+
settings(),
|
|
325
|
+
config(),
|
|
326
|
+
{ days: 1 },
|
|
327
|
+
deps,
|
|
328
|
+
);
|
|
329
|
+
assert.deepEqual(summary.transcriptsWritten, ["2026-06-11"]);
|
|
330
|
+
assert.equal(written.length, 2);
|
|
331
|
+
} finally {
|
|
332
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("memoryMode wanting extraction without an engine warns instead of failing", async () => {
|
|
337
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
338
|
+
try {
|
|
339
|
+
const { deps } = makeDeps(memoryDir);
|
|
340
|
+
deps.memoryGen = null;
|
|
341
|
+
const summary = await syncWearableSource(
|
|
342
|
+
fakeConnector({
|
|
343
|
+
"2026-06-11": [
|
|
344
|
+
makeConversation("c1", "2026-06-11", [
|
|
345
|
+
{ speaker: "user", isWearer: true, text: "A real conversation about the quarterly numbers happened." },
|
|
346
|
+
{ speaker: "Speaker 2", text: "Yes the numbers looked strong across all three regions." },
|
|
347
|
+
]),
|
|
348
|
+
],
|
|
349
|
+
}),
|
|
350
|
+
settings(),
|
|
351
|
+
config(),
|
|
352
|
+
{ days: 1 },
|
|
353
|
+
deps,
|
|
354
|
+
);
|
|
355
|
+
assert.equal(summary.transcriptsWritten.length, 1, "transcripts still sync");
|
|
356
|
+
assert.equal(summary.memoriesCreated, 0);
|
|
357
|
+
assert.ok(summary.warnings.some((warning) => warning.includes("no extraction engine")));
|
|
358
|
+
} finally {
|
|
359
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("native memories import once and are tracked across syncs", async () => {
|
|
364
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
365
|
+
try {
|
|
366
|
+
const native = [{ id: "nat-1", content: "User volunteers at the food bank monthly." }];
|
|
367
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
368
|
+
const run = () =>
|
|
369
|
+
syncWearableSource(
|
|
370
|
+
fakeConnector({}, native),
|
|
371
|
+
settings({ importNativeMemories: "review" }),
|
|
372
|
+
config(),
|
|
373
|
+
{ days: 1 },
|
|
374
|
+
deps,
|
|
375
|
+
);
|
|
376
|
+
const first = await run();
|
|
377
|
+
assert.equal(first.nativeMemoriesImported, 1);
|
|
378
|
+
assert.equal(memoryWrites.length, 1);
|
|
379
|
+
assert.equal(memoryWrites[0].options.status, "pending_review");
|
|
380
|
+
|
|
381
|
+
const second = await run();
|
|
382
|
+
assert.equal(second.nativeMemoriesImported, 0, "already-imported ids skip");
|
|
383
|
+
assert.equal(memoryWrites.length, 1);
|
|
384
|
+
} finally {
|
|
385
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("a failed memory pass retries on the next sync even when the day is unchanged", async () => {
|
|
390
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
391
|
+
try {
|
|
392
|
+
const byDate = {
|
|
393
|
+
"2026-06-11": [
|
|
394
|
+
makeConversation("c1", "2026-06-11", [
|
|
395
|
+
{ speaker: "user", isWearer: true, text: "An important decision about the vendor contract was made." },
|
|
396
|
+
{ speaker: "Speaker 2", text: "Yes, the vendor contract terms were finalized this afternoon." },
|
|
397
|
+
]),
|
|
398
|
+
],
|
|
399
|
+
};
|
|
400
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
401
|
+
const healthyExtract = deps.memoryGen?.extract;
|
|
402
|
+
assert.ok(deps.memoryGen && healthyExtract);
|
|
403
|
+
|
|
404
|
+
// First sync: transcript stores, but the extraction engine fails.
|
|
405
|
+
deps.memoryGen.extract = async () => {
|
|
406
|
+
throw new Error("provider outage");
|
|
407
|
+
};
|
|
408
|
+
const first = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
409
|
+
assert.equal(first.transcriptsWritten.length, 1);
|
|
410
|
+
assert.equal(first.memoriesCreated, 0);
|
|
411
|
+
assert.ok(first.warnings.some((warning) => warning.includes("extraction failed")));
|
|
412
|
+
|
|
413
|
+
// Second sync: day unchanged, engine healthy — the memory pass must
|
|
414
|
+
// re-run instead of being frozen out by the unchanged-day skip.
|
|
415
|
+
deps.memoryGen.extract = healthyExtract;
|
|
416
|
+
const second = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
417
|
+
assert.equal(second.transcriptsWritten.length, 0, "transcript unchanged");
|
|
418
|
+
assert.equal(second.memoriesCreated, 1, "memory pass retried");
|
|
419
|
+
assert.equal(memoryWrites.length, 1);
|
|
420
|
+
|
|
421
|
+
// Third sync: completion recorded — no further re-extraction.
|
|
422
|
+
const third = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
423
|
+
assert.equal(third.memoriesCreated, 0);
|
|
424
|
+
assert.equal(memoryWrites.length, 1);
|
|
425
|
+
} finally {
|
|
426
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("a stale completion record cannot mask a failed pass on a recreated day file", async () => {
|
|
431
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
432
|
+
try {
|
|
433
|
+
const byDate = {
|
|
434
|
+
"2026-06-11": [
|
|
435
|
+
makeConversation("c1", "2026-06-11", [
|
|
436
|
+
{ speaker: "user", isWearer: true, text: "Decisions about the annual offsite were finalized today." },
|
|
437
|
+
{ speaker: "Speaker 2", text: "The offsite plan is locked for the first week of October." },
|
|
438
|
+
]),
|
|
439
|
+
],
|
|
440
|
+
};
|
|
441
|
+
const { deps, memoryWrites } = makeDeps(memoryDir);
|
|
442
|
+
const healthyExtract = deps.memoryGen?.extract;
|
|
443
|
+
assert.ok(deps.memoryGen && healthyExtract);
|
|
444
|
+
|
|
445
|
+
// Sync 1: clean pass — completion recorded for body hash H.
|
|
446
|
+
await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
447
|
+
assert.equal(memoryWrites.length, 1);
|
|
448
|
+
|
|
449
|
+
// Sync 2: the day file was deleted (changed=true on recreate) and
|
|
450
|
+
// the engine fails — the old completion record for the SAME body
|
|
451
|
+
// hash must be cleared, not carried forward.
|
|
452
|
+
const readBackup = deps.readDayContentHash;
|
|
453
|
+
deps.readDayContentHash = async () => null;
|
|
454
|
+
deps.memoryGen.extract = async () => {
|
|
455
|
+
throw new Error("engine outage");
|
|
456
|
+
};
|
|
457
|
+
const second = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
458
|
+
assert.ok(second.warnings.some((warning) => warning.includes("extraction failed")));
|
|
459
|
+
deps.readDayContentHash = readBackup;
|
|
460
|
+
|
|
461
|
+
// Sync 3: unchanged file, healthy engine — extraction must re-run.
|
|
462
|
+
deps.memoryGen.extract = healthyExtract;
|
|
463
|
+
const third = await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
464
|
+
assert.equal(third.transcriptsWritten.length, 0, "file unchanged");
|
|
465
|
+
assert.equal(third.memoriesCreated, 1, "failed pass retried despite stale record");
|
|
466
|
+
} finally {
|
|
467
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("an all-elided day replaces an existing transcript but never creates one from nothing", async () => {
|
|
472
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
473
|
+
try {
|
|
474
|
+
const { deps, written } = makeDeps(memoryDir);
|
|
475
|
+
const normalDay = {
|
|
476
|
+
"2026-06-11": [
|
|
477
|
+
makeConversation("c1", "2026-06-11", [
|
|
478
|
+
{ speaker: "user", isWearer: true, text: "Recorded thoughts about the quarterly planning session." },
|
|
479
|
+
{ speaker: "Speaker 2", text: "The planning session covered hiring and the budget." },
|
|
480
|
+
]),
|
|
481
|
+
],
|
|
482
|
+
};
|
|
483
|
+
await syncWearableSource(fakeConnector(normalDay), settings(), config(), { days: 1 }, deps);
|
|
484
|
+
assert.equal(written.length, 1);
|
|
485
|
+
|
|
486
|
+
// Same day re-fetched, but the provider's data was re-processed
|
|
487
|
+
// into pure ASR garbage — cleanup drops every segment, so the
|
|
488
|
+
// stored transcript must be replaced with an explicit empty-day
|
|
489
|
+
// file rather than lingering with stale content.
|
|
490
|
+
const garbageDay = {
|
|
491
|
+
"2026-06-11": [
|
|
492
|
+
makeConversation("c1", "2026-06-11", [
|
|
493
|
+
{ speaker: "user", isWearer: true, text: "zzzzzzzzzzzz" },
|
|
494
|
+
{ speaker: "Speaker 2", text: "############" },
|
|
495
|
+
]),
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
const second = await syncWearableSource(fakeConnector(garbageDay), settings(), config(), { days: 1 }, deps);
|
|
499
|
+
assert.equal(second.transcriptsWritten.length, 1, "replacement written");
|
|
500
|
+
const replaced = written[written.length - 1].serialized;
|
|
501
|
+
assert.match(replaced, /No storable conversation content/);
|
|
502
|
+
assert.ok(!replaced.includes("quarterly planning"), "old content gone");
|
|
503
|
+
|
|
504
|
+
// The same all-garbage day with no stored file writes nothing.
|
|
505
|
+
const { deps: freshDeps, written: freshWritten } = makeDeps(
|
|
506
|
+
mkdtempSync(path.join(tmpdir(), "remnic-pipeline-fresh-")),
|
|
507
|
+
);
|
|
508
|
+
await syncWearableSource(fakeConnector(garbageDay), settings(), config(), { days: 1 }, freshDeps);
|
|
509
|
+
assert.equal(freshWritten.length, 0);
|
|
510
|
+
} finally {
|
|
511
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("zero provider conversations never clobber an existing transcript", async () => {
|
|
516
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
517
|
+
try {
|
|
518
|
+
const { deps, written } = makeDeps(memoryDir);
|
|
519
|
+
const byDate = {
|
|
520
|
+
"2026-06-11": [
|
|
521
|
+
makeConversation("c1", "2026-06-11", [
|
|
522
|
+
{ speaker: "user", isWearer: true, text: "A transcript that must survive provider hiccups intact." },
|
|
523
|
+
{ speaker: "Speaker 2", text: "Provider outages should never erase stored history." },
|
|
524
|
+
]),
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
await syncWearableSource(fakeConnector(byDate), settings(), config(), { days: 1 }, deps);
|
|
528
|
+
assert.equal(written.length, 1);
|
|
529
|
+
|
|
530
|
+
// Provider hiccup: empty result for a day we have on disk.
|
|
531
|
+
const summary = await syncWearableSource(fakeConnector({}), settings(), config(), { days: 1 }, deps);
|
|
532
|
+
assert.equal(summary.transcriptsWritten.length, 0);
|
|
533
|
+
assert.equal(written.length, 1, "stored transcript untouched");
|
|
534
|
+
assert.ok(
|
|
535
|
+
summary.warnings.some((warning) => warning.includes("leaving it in place")),
|
|
536
|
+
`expected stale-transcript warning, got: ${summary.warnings.join(" | ")}`,
|
|
537
|
+
);
|
|
538
|
+
} finally {
|
|
539
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("page-capped days carry a visible partial marker and keep warning", async () => {
|
|
544
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
545
|
+
try {
|
|
546
|
+
const conversation = makeConversation("c1", "2026-06-11", [
|
|
547
|
+
{ speaker: "user", isWearer: true, text: "First chunk of a very long recorded day of conversations." },
|
|
548
|
+
{ speaker: "Speaker 2", text: "Indeed, the recordings just keep going on and on today." },
|
|
549
|
+
]);
|
|
550
|
+
// A connector that always reports another page (pathological).
|
|
551
|
+
const endlessConnector = {
|
|
552
|
+
id: "testsource",
|
|
553
|
+
displayName: "Test Source",
|
|
554
|
+
async verifyAuth() {
|
|
555
|
+
return { ok: true };
|
|
556
|
+
},
|
|
557
|
+
async fetchConversations(opts: { cursor?: string | null }) {
|
|
558
|
+
return {
|
|
559
|
+
conversations: opts.cursor ? [] : [conversation],
|
|
560
|
+
nextCursor: "more",
|
|
561
|
+
};
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
const { deps, written } = makeDeps(memoryDir);
|
|
565
|
+
const first = await syncWearableSource(endlessConnector, settings(), config(), { days: 1 }, deps);
|
|
566
|
+
assert.ok(first.warnings.some((warning) => warning.includes("stopped paginating")));
|
|
567
|
+
assert.equal(written.length, 1);
|
|
568
|
+
assert.match(written[0].serialized, /pagination safety cap reached/);
|
|
569
|
+
|
|
570
|
+
// Identical second sync: file unchanged (no rewrite), warning persists.
|
|
571
|
+
const second = await syncWearableSource(endlessConnector, settings(), config(), { days: 1 }, deps);
|
|
572
|
+
assert.equal(second.transcriptsWritten.length, 0);
|
|
573
|
+
assert.ok(second.warnings.some((warning) => warning.includes("stopped paginating")));
|
|
574
|
+
} finally {
|
|
575
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("a transcript write failure prevents the sync watermark from advancing", async () => {
|
|
580
|
+
const memoryDir = mkdtempSync(path.join(tmpdir(), "remnic-pipeline-"));
|
|
581
|
+
try {
|
|
582
|
+
const { deps } = makeDeps(memoryDir);
|
|
583
|
+
deps.writeDayTranscript = async () => {
|
|
584
|
+
throw new Error("disk full");
|
|
585
|
+
};
|
|
586
|
+
await assert.rejects(
|
|
587
|
+
syncWearableSource(
|
|
588
|
+
fakeConnector({
|
|
589
|
+
"2026-06-11": [
|
|
590
|
+
makeConversation("c1", "2026-06-11", [
|
|
591
|
+
{ speaker: "user", isWearer: true, text: "Something memorable happened today at the office." },
|
|
592
|
+
{ speaker: "Speaker 2", text: "It really did, everyone was talking about it after." },
|
|
593
|
+
]),
|
|
594
|
+
],
|
|
595
|
+
}),
|
|
596
|
+
settings(),
|
|
597
|
+
config(),
|
|
598
|
+
{ days: 1 },
|
|
599
|
+
deps,
|
|
600
|
+
),
|
|
601
|
+
/disk full/,
|
|
602
|
+
);
|
|
603
|
+
const state = await loadSyncState(memoryDir);
|
|
604
|
+
assert.equal(state.sources.testsource, undefined, "watermark must not advance");
|
|
605
|
+
} finally {
|
|
606
|
+
rmSync(memoryDir, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
});
|