@longarc/mdash 3.1.2 → 3.1.3

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 (172) hide show
  1. package/README.md +86 -23
  2. package/SECURITY.md +254 -0
  3. package/dist/accountability/engine.d.ts +27 -0
  4. package/dist/accountability/engine.d.ts.map +1 -0
  5. package/dist/accountability/engine.js +148 -0
  6. package/dist/accountability/engine.js.map +1 -0
  7. package/dist/accountability/types.d.ts +46 -0
  8. package/dist/accountability/types.d.ts.map +1 -0
  9. package/dist/accountability/types.js +8 -0
  10. package/dist/accountability/types.js.map +1 -0
  11. package/dist/checkpoint/engine.d.ts.map +1 -1
  12. package/dist/checkpoint/engine.js +4 -0
  13. package/dist/checkpoint/engine.js.map +1 -1
  14. package/dist/context/compose.d.ts +62 -0
  15. package/dist/context/compose.d.ts.map +1 -0
  16. package/dist/context/compose.js +286 -0
  17. package/dist/context/compose.js.map +1 -0
  18. package/dist/context/crypto/hash.d.ts +100 -0
  19. package/dist/context/crypto/hash.d.ts.map +1 -0
  20. package/dist/context/crypto/hash.js +248 -0
  21. package/dist/context/crypto/hash.js.map +1 -0
  22. package/dist/context/crypto/hmac.d.ts +80 -0
  23. package/dist/context/crypto/hmac.d.ts.map +1 -0
  24. package/dist/context/crypto/hmac.js +192 -0
  25. package/dist/context/crypto/hmac.js.map +1 -0
  26. package/dist/context/crypto/index.d.ts +7 -0
  27. package/dist/context/crypto/index.d.ts.map +1 -0
  28. package/dist/context/crypto/index.js +7 -0
  29. package/dist/context/crypto/index.js.map +1 -0
  30. package/dist/context/engine-v3.0-backup.d.ts +197 -0
  31. package/dist/context/engine-v3.0-backup.d.ts.map +1 -0
  32. package/dist/context/engine-v3.0-backup.js +392 -0
  33. package/dist/context/engine-v3.0-backup.js.map +1 -0
  34. package/dist/context/fragment.d.ts +99 -0
  35. package/dist/context/fragment.d.ts.map +1 -0
  36. package/dist/context/fragment.js +316 -0
  37. package/dist/context/fragment.js.map +1 -0
  38. package/dist/context/index.d.ts +99 -0
  39. package/dist/context/index.d.ts.map +1 -0
  40. package/dist/context/index.js +180 -0
  41. package/dist/context/index.js.map +1 -0
  42. package/dist/context/provenance.d.ts +80 -0
  43. package/dist/context/provenance.d.ts.map +1 -0
  44. package/dist/context/provenance.js +294 -0
  45. package/dist/context/provenance.js.map +1 -0
  46. package/dist/context/resolve.d.ts +106 -0
  47. package/dist/context/resolve.d.ts.map +1 -0
  48. package/dist/context/resolve.js +440 -0
  49. package/dist/context/resolve.js.map +1 -0
  50. package/dist/context/store.d.ts +156 -0
  51. package/dist/context/store.d.ts.map +1 -0
  52. package/dist/context/store.js +396 -0
  53. package/dist/context/store.js.map +1 -0
  54. package/dist/context/types.d.ts +463 -0
  55. package/dist/context/types.d.ts.map +1 -0
  56. package/dist/context/types.js +94 -0
  57. package/dist/context/types.js.map +1 -0
  58. package/dist/context/utils/atomic.d.ts +76 -0
  59. package/dist/context/utils/atomic.d.ts.map +1 -0
  60. package/dist/context/utils/atomic.js +159 -0
  61. package/dist/context/utils/atomic.js.map +1 -0
  62. package/dist/context/utils/credit.d.ts +65 -0
  63. package/dist/context/utils/credit.d.ts.map +1 -0
  64. package/dist/context/utils/credit.js +164 -0
  65. package/dist/context/utils/credit.js.map +1 -0
  66. package/dist/context/utils/index.d.ts +13 -0
  67. package/dist/context/utils/index.d.ts.map +1 -0
  68. package/dist/context/utils/index.js +13 -0
  69. package/dist/context/utils/index.js.map +1 -0
  70. package/dist/context/utils/utility.d.ts +63 -0
  71. package/dist/context/utils/utility.d.ts.map +1 -0
  72. package/dist/context/utils/utility.js +141 -0
  73. package/dist/context/utils/utility.js.map +1 -0
  74. package/dist/core/commitment.d.ts +25 -2
  75. package/dist/core/commitment.d.ts.map +1 -1
  76. package/dist/core/commitment.js +44 -6
  77. package/dist/core/commitment.js.map +1 -1
  78. package/dist/core/crypto.d.ts +2 -0
  79. package/dist/core/crypto.d.ts.map +1 -1
  80. package/dist/core/crypto.js +12 -0
  81. package/dist/core/crypto.js.map +1 -1
  82. package/dist/index.d.ts +11 -6
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +35 -10
  85. package/dist/index.js.map +1 -1
  86. package/dist/mcca/engine.d.ts.map +1 -1
  87. package/dist/mcca/engine.js +5 -4
  88. package/dist/mcca/engine.js.map +1 -1
  89. package/dist/physics/engine.d.ts +1 -0
  90. package/dist/physics/engine.d.ts.map +1 -1
  91. package/dist/physics/engine.js +36 -2
  92. package/dist/physics/engine.js.map +1 -1
  93. package/dist/provenance/api-handler.d.ts +45 -0
  94. package/dist/provenance/api-handler.d.ts.map +1 -0
  95. package/dist/provenance/api-handler.js +223 -0
  96. package/dist/provenance/api-handler.js.map +1 -0
  97. package/dist/provenance/api-types.d.ts +108 -0
  98. package/dist/provenance/api-types.d.ts.map +1 -0
  99. package/dist/provenance/api-types.js +9 -0
  100. package/dist/provenance/api-types.js.map +1 -0
  101. package/dist/provenance/index.d.ts +6 -0
  102. package/dist/provenance/index.d.ts.map +1 -0
  103. package/dist/provenance/index.js +3 -0
  104. package/dist/provenance/index.js.map +1 -0
  105. package/dist/provenance/provenance-engine.d.ts +63 -0
  106. package/dist/provenance/provenance-engine.d.ts.map +1 -0
  107. package/dist/provenance/provenance-engine.js +311 -0
  108. package/dist/provenance/provenance-engine.js.map +1 -0
  109. package/dist/provenance/types.d.ts +193 -0
  110. package/dist/provenance/types.d.ts.map +1 -0
  111. package/dist/provenance/types.js +9 -0
  112. package/dist/provenance/types.js.map +1 -0
  113. package/dist/tee/engine.d.ts.map +1 -1
  114. package/dist/tee/engine.js +14 -0
  115. package/dist/tee/engine.js.map +1 -1
  116. package/dist/warrant/engine.d.ts +24 -1
  117. package/dist/warrant/engine.d.ts.map +1 -1
  118. package/dist/warrant/engine.js +76 -1
  119. package/dist/warrant/engine.js.map +1 -1
  120. package/dist/zk/engine.d.ts.map +1 -1
  121. package/dist/zk/engine.js +7 -4
  122. package/dist/zk/engine.js.map +1 -1
  123. package/docs/SECURITY-PATCHES.md +170 -0
  124. package/package.json +17 -5
  125. package/src/__tests__/accountability.test.ts +308 -0
  126. package/src/__tests__/l1-verification-modes.test.ts +424 -0
  127. package/src/__tests__/phase1.benchmark.test.ts +94 -0
  128. package/src/__tests__/phase1.test.ts +0 -77
  129. package/src/__tests__/phase2-4.benchmark.test.ts +60 -0
  130. package/src/__tests__/phase2-4.test.ts +1 -52
  131. package/src/__tests__/provenance/api-handler.test.ts +356 -0
  132. package/src/__tests__/provenance/provenance-engine.test.ts +628 -0
  133. package/src/__tests__/sa-2026-008.test.ts +45 -0
  134. package/src/__tests__/sa-2026-009.test.ts +86 -0
  135. package/src/__tests__/sa-2026-010.test.ts +72 -0
  136. package/src/__tests__/sa-2026-012.test.ts +65 -0
  137. package/src/__tests__/sa-2026-nfc.test.ts +40 -0
  138. package/src/__tests__/security.test.ts +786 -0
  139. package/src/accountability/engine.ts +230 -0
  140. package/src/accountability/types.ts +58 -0
  141. package/src/checkpoint/engine.ts +4 -0
  142. package/src/context/__tests__/caret-v0.2.0.test.ts +860 -0
  143. package/src/context/__tests__/integration.test.ts +356 -0
  144. package/src/context/compose.ts +388 -0
  145. package/src/context/crypto/hash.ts +277 -0
  146. package/src/context/crypto/hmac.ts +253 -0
  147. package/src/context/crypto/index.ts +29 -0
  148. package/src/context/engine-v3.0-backup.ts +598 -0
  149. package/src/context/fragment.ts +454 -0
  150. package/src/context/index.ts +427 -0
  151. package/src/context/provenance.ts +380 -0
  152. package/src/context/resolve.ts +581 -0
  153. package/src/context/store.ts +503 -0
  154. package/src/context/types.ts +679 -0
  155. package/src/context/utils/atomic.ts +207 -0
  156. package/src/context/utils/credit.ts +224 -0
  157. package/src/context/utils/index.ts +13 -0
  158. package/src/context/utils/utility.ts +200 -0
  159. package/src/core/commitment.ts +129 -67
  160. package/src/core/crypto.ts +13 -0
  161. package/src/index.ts +62 -10
  162. package/src/mcca/engine.ts +5 -4
  163. package/src/physics/engine.ts +40 -3
  164. package/src/provenance/api-handler.ts +248 -0
  165. package/src/provenance/api-types.ts +112 -0
  166. package/src/provenance/index.ts +19 -0
  167. package/src/provenance/provenance-engine.ts +387 -0
  168. package/src/provenance/types.ts +211 -0
  169. package/src/tee/engine.ts +16 -0
  170. package/src/warrant/engine.ts +89 -1
  171. package/src/zk/engine.ts +8 -4
  172. package/tsconfig.json +1 -1
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Caret — Fragment Store Implementation
3
+ * @module @longarcstudios/caret/store
4
+ *
5
+ * Storage interface for context fragments.
6
+ * Provides in-memory implementation and interface for custom backends.
7
+ */
8
+
9
+ import type {
10
+ ContextFragment,
11
+ FragmentStore,
12
+ FragmentId,
13
+ Hash,
14
+ SourceURI,
15
+ Timestamp,
16
+ } from './types.js';
17
+ import { verifyFragment } from './fragment.js';
18
+
19
+ // ============================================================================
20
+ // IN-MEMORY STORE
21
+ // ============================================================================
22
+
23
+ /**
24
+ * In-memory fragment store.
25
+ * Suitable for development, testing, and single-session use.
26
+ */
27
+ export class MemoryStore implements FragmentStore {
28
+ private fragments: Map<FragmentId, ContextFragment> = new Map();
29
+ private hashIndex: Map<Hash, FragmentId> = new Map();
30
+ private sourceIndex: Map<SourceURI, Set<FragmentId>> = new Map();
31
+ private deletedIds: Set<FragmentId> = new Set();
32
+ private seal_key: string;
33
+
34
+ constructor(seal_key: string) {
35
+ this.seal_key = seal_key;
36
+ }
37
+
38
+ async put(fragment: ContextFragment): Promise<void> {
39
+ this.fragments.set(fragment.id, fragment);
40
+ this.hashIndex.set(fragment.hash, fragment.id);
41
+
42
+ const source = fragment.provenance.head.source;
43
+ if (!this.sourceIndex.has(source)) {
44
+ this.sourceIndex.set(source, new Set());
45
+ }
46
+ this.sourceIndex.get(source)!.add(fragment.id);
47
+ this.deletedIds.delete(fragment.id);
48
+ }
49
+
50
+ async get(id: FragmentId): Promise<ContextFragment | null> {
51
+ if (this.deletedIds.has(id)) return null;
52
+ return this.fragments.get(id) ?? null;
53
+ }
54
+
55
+ async getByHash(hash: Hash): Promise<ContextFragment | null> {
56
+ const id = this.hashIndex.get(hash);
57
+ if (!id || this.deletedIds.has(id)) return null;
58
+ return this.fragments.get(id) ?? null;
59
+ }
60
+
61
+ async queryBySource(source: SourceURI): Promise<readonly ContextFragment[]> {
62
+ const ids = this.sourceIndex.get(source);
63
+ if (!ids) return [];
64
+
65
+ const results: ContextFragment[] = [];
66
+ for (const id of ids) {
67
+ if (!this.deletedIds.has(id)) {
68
+ const fragment = this.fragments.get(id);
69
+ if (fragment) results.push(fragment);
70
+ }
71
+ }
72
+ return results;
73
+ }
74
+
75
+ async queryByTime(from: Timestamp, to: Timestamp): Promise<readonly ContextFragment[]> {
76
+ const fromTime = new Date(from).getTime();
77
+ const toTime = new Date(to).getTime();
78
+ const results: ContextFragment[] = [];
79
+
80
+ for (const [id, fragment] of this.fragments) {
81
+ if (this.deletedIds.has(id)) continue;
82
+ const sealedTime = new Date(fragment.sealed_at).getTime();
83
+ if (sealedTime >= fromTime && sealedTime <= toTime) {
84
+ results.push(fragment);
85
+ }
86
+ }
87
+
88
+ return results.sort((a, b) =>
89
+ new Date(a.sealed_at).getTime() - new Date(b.sealed_at).getTime()
90
+ );
91
+ }
92
+
93
+ async delete(id: FragmentId): Promise<boolean> {
94
+ if (!this.fragments.has(id) || this.deletedIds.has(id)) return false;
95
+ this.deletedIds.add(id);
96
+ return true;
97
+ }
98
+
99
+ async verify(id: FragmentId): Promise<boolean> {
100
+ const fragment = await this.get(id);
101
+ if (!fragment) return false;
102
+ const result = await verifyFragment(fragment, this.seal_key);
103
+ return result.valid;
104
+ }
105
+
106
+ size(): number {
107
+ return this.fragments.size - this.deletedIds.size;
108
+ }
109
+
110
+ async all(): Promise<readonly ContextFragment[]> {
111
+ const results: ContextFragment[] = [];
112
+ for (const [id, fragment] of this.fragments) {
113
+ if (!this.deletedIds.has(id)) results.push(fragment);
114
+ }
115
+ return results;
116
+ }
117
+
118
+ clear(): void {
119
+ this.fragments.clear();
120
+ this.hashIndex.clear();
121
+ this.sourceIndex.clear();
122
+ this.deletedIds.clear();
123
+ }
124
+
125
+ compact(): void {
126
+ for (const id of this.deletedIds) {
127
+ const fragment = this.fragments.get(id);
128
+ if (fragment) {
129
+ this.hashIndex.delete(fragment.hash);
130
+ this.sourceIndex.get(fragment.provenance.head.source)?.delete(id);
131
+ }
132
+ this.fragments.delete(id);
133
+ }
134
+ this.deletedIds.clear();
135
+ }
136
+
137
+ /**
138
+ * Layer 2: Keyword search (lexical).
139
+ * Searches fragment content for matching keywords.
140
+ *
141
+ * @param keywords - Keywords to match (case-insensitive)
142
+ * @param matchMode - 'any' matches if any keyword found, 'all' requires all keywords
143
+ * @returns Matching fragments
144
+ */
145
+ async queryByKeywords(
146
+ keywords: string[],
147
+ matchMode: 'any' | 'all' = 'any'
148
+ ): Promise<readonly ContextFragment[]> {
149
+ if (keywords.length === 0) return [];
150
+
151
+ const normalizedKeywords = keywords.map(k => k.toLowerCase());
152
+ const results: ContextFragment[] = [];
153
+
154
+ for (const [id, fragment] of this.fragments) {
155
+ if (this.deletedIds.has(id)) continue;
156
+
157
+ // Convert fragment content to searchable text
158
+ const text = this.fragmentToSearchableText(fragment).toLowerCase();
159
+
160
+ const matches = normalizedKeywords.map(keyword => text.includes(keyword));
161
+
162
+ const isMatch = matchMode === 'any'
163
+ ? matches.some(Boolean)
164
+ : matches.every(Boolean);
165
+
166
+ if (isMatch) {
167
+ results.push(fragment);
168
+ }
169
+ }
170
+
171
+ return results;
172
+ }
173
+
174
+ /**
175
+ * Convert fragment to searchable text.
176
+ * Extracts text from content and source.
177
+ */
178
+ private fragmentToSearchableText(fragment: ContextFragment): string {
179
+ const parts: string[] = [];
180
+
181
+ // Content
182
+ if (typeof fragment.content.data === 'string') {
183
+ parts.push(fragment.content.data);
184
+ } else if (fragment.content.data !== null && typeof fragment.content.data === 'object') {
185
+ parts.push(JSON.stringify(fragment.content.data));
186
+ }
187
+
188
+ // Source URI
189
+ parts.push(fragment.provenance.head.source);
190
+
191
+ return parts.join(' ');
192
+ }
193
+ }
194
+
195
+ // ============================================================================
196
+ // CACHED STORE
197
+ // ============================================================================
198
+
199
+ export interface CacheConfig {
200
+ maxSize: number;
201
+ ttlMs?: number;
202
+ }
203
+
204
+ export class CachedStore implements FragmentStore {
205
+ private backend: FragmentStore;
206
+ private cache: Map<FragmentId, { fragment: ContextFragment; timestamp: number }>;
207
+ private maxSize: number;
208
+ private ttlMs: number;
209
+
210
+ constructor(backend: FragmentStore, config: CacheConfig) {
211
+ this.backend = backend;
212
+ this.cache = new Map();
213
+ this.maxSize = config.maxSize;
214
+ this.ttlMs = config.ttlMs ?? 300000;
215
+ }
216
+
217
+ async put(fragment: ContextFragment): Promise<void> {
218
+ await this.backend.put(fragment);
219
+ this.cacheFragment(fragment);
220
+ }
221
+
222
+ async get(id: FragmentId): Promise<ContextFragment | null> {
223
+ const cached = this.cache.get(id);
224
+ if (cached && Date.now() - cached.timestamp < this.ttlMs) {
225
+ this.cache.delete(id);
226
+ this.cache.set(id, cached);
227
+ return cached.fragment;
228
+ }
229
+ if (cached) this.cache.delete(id);
230
+
231
+ const fragment = await this.backend.get(id);
232
+ if (fragment) this.cacheFragment(fragment);
233
+ return fragment;
234
+ }
235
+
236
+ async getByHash(hash: Hash): Promise<ContextFragment | null> {
237
+ const fragment = await this.backend.getByHash(hash);
238
+ if (fragment) this.cacheFragment(fragment);
239
+ return fragment;
240
+ }
241
+
242
+ async queryBySource(source: SourceURI): Promise<readonly ContextFragment[]> {
243
+ return this.backend.queryBySource(source);
244
+ }
245
+
246
+ async queryByTime(from: Timestamp, to: Timestamp): Promise<readonly ContextFragment[]> {
247
+ return this.backend.queryByTime(from, to);
248
+ }
249
+
250
+ async delete(id: FragmentId): Promise<boolean> {
251
+ this.cache.delete(id);
252
+ return this.backend.delete(id);
253
+ }
254
+
255
+ async verify(id: FragmentId): Promise<boolean> {
256
+ return this.backend.verify(id);
257
+ }
258
+
259
+ private cacheFragment(fragment: ContextFragment): void {
260
+ if (this.cache.size >= this.maxSize) {
261
+ const oldest = this.cache.keys().next().value;
262
+ if (oldest) this.cache.delete(oldest);
263
+ }
264
+ this.cache.set(fragment.id, { fragment, timestamp: Date.now() });
265
+ }
266
+
267
+ clearCache(): void {
268
+ this.cache.clear();
269
+ }
270
+
271
+ cacheStats(): { size: number; maxSize: number } {
272
+ return { size: this.cache.size, maxSize: this.maxSize };
273
+ }
274
+ }
275
+
276
+ // ============================================================================
277
+ // COMPOSITE STORE
278
+ // ============================================================================
279
+
280
+ export class CompositeStore implements FragmentStore {
281
+ private primary: FragmentStore;
282
+ private replicas: FragmentStore[];
283
+
284
+ constructor(primary: FragmentStore, replicas: FragmentStore[] = []) {
285
+ this.primary = primary;
286
+ this.replicas = replicas;
287
+ }
288
+
289
+ async put(fragment: ContextFragment): Promise<void> {
290
+ await Promise.all([
291
+ this.primary.put(fragment),
292
+ ...this.replicas.map(r => r.put(fragment)),
293
+ ]);
294
+ }
295
+
296
+ async get(id: FragmentId): Promise<ContextFragment | null> {
297
+ return this.primary.get(id);
298
+ }
299
+
300
+ async getByHash(hash: Hash): Promise<ContextFragment | null> {
301
+ return this.primary.getByHash(hash);
302
+ }
303
+
304
+ async queryBySource(source: SourceURI): Promise<readonly ContextFragment[]> {
305
+ return this.primary.queryBySource(source);
306
+ }
307
+
308
+ async queryByTime(from: Timestamp, to: Timestamp): Promise<readonly ContextFragment[]> {
309
+ return this.primary.queryByTime(from, to);
310
+ }
311
+
312
+ async delete(id: FragmentId): Promise<boolean> {
313
+ const results = await Promise.all([
314
+ this.primary.delete(id),
315
+ ...this.replicas.map(r => r.delete(id)),
316
+ ]);
317
+ return results[0];
318
+ }
319
+
320
+ async verify(id: FragmentId): Promise<boolean> {
321
+ return this.primary.verify(id);
322
+ }
323
+ }
324
+
325
+ // ============================================================================
326
+ // STORE FACTORY
327
+ // ============================================================================
328
+
329
+ export type StoreType = 'memory' | 'cached-memory';
330
+
331
+ export interface StoreOptions {
332
+ type: StoreType;
333
+ seal_key: string;
334
+ cache?: CacheConfig;
335
+ }
336
+
337
+ export function createStore(options: StoreOptions): FragmentStore {
338
+ switch (options.type) {
339
+ case 'memory':
340
+ return new MemoryStore(options.seal_key);
341
+ case 'cached-memory':
342
+ const memory = new MemoryStore(options.seal_key);
343
+ return new CachedStore(memory, options.cache ?? { maxSize: 1000 });
344
+ default:
345
+ throw new Error(`Unknown store type: ${options.type}`);
346
+ }
347
+ }
348
+
349
+ // ============================================================================
350
+ // EXTENDED MEMORY STORE (Three-Layer Index)
351
+ // ============================================================================
352
+
353
+ /**
354
+ * Embedding function type.
355
+ * Takes text and returns embedding vector.
356
+ */
357
+ export type EmbeddingFunction = (text: string) => Promise<Float32Array>;
358
+
359
+ /**
360
+ * Extended memory store with Three-Layer Index support.
361
+ *
362
+ * Layer 1: Semantic (embedding-based similarity)
363
+ * Layer 2: Lexical (keyword search)
364
+ * Layer 3: Symbolic (structured metadata — inherited from MemoryStore)
365
+ */
366
+ export class ExtendedMemoryStore extends MemoryStore {
367
+ private embeddings: Map<FragmentId, Float32Array> = new Map();
368
+ private embeddingFn: EmbeddingFunction | null = null;
369
+
370
+ /**
371
+ * Set the embedding function for semantic search.
372
+ *
373
+ * @param fn - Function that converts text to embedding vector
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * store.setEmbeddingFunction(async (text) => {
378
+ * // Use OpenAI, local model, etc.
379
+ * return await embed(text);
380
+ * });
381
+ * ```
382
+ */
383
+ setEmbeddingFunction(fn: EmbeddingFunction): void {
384
+ this.embeddingFn = fn;
385
+ }
386
+
387
+ /**
388
+ * Store fragment and compute embedding if embedding function is set.
389
+ */
390
+ async put(fragment: ContextFragment): Promise<void> {
391
+ await super.put(fragment);
392
+
393
+ // Compute and store embedding if function available
394
+ if (this.embeddingFn) {
395
+ const text = this.fragmentToText(fragment);
396
+ const embedding = await this.embeddingFn(text);
397
+ this.embeddings.set(fragment.id, embedding);
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Manually set embedding for a fragment.
403
+ * Useful when embeddings are pre-computed.
404
+ */
405
+ setEmbedding(id: FragmentId, embedding: Float32Array): void {
406
+ this.embeddings.set(id, embedding);
407
+ }
408
+
409
+ /**
410
+ * Layer 1: Semantic similarity search.
411
+ * Requires embedding function to be set.
412
+ *
413
+ * @param embedding - Query embedding vector
414
+ * @param threshold - Minimum cosine similarity (0.0-1.0)
415
+ * @param limit - Maximum results
416
+ * @returns Matching fragments with similarity scores
417
+ */
418
+ async queryBySimilarity(
419
+ embedding: Float32Array,
420
+ threshold: number = 0.7,
421
+ limit: number = 10
422
+ ): Promise<readonly { fragment: ContextFragment; similarity: number }[]> {
423
+ const results: { fragment: ContextFragment; similarity: number }[] = [];
424
+
425
+ const allFragments = await this.all();
426
+
427
+ for (const fragment of allFragments) {
428
+ const storedEmbedding = this.embeddings.get(fragment.id);
429
+ if (!storedEmbedding) continue;
430
+
431
+ const similarity = cosineSimilarity(embedding, storedEmbedding);
432
+
433
+ if (similarity >= threshold) {
434
+ results.push({ fragment, similarity });
435
+ }
436
+ }
437
+
438
+ // Sort by similarity descending
439
+ results.sort((a, b) => b.similarity - a.similarity);
440
+
441
+ // Apply limit
442
+ return results.slice(0, limit);
443
+ }
444
+
445
+ /**
446
+ * Convert fragment to text for embedding.
447
+ */
448
+ private fragmentToText(fragment: ContextFragment): string {
449
+ const parts: string[] = [];
450
+
451
+ if (typeof fragment.content.data === 'string') {
452
+ parts.push(fragment.content.data);
453
+ } else if (fragment.content.data !== null && typeof fragment.content.data === 'object') {
454
+ parts.push(JSON.stringify(fragment.content.data));
455
+ }
456
+
457
+ return parts.join(' ');
458
+ }
459
+
460
+ /**
461
+ * Clear embeddings cache.
462
+ */
463
+ clearEmbeddings(): void {
464
+ this.embeddings.clear();
465
+ }
466
+
467
+ /**
468
+ * Get embedding statistics.
469
+ */
470
+ embeddingStats(): { count: number; fragmentCount: number } {
471
+ return {
472
+ count: this.embeddings.size,
473
+ fragmentCount: this.size(),
474
+ };
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Compute cosine similarity between two vectors.
480
+ */
481
+ function cosineSimilarity(a: Float32Array, b: Float32Array): number {
482
+ if (a.length !== b.length) {
483
+ throw new Error(`Embedding dimension mismatch: ${a.length} vs ${b.length}`);
484
+ }
485
+
486
+ let dotProduct = 0;
487
+ let normA = 0;
488
+ let normB = 0;
489
+
490
+ for (let i = 0; i < a.length; i++) {
491
+ const aVal = a[i] ?? 0;
492
+ const bVal = b[i] ?? 0;
493
+ dotProduct += aVal * bVal;
494
+ normA += aVal * aVal;
495
+ normB += bVal * bVal;
496
+ }
497
+
498
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
499
+
500
+ if (denominator === 0) return 0;
501
+
502
+ return dotProduct / denominator;
503
+ }