@remnic/core 9.69.31 → 9.69.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/access-admin-ops-surface.d.ts +2 -2
  2. package/dist/access-admin-ops-surface.js +4 -4
  3. package/dist/access-authorization-probe.d.ts +2 -2
  4. package/dist/access-authorization-probe.js +5 -5
  5. package/dist/access-boundary.d.ts +2 -2
  6. package/dist/access-boundary.js +4 -4
  7. package/dist/access-cli.js +20 -20
  8. package/dist/access-extraction-force-flush.d.ts +2 -2
  9. package/dist/access-extraction-force-flush.js +4 -4
  10. package/dist/access-http-briefing.d.ts +2 -2
  11. package/dist/access-http-briefing.js +5 -5
  12. package/dist/access-http-lcm-compaction.d.ts +2 -2
  13. package/dist/access-http-lifecycle-flush.d.ts +2 -2
  14. package/dist/access-http-offline-stream.d.ts +2 -2
  15. package/dist/access-http-query.js +4 -4
  16. package/dist/access-http.d.ts +3 -3
  17. package/dist/access-http.js +14 -14
  18. package/dist/access-identity-continuity-surface.d.ts +1 -1
  19. package/dist/access-identity-continuity-surface.js +4 -4
  20. package/dist/access-lcm-surface.d.ts +2 -2
  21. package/dist/access-lcm-surface.js +4 -4
  22. package/dist/access-mcp.d.ts +2 -2
  23. package/dist/access-mcp.js +9 -9
  24. package/dist/access-namespace-preflight.js +4 -4
  25. package/dist/access-observe-transcript.d.ts +1 -1
  26. package/dist/access-observe-write-surface.d.ts +2 -2
  27. package/dist/access-observe-write-surface.js +4 -4
  28. package/dist/access-offline-manifest.d.ts +1 -1
  29. package/dist/access-operations-batch.js +5 -5
  30. package/dist/access-operations.d.ts +7 -7
  31. package/dist/access-operations.js +8 -8
  32. package/dist/access-recall-concurrency.d.ts +2 -2
  33. package/dist/access-recall-concurrency.js +4 -4
  34. package/dist/access-recall-response.d.ts +2 -2
  35. package/dist/access-recall-response.js +4 -4
  36. package/dist/access-recall-surface.d.ts +2 -2
  37. package/dist/access-recall-surface.js +4 -4
  38. package/dist/access-schema.d.ts +91 -91
  39. package/dist/{access-service-Cv8Rizo0.d.ts → access-service-CCpOuS46.d.ts} +65 -65
  40. package/dist/access-service-helpers.d.ts +2 -2
  41. package/dist/access-service.d.ts +2 -2
  42. package/dist/access-service.js +4 -4
  43. package/dist/access-surface-catalog.d.ts +2 -2
  44. package/dist/access-surface-catalog.js +6 -6
  45. package/dist/bootstrap.d.ts +1 -1
  46. package/dist/briefing-window.d.ts +25 -5
  47. package/dist/briefing-window.js +4 -1
  48. package/dist/briefing.js +2 -2
  49. package/dist/buffer.js +2 -2
  50. package/dist/{chunk-SMOFBBPU.js → chunk-2GPE5UCR.js} +2 -2
  51. package/dist/{chunk-IA2YSHTX.js → chunk-42SDNZCU.js} +8 -8
  52. package/dist/{chunk-W64HKJJM.js → chunk-5N53UBH2.js} +2 -2
  53. package/dist/{chunk-T7CJQVRZ.js → chunk-6H5JYDOM.js} +2 -2
  54. package/dist/chunk-6JMQR72P.js +134 -0
  55. package/dist/chunk-6JMQR72P.js.map +1 -0
  56. package/dist/{chunk-X74UAWSI.js → chunk-C4OTC6WY.js} +2 -2
  57. package/dist/{chunk-PLBW64UM.js → chunk-CZYYJZB6.js} +10 -11
  58. package/dist/chunk-CZYYJZB6.js.map +1 -0
  59. package/dist/{chunk-JDDXXZWW.js → chunk-GQOMZ3MK.js} +2 -2
  60. package/dist/{chunk-SH2ZWEWB.js → chunk-HG6HXVHQ.js} +3 -3
  61. package/dist/{chunk-OGELWNYC.js → chunk-ITPS5DDH.js} +3 -3
  62. package/dist/{chunk-EYMOJ3J6.js → chunk-LPECRQO4.js} +2 -2
  63. package/dist/{chunk-666OOKB7.js → chunk-M75RYSHS.js} +2 -2
  64. package/dist/{chunk-5ABN4WVL.js → chunk-MYMNC6O5.js} +4 -4
  65. package/dist/{chunk-KLMESRPV.js → chunk-NAXDPKVM.js} +2 -2
  66. package/dist/{chunk-OXNU333D.js → chunk-Q3O3VKRB.js} +2 -2
  67. package/dist/{chunk-ENMTJRXA.js → chunk-QMGXMJ2O.js} +2 -2
  68. package/dist/{chunk-PEOTOFH5.js → chunk-TIT2C6WX.js} +2 -2
  69. package/dist/{chunk-U34EHBPL.js → chunk-VED2YHUD.js} +2 -2
  70. package/dist/{chunk-RAVRDTP7.js → chunk-XQ2Q43XD.js} +3 -1
  71. package/dist/chunk-XQ2Q43XD.js.map +1 -0
  72. package/dist/{chunk-7DUFJAOE.js → chunk-Z4PCVRTN.js} +3 -3
  73. package/dist/{cli-B_YwqoIr.d.ts → cli-NEEdq5Rt.d.ts} +2 -2
  74. package/dist/cli.d.ts +3 -3
  75. package/dist/cli.js +15 -15
  76. package/dist/deep-recall-http-glue.d.ts +2 -2
  77. package/dist/deep-recall-mcp-tools.d.ts +2 -2
  78. package/dist/explicit-capture.d.ts +1 -1
  79. package/dist/external-wiki-access.d.ts +2 -2
  80. package/dist/external-wiki-access.js +5 -5
  81. package/dist/external-wiki-mcp-tools.d.ts +2 -2
  82. package/dist/extraction.js +3 -3
  83. package/dist/index.d.ts +681 -681
  84. package/dist/index.js +20 -20
  85. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  86. package/dist/memory-subject-cli.d.ts +2 -2
  87. package/dist/memory-subject.d.ts +2 -2
  88. package/dist/{orchestrator-DgyvFfPR.d.ts → orchestrator-kqSzGv7e.d.ts} +48 -64
  89. package/dist/orchestrator.d.ts +1 -1
  90. package/dist/orchestrator.js +20 -20
  91. package/dist/{public-http-D6d4W8x-.d.ts → public-http-CW3l0g3P.d.ts} +1 -1
  92. package/dist/schemas.d.ts +176 -176
  93. package/dist/shared-context/manager.d.ts +6 -6
  94. package/dist/source-agent-qualifier.js +2 -2
  95. package/dist/support-passport/index.d.ts +5 -5
  96. package/dist/support-passport/index.js +7 -7
  97. package/dist/transfer/types.d.ts +78 -78
  98. package/dist/who-knows-cli.d.ts +2 -2
  99. package/dist/who-knows.d.ts +2 -2
  100. package/package.json +2 -2
  101. package/src/briefing-window.test.ts +554 -0
  102. package/src/briefing-window.ts +201 -12
  103. package/src/briefing.test.ts +2 -1
  104. package/src/orchestration/extraction-shutdown.ts +2 -10
  105. package/src/orchestration/graph-recall-coordinator.ts +19 -16
  106. package/src/orchestration/namespace-read-fanout.ts +2 -5
  107. package/src/orchestration/orchestrator-helpers.ts +2 -0
  108. package/src/orchestration/recall-internal-deps.ts +1 -5
  109. package/src/orchestration/recall-search-pipeline.ts +1 -4
  110. package/src/orchestrator.ts +1 -3
  111. package/dist/chunk-PLBW64UM.js.map +0 -1
  112. package/dist/chunk-RAVRDTP7.js.map +0 -1
  113. package/dist/chunk-UBCBDJOB.js +0 -22
  114. package/dist/chunk-UBCBDJOB.js.map +0 -1
  115. package/src/orchestration/graph-recall-seam.ts +0 -19
  116. /package/dist/{chunk-SMOFBBPU.js.map → chunk-2GPE5UCR.js.map} +0 -0
  117. /package/dist/{chunk-IA2YSHTX.js.map → chunk-42SDNZCU.js.map} +0 -0
  118. /package/dist/{chunk-W64HKJJM.js.map → chunk-5N53UBH2.js.map} +0 -0
  119. /package/dist/{chunk-T7CJQVRZ.js.map → chunk-6H5JYDOM.js.map} +0 -0
  120. /package/dist/{chunk-X74UAWSI.js.map → chunk-C4OTC6WY.js.map} +0 -0
  121. /package/dist/{chunk-JDDXXZWW.js.map → chunk-GQOMZ3MK.js.map} +0 -0
  122. /package/dist/{chunk-SH2ZWEWB.js.map → chunk-HG6HXVHQ.js.map} +0 -0
  123. /package/dist/{chunk-OGELWNYC.js.map → chunk-ITPS5DDH.js.map} +0 -0
  124. /package/dist/{chunk-EYMOJ3J6.js.map → chunk-LPECRQO4.js.map} +0 -0
  125. /package/dist/{chunk-666OOKB7.js.map → chunk-M75RYSHS.js.map} +0 -0
  126. /package/dist/{chunk-5ABN4WVL.js.map → chunk-MYMNC6O5.js.map} +0 -0
  127. /package/dist/{chunk-KLMESRPV.js.map → chunk-NAXDPKVM.js.map} +0 -0
  128. /package/dist/{chunk-OXNU333D.js.map → chunk-Q3O3VKRB.js.map} +0 -0
  129. /package/dist/{chunk-ENMTJRXA.js.map → chunk-QMGXMJ2O.js.map} +0 -0
  130. /package/dist/{chunk-PEOTOFH5.js.map → chunk-TIT2C6WX.js.map} +0 -0
  131. /package/dist/{chunk-U34EHBPL.js.map → chunk-VED2YHUD.js.map} +0 -0
  132. /package/dist/{chunk-7DUFJAOE.js.map → chunk-Z4PCVRTN.js.map} +0 -0
@@ -0,0 +1,554 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { initLogger } from "./logger.js";
4
+ import { BRIEFING_FULL_READ_FALLBACK_MS, safeReadMemories } from "./briefing-window.js";
5
+ import type { CorpusReadOptions } from "./corpus-read-cancellation.js";
6
+ import { buildBriefing, type ParsedBriefingWindow } from "./briefing.js";
7
+ import type { StorageManager } from "./storage.js";
8
+ import type { MemoryFile } from "./types.js";
9
+
10
+ // ──────────────────────────────────────────────────────────────────────────
11
+ // Issue #2779 — briefing memory-read discriminator + cancellable fallback
12
+ // ──────────────────────────────────────────────────────────────────────────
13
+
14
+ function makeMemory(updated: string): MemoryFile {
15
+ return {
16
+ path: "/synthetic/mem.md",
17
+ frontmatter: {
18
+ id: "test-mem",
19
+ category: "fact",
20
+ created: updated,
21
+ updated,
22
+ source: "test",
23
+ confidence: 0.9,
24
+ confidenceTier: "explicit",
25
+ tags: [],
26
+ },
27
+ content: "synthetic memory",
28
+ };
29
+ }
30
+
31
+ const WINDOW: ParsedBriefingWindow = {
32
+ from: new Date("2026-04-10T00:00:00.000Z"),
33
+ to: new Date("2026-04-11T00:00:00.000Z"),
34
+ label: "test-window",
35
+ };
36
+
37
+ /** Install a capturing logger backend; returns lines plus a restore fn. */
38
+ function captureLogs(): { lines: string[]; restore: () => void } {
39
+ const lines: string[] = [];
40
+ initLogger(
41
+ {
42
+ info: (message: string) => lines.push(message),
43
+ warn: (message: string) => lines.push(message),
44
+ error: (message: string) => lines.push(message),
45
+ debug: (message: string) => lines.push(message),
46
+ },
47
+ false,
48
+ { timestamps: false },
49
+ );
50
+ return {
51
+ lines,
52
+ restore: () => initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false),
53
+ };
54
+ }
55
+
56
+ /**
57
+ * The slow-double test below deliberately exercises the REAL abort-timer
58
+ * protocol (AbortController armed by production setTimeout) against the
59
+ * platform clock; mock timers cannot drive a deadline the code under test
60
+ * arms itself.
61
+ */
62
+ function delay(ms: number): Promise<void> {
63
+ const { promise, resolve } = Promise.withResolvers<void>();
64
+ setTimeout(resolve, ms);
65
+ return promise;
66
+ }
67
+
68
+ /** The structured discriminator line(s): info lines carrying an outcome= field. */
69
+ function discriminators(lines: string[]): string[] {
70
+ return lines.filter((line) => line.includes("briefing: memory read") && line.includes("outcome="));
71
+ }
72
+
73
+ test("safeReadMemories prefers readMemoriesWindow and logs exactly one windowed success discriminator", async () => {
74
+ const { lines, restore } = captureLogs();
75
+ try {
76
+ let windowOptions: { updatedAfter?: Date } | undefined;
77
+ let fullReadCalls = 0;
78
+ const storage = {
79
+ readMemoriesWindow: async (options: { updatedAfter?: Date }) => {
80
+ windowOptions = options;
81
+ return { memories: [makeMemory("2026-04-10T01:00:00.000Z")], filePaths: [] };
82
+ },
83
+ readAllMemories: async () => {
84
+ fullReadCalls += 1;
85
+ return [];
86
+ },
87
+ };
88
+
89
+ const memories = await safeReadMemories(storage, WINDOW);
90
+
91
+ assert.equal(memories.length, 1);
92
+ assert.equal(windowOptions?.updatedAfter, WINDOW.from, "window start must reach the adapter");
93
+ assert.equal(fullReadCalls, 0, "full corpus read must never run when the adapter can window");
94
+ const marks = discriminators(lines);
95
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
96
+ assert.match(
97
+ marks[0]!,
98
+ /briefing: memory read mode=windowed durationMs=\d+ count=1 outcome=success$/,
99
+ );
100
+ } finally {
101
+ restore();
102
+ }
103
+ });
104
+
105
+ test("discriminator logger failure cannot override a successful memory read", async () => {
106
+ initLogger(
107
+ {
108
+ info: () => {
109
+ throw new Error("logger transport unavailable");
110
+ },
111
+ warn() {},
112
+ error() {},
113
+ debug() {},
114
+ },
115
+ false,
116
+ );
117
+ try {
118
+ const expected = makeMemory("2026-04-10T01:00:00.000Z");
119
+ const storage = {
120
+ readMemoriesWindow: async () => ({ memories: [expected], filePaths: [] }),
121
+ readAllMemories: async () => [],
122
+ };
123
+
124
+ assert.deepEqual(await safeReadMemories(storage, WINDOW), [expected]);
125
+ } finally {
126
+ initLogger({ info() {}, warn() {}, error() {}, debug() {} }, false);
127
+ }
128
+ });
129
+
130
+ test("safeReadMemories falls back to a signal-aware readAllMemories and logs exactly one success discriminator", async () => {
131
+ const { lines, restore } = captureLogs();
132
+ try {
133
+ let fullReadCalls = 0;
134
+ let receivedSignal: AbortSignal | undefined;
135
+ const storage = {
136
+ supportsAbortSignal: true,
137
+ readAllMemories: async (options?: CorpusReadOptions): Promise<MemoryFile[]> => {
138
+ fullReadCalls += 1;
139
+ receivedSignal = options?.abortSignal;
140
+ return [
141
+ makeMemory("2026-04-10T01:00:00.000Z"),
142
+ makeMemory("2026-04-10T02:00:00.000Z"),
143
+ makeMemory("2026-04-09T00:00:00.000Z"),
144
+ ];
145
+ },
146
+ };
147
+
148
+ const memories = await safeReadMemories(storage, WINDOW);
149
+
150
+ assert.equal(fullReadCalls, 1);
151
+ assert.ok(receivedSignal instanceof AbortSignal, "deadline must reach the read as an abort signal");
152
+ assert.ok(!receivedSignal.aborted, "deadline timer must be cleared after a successful read");
153
+ assert.equal(memories.length, 3, "signal-aware double keeps its full-read behavior (backward compat)");
154
+ const marks = discriminators(lines);
155
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
156
+ assert.match(
157
+ marks[0]!,
158
+ /briefing: memory read mode=full-read-fallback durationMs=\d+ count=3 outcome=success$/,
159
+ );
160
+ } finally {
161
+ restore();
162
+ }
163
+ });
164
+
165
+ test("safeReadMemories aborts a slow signal-aware fallback read: no rows after deadline, no active work remains", async () => {
166
+ const { lines, restore } = captureLogs();
167
+ try {
168
+ const TOTAL_ROWS = 200;
169
+ let sawSignal = false;
170
+ let rowsProduced = 0;
171
+ let abortChecks = 0;
172
+ let readSettled = false;
173
+ const storage = {
174
+ supportsAbortSignal: true,
175
+ readAllMemories: async (options?: CorpusReadOptions): Promise<MemoryFile[]> => {
176
+ sawSignal = options?.abortSignal instanceof AbortSignal;
177
+ const memories: MemoryFile[] = [];
178
+ for (let i = 0; i < TOTAL_ROWS; i++) {
179
+ if (options?.abortSignal?.aborted) {
180
+ abortChecks += 1;
181
+ break;
182
+ }
183
+ rowsProduced += 1;
184
+ memories.push(makeMemory("2026-04-10T01:00:00.000Z"));
185
+ await delay(5);
186
+ }
187
+ readSettled = true;
188
+ return memories;
189
+ },
190
+ };
191
+
192
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
193
+
194
+ assert.deepEqual(memories, [], "aborted fallback fails open with an empty read");
195
+ assert.ok(sawSignal, "the deadline must reach the read as an abort signal");
196
+ assert.ok(rowsProduced > 0 && rowsProduced < TOTAL_ROWS, "read must stop mid-corpus, not run to completion");
197
+ assert.ok(abortChecks > 0, "the double must observe the aborted signal");
198
+ assert.ok(readSettled, "no active read work remains once safeReadMemories returns");
199
+ const marks = discriminators(lines);
200
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
201
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=timeout$/);
202
+ assert.ok(
203
+ lines.some((line) => line.includes("timed out after 25ms")),
204
+ "timeout must be diagnosable from the log",
205
+ );
206
+ } finally {
207
+ restore();
208
+ }
209
+ });
210
+
211
+ test("safeReadMemories refuses an unmarked legacy adapter before readAllMemories call", async () => {
212
+ const { lines, restore } = captureLogs();
213
+ try {
214
+ let fullReadCalls = 0;
215
+ const storage = {
216
+ readAllMemories: () => {
217
+ fullReadCalls += 1;
218
+ return new Promise<MemoryFile[]>(() => {});
219
+ },
220
+ };
221
+
222
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
223
+
224
+ assert.deepEqual(memories, [], "unmarked fallback fails open with an empty read");
225
+ assert.equal(fullReadCalls, 0, "an unmarked legacy readAllMemories must never be called");
226
+ assert.ok(
227
+ lines.some((line) => line.includes("refused")),
228
+ "the refusal must tell the operator which adapter upgrade unblocks it",
229
+ );
230
+ const marks = discriminators(lines);
231
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
232
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=error err=LegacyReadUnsupported$/);
233
+ } finally {
234
+ restore();
235
+ }
236
+ });
237
+
238
+ test("safeReadMemories refuses an adapter with explicit false cancellation support before readAllMemories call", async () => {
239
+ const { lines, restore } = captureLogs();
240
+ try {
241
+ let fullReadCalls = 0;
242
+ const storage = {
243
+ supportsAbortSignal: false,
244
+ readAllMemories: () => {
245
+ fullReadCalls += 1;
246
+ return new Promise<MemoryFile[]>(() => {});
247
+ },
248
+ };
249
+
250
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
251
+
252
+ assert.deepEqual(memories, [], "refused fallback fails open with an empty read");
253
+ assert.equal(fullReadCalls, 0, "an uncancellable full read must never be started");
254
+ assert.ok(
255
+ lines.some((line) => line.includes("refused")),
256
+ "the refusal must tell the operator which adapter upgrade unblocks it",
257
+ );
258
+ const marks = discriminators(lines);
259
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
260
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=error err=LegacyReadUnsupported$/);
261
+ } finally {
262
+ restore();
263
+ }
264
+ });
265
+
266
+ test("safeReadMemories invokes fallback for each explicit capability marker when marker is true", async () => {
267
+ type ReaderFn = (options?: CorpusReadOptions) => Promise<MemoryFile[]>;
268
+ const cases: Array<{
269
+ label: string;
270
+ makeStorage: (fn: ReaderFn) => Parameters<typeof safeReadMemories>[0];
271
+ }> = [
272
+ {
273
+ label: "storage.supportsAbortSignal",
274
+ makeStorage: (fn) => ({ supportsAbortSignal: true, readAllMemories: fn }),
275
+ },
276
+ {
277
+ label: "storage.supportsCancellation",
278
+ makeStorage: (fn) => ({ supportsCancellation: true, readAllMemories: fn }),
279
+ },
280
+ {
281
+ label: "storage.cancellable",
282
+ makeStorage: (fn) => ({ cancellable: true, readAllMemories: fn }),
283
+ },
284
+ {
285
+ label: "readAllMemories.supportsAbortSignal",
286
+ makeStorage: (fn) => {
287
+ const readFn = fn as ReaderFn & { supportsAbortSignal?: boolean };
288
+ readFn.supportsAbortSignal = true;
289
+ return { readAllMemories: readFn };
290
+ },
291
+ },
292
+ ];
293
+
294
+ for (const { label, makeStorage } of cases) {
295
+ const { restore } = captureLogs();
296
+ try {
297
+ let fullReadCalls = 0;
298
+ let receivedSignal: AbortSignal | undefined;
299
+ const storage = makeStorage(async (options?: CorpusReadOptions) => {
300
+ fullReadCalls += 1;
301
+ receivedSignal = options?.abortSignal;
302
+ return [makeMemory("2026-04-10T01:00:00.000Z")];
303
+ });
304
+
305
+ const memories = await safeReadMemories(storage, WINDOW);
306
+
307
+ assert.equal(fullReadCalls, 1, `readAllMemories must be invoked when ${label} is true`);
308
+ assert.ok(receivedSignal instanceof AbortSignal, `abort signal must reach readAllMemories for ${label}`);
309
+ assert.equal(memories.length, 1);
310
+ } finally {
311
+ restore();
312
+ }
313
+ }
314
+ });
315
+
316
+ test("windowed rejection emits exactly one error discriminator — class only, never the message", async () => {
317
+ const { lines, restore } = captureLogs();
318
+ try {
319
+ const storage = {
320
+ readMemoriesWindow: async (): Promise<{ memories: MemoryFile[] }> => {
321
+ throw new TypeError("boom-sync-window");
322
+ },
323
+ readAllMemories: async () => [],
324
+ };
325
+
326
+ const memories = await safeReadMemories(storage, WINDOW);
327
+
328
+ assert.deepEqual(memories, [], "read failure fails open with an empty read");
329
+ const marks = discriminators(lines);
330
+ assert.equal(marks.length, 1, "exactly one discriminator per read, rejection included");
331
+ assert.match(marks[0]!, /mode=windowed durationMs=\d+ outcome=error err=TypeError$/);
332
+ assert.ok(
333
+ !lines.some((line) => line.includes("boom-sync-window")),
334
+ "error message content must never reach the log",
335
+ );
336
+ } finally {
337
+ restore();
338
+ }
339
+ });
340
+
341
+ test("fallback rejection emits exactly one error discriminator — class only, never the message", async () => {
342
+ const { lines, restore } = captureLogs();
343
+ try {
344
+ const storage = {
345
+ supportsAbortSignal: true,
346
+ readAllMemories: async (_options?: CorpusReadOptions): Promise<MemoryFile[]> => {
347
+ throw new Error("corpus read exploded");
348
+ },
349
+ };
350
+
351
+ const memories = await safeReadMemories(storage, WINDOW);
352
+
353
+ assert.deepEqual(memories, [], "read failure fails open with an empty read");
354
+ const marks = discriminators(lines);
355
+ assert.equal(marks.length, 1, "exactly one discriminator per read, rejection included");
356
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=error err=Error$/);
357
+ assert.ok(
358
+ !lines.some((line) => line.includes("corpus read exploded")),
359
+ "error message content must never reach the log",
360
+ );
361
+ } finally {
362
+ restore();
363
+ }
364
+ });
365
+
366
+ test("fallback AbortError is a timeout only after the briefing deadline fires", async () => {
367
+ const { lines, restore } = captureLogs();
368
+ try {
369
+ const storage = {
370
+ supportsAbortSignal: true,
371
+ readAllMemories: async (_options?: CorpusReadOptions): Promise<MemoryFile[]> => {
372
+ const error = new Error("backend cancelled independently");
373
+ error.name = "AbortError";
374
+ throw error;
375
+ },
376
+ };
377
+
378
+ assert.deepEqual(await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 1_000 }), []);
379
+ const marks = discriminators(lines);
380
+ assert.equal(marks.length, 1);
381
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=error err=AbortError$/);
382
+ assert.equal(lines.some((line) => line.includes("timed out")), false);
383
+ } finally {
384
+ restore();
385
+ }
386
+ });
387
+
388
+ test("safeReadMemories cancels readAllMemories declared with default parameters", async () => {
389
+ const { lines, restore } = captureLogs();
390
+ try {
391
+ let sawSignal = false;
392
+ const storage = {
393
+ supportsAbortSignal: true,
394
+ readAllMemories: async (options = {} as CorpusReadOptions): Promise<MemoryFile[]> => {
395
+ sawSignal = options?.abortSignal instanceof AbortSignal;
396
+ if (options?.abortSignal) {
397
+ await new Promise<void>((resolve) => {
398
+ options.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
399
+ });
400
+ const err = new Error("aborted");
401
+ err.name = "AbortError";
402
+ throw err;
403
+ }
404
+ return [];
405
+ },
406
+ };
407
+
408
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
409
+
410
+ assert.deepEqual(memories, []);
411
+ assert.ok(sawSignal, "default-parameter readAllMemories must receive the abortSignal");
412
+ const marks = discriminators(lines);
413
+ assert.equal(marks.length, 1);
414
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=timeout$/);
415
+ } finally {
416
+ restore();
417
+ }
418
+ });
419
+
420
+ test("safeReadMemories cancels readAllMemories declared with rest parameters", async () => {
421
+ const { lines, restore } = captureLogs();
422
+ try {
423
+ let sawSignal = false;
424
+ const storage = {
425
+ supportsCancellation: true,
426
+ readAllMemories: async (...args: [CorpusReadOptions?]): Promise<MemoryFile[]> => {
427
+ const options = args[0];
428
+ sawSignal = options?.abortSignal instanceof AbortSignal;
429
+ if (options?.abortSignal) {
430
+ await new Promise<void>((resolve) => {
431
+ options.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
432
+ });
433
+ const err = new Error("aborted");
434
+ err.name = "AbortError";
435
+ throw err;
436
+ }
437
+ return [];
438
+ },
439
+ };
440
+
441
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
442
+
443
+ assert.deepEqual(memories, []);
444
+ assert.ok(sawSignal, "rest-parameter readAllMemories must receive the abortSignal");
445
+ const marks = discriminators(lines);
446
+ assert.equal(marks.length, 1);
447
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=timeout$/);
448
+ } finally {
449
+ restore();
450
+ }
451
+ });
452
+
453
+ test("safeReadMemories cancels readAllMemories when bound function is passed", async () => {
454
+ const { lines, restore } = captureLogs();
455
+ try {
456
+ let sawSignal = false;
457
+ const fn = async (options?: CorpusReadOptions): Promise<MemoryFile[]> => {
458
+ sawSignal = options?.abortSignal instanceof AbortSignal;
459
+ if (options?.abortSignal) {
460
+ await new Promise<void>((resolve) => {
461
+ options.abortSignal?.addEventListener("abort", () => resolve(), { once: true });
462
+ });
463
+ const err = new Error("aborted");
464
+ err.name = "AbortError";
465
+ throw err;
466
+ }
467
+ return [];
468
+ };
469
+ const readAllMemories = fn.bind(null) as typeof fn & { supportsAbortSignal?: boolean };
470
+ readAllMemories.supportsAbortSignal = true;
471
+ const storage = {
472
+ readAllMemories,
473
+ };
474
+
475
+ const memories = await safeReadMemories(storage, WINDOW, { fallbackDeadlineMs: 25 });
476
+
477
+ assert.deepEqual(memories, []);
478
+ assert.ok(sawSignal, "bound function readAllMemories must receive the abortSignal");
479
+ const marks = discriminators(lines);
480
+ assert.equal(marks.length, 1);
481
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=timeout$/);
482
+ } finally {
483
+ restore();
484
+ }
485
+ });
486
+
487
+ test("errorClass sanitizes malicious or backend-controlled Error.name to safe bounded class", async () => {
488
+ const { lines, restore } = captureLogs();
489
+ try {
490
+ const storage = {
491
+ supportsAbortSignal: true,
492
+ readAllMemories: async (_options?: CorpusReadOptions): Promise<MemoryFile[]> => {
493
+ const err = new Error("leak secret payload");
494
+ err.name = "MaliciousError\nLOG_INJECTION\nsecret_token_12345";
495
+ throw err;
496
+ },
497
+ };
498
+
499
+ const memories = await safeReadMemories(storage, WINDOW);
500
+
501
+ assert.deepEqual(memories, []);
502
+ const marks = discriminators(lines);
503
+ assert.equal(marks.length, 1, "exactly one discriminator per read");
504
+ assert.match(marks[0]!, /mode=full-read-fallback durationMs=\d+ outcome=error err=CustomError$/);
505
+ assert.ok(
506
+ !lines.some((line) => line.includes("MaliciousError") || line.includes("LOG_INJECTION") || line.includes("secret_token")),
507
+ "malicious Error.name must never land in discriminator or logs",
508
+ );
509
+ } finally {
510
+ restore();
511
+ }
512
+ });
513
+
514
+ test("default fallback deadline stays well under the 60s MCP client timeout", () => {
515
+ assert.ok(BRIEFING_FULL_READ_FALLBACK_MS > 0 && BRIEFING_FULL_READ_FALLBACK_MS < 60_000);
516
+ });
517
+
518
+ test("buildBriefing over a large-corpus fake completes without full-corpus materialization", async () => {
519
+ const { lines, restore } = captureLogs();
520
+ try {
521
+ const CORPUS_SIZE = 50_000;
522
+ let windowCalls = 0;
523
+ let fullReadCalls = 0;
524
+ const storage = {
525
+ readMemoriesWindow: async (options: { updatedAfter?: Date }) => {
526
+ windowCalls += 1;
527
+ assert.ok(options.updatedAfter instanceof Date);
528
+ return { memories: [makeMemory("2026-04-10T01:00:00.000Z")], filePaths: [] };
529
+ },
530
+ readAllMemories: async () => {
531
+ fullReadCalls += 1;
532
+ return Array.from({ length: CORPUS_SIZE }, () => makeMemory("2026-01-01T00:00:00.000Z"));
533
+ },
534
+ readAllEntityFiles: async () => [],
535
+ } as unknown as StorageManager;
536
+
537
+ const result = await buildBriefing({
538
+ storage,
539
+ window: WINDOW,
540
+ allowLlm: false,
541
+ now: new Date("2026-04-11T10:00:00.000Z"),
542
+ });
543
+
544
+ assert.ok(result.markdown.length > 0);
545
+ assert.equal(windowCalls, 1, "exactly one windowed read serves the briefing");
546
+ assert.equal(fullReadCalls, 0, "briefing must never materialize the full corpus");
547
+ assert.match(
548
+ discriminators(lines)[0] ?? "",
549
+ /mode=windowed durationMs=\d+ count=1 outcome=success/,
550
+ );
551
+ } finally {
552
+ restore();
553
+ }
554
+ });