@fgv/ts-agent-memory 5.1.0-39 → 5.1.0-41

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 (210) hide show
  1. package/.rush/temp/{b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log → cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log} +14 -2
  2. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
  3. package/.rush/temp/operation/build/all.log +2 -2
  4. package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
  5. package/.rush/temp/operation/build/state.json +1 -1
  6. package/dist/packlets/converters/envelopeConverter.js +17 -3
  7. package/dist/packlets/converters/envelopeConverter.js.map +1 -1
  8. package/dist/packlets/index/memoryIndex.js +58 -10
  9. package/dist/packlets/index/memoryIndex.js.map +1 -1
  10. package/dist/packlets/ingest/cycleGuard.js +13 -6
  11. package/dist/packlets/ingest/cycleGuard.js.map +1 -1
  12. package/dist/packlets/ingest/hostStages.js.map +1 -1
  13. package/dist/packlets/ingest/model.js.map +1 -1
  14. package/dist/packlets/ingest/orchestrator.js +94 -49
  15. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  16. package/dist/packlets/retrieve/hybridRetriever.js +13 -2
  17. package/dist/packlets/retrieve/hybridRetriever.js.map +1 -1
  18. package/dist/packlets/retrieve/linkTraversalRetriever.js +46 -57
  19. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  20. package/dist/packlets/retrieve/recencyRetriever.js +3 -3
  21. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
  22. package/dist/packlets/retrieve/retriever.js +50 -7
  23. package/dist/packlets/retrieve/retriever.js.map +1 -1
  24. package/dist/packlets/retrieve/semanticRetriever.js +9 -3
  25. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
  26. package/dist/packlets/retrieve/structuredFilterRetriever.js +3 -3
  27. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  28. package/dist/packlets/retrieve/tagRetriever.js +3 -3
  29. package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
  30. package/dist/packlets/retrieve/temporalRetrievers.js +3 -3
  31. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
  32. package/dist/packlets/store/fileTreeMemoryStore.js +72 -16
  33. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  34. package/dist/packlets/tools/memoryTools.js +104 -21
  35. package/dist/packlets/tools/memoryTools.js.map +1 -1
  36. package/dist/packlets/types/envelope.js +13 -1
  37. package/dist/packlets/types/envelope.js.map +1 -1
  38. package/dist/packlets/vector/inMemoryCosineIndex.js +22 -17
  39. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  40. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  41. package/dist/test/unit/converters/antagonistRoundTrip.test.js +3 -3
  42. package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
  43. package/dist/test/unit/converters/envelopeConverter.test.js +125 -8
  44. package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -1
  45. package/dist/test/unit/index/memoryIndex.test.js +87 -25
  46. package/dist/test/unit/index/memoryIndex.test.js.map +1 -1
  47. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
  48. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
  49. package/dist/test/unit/ingest/cycleGuard.test.js +28 -1
  50. package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -1
  51. package/dist/test/unit/ingest/orchestrator.test.js +187 -45
  52. package/dist/test/unit/ingest/orchestrator.test.js.map +1 -1
  53. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
  54. package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
  55. package/dist/test/unit/retrieve/retrievers.test.js +299 -37
  56. package/dist/test/unit/retrieve/retrievers.test.js.map +1 -1
  57. package/dist/test/unit/store/embedOnWrite.test.js +69 -12
  58. package/dist/test/unit/store/embedOnWrite.test.js.map +1 -1
  59. package/dist/test/unit/store/listScoped.test.js +109 -0
  60. package/dist/test/unit/store/listScoped.test.js.map +1 -0
  61. package/dist/test/unit/store/rankAxis.test.js +254 -0
  62. package/dist/test/unit/store/rankAxis.test.js.map +1 -0
  63. package/dist/test/unit/tools/memoryTools.test.js +280 -11
  64. package/dist/test/unit/tools/memoryTools.test.js.map +1 -1
  65. package/dist/test/unit/types/writePolicy.test.js +9 -2
  66. package/dist/test/unit/types/writePolicy.test.js.map +1 -1
  67. package/dist/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
  68. package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
  69. package/dist/test/unit/vector/vectorIndex.test.js +24 -15
  70. package/dist/test/unit/vector/vectorIndex.test.js.map +1 -1
  71. package/dist/ts-agent-memory.d.ts +384 -97
  72. package/etc/ts-agent-memory.api.md +64 -22
  73. package/lib/packlets/converters/envelopeConverter.d.ts +8 -1
  74. package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -1
  75. package/lib/packlets/converters/envelopeConverter.js +18 -4
  76. package/lib/packlets/converters/envelopeConverter.js.map +1 -1
  77. package/lib/packlets/index/memoryIndex.d.ts +42 -10
  78. package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
  79. package/lib/packlets/index/memoryIndex.js +58 -10
  80. package/lib/packlets/index/memoryIndex.js.map +1 -1
  81. package/lib/packlets/ingest/cycleGuard.d.ts +5 -5
  82. package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -1
  83. package/lib/packlets/ingest/cycleGuard.js +13 -6
  84. package/lib/packlets/ingest/cycleGuard.js.map +1 -1
  85. package/lib/packlets/ingest/hostStages.d.ts +3 -3
  86. package/lib/packlets/ingest/hostStages.d.ts.map +1 -1
  87. package/lib/packlets/ingest/hostStages.js.map +1 -1
  88. package/lib/packlets/ingest/model.d.ts +25 -14
  89. package/lib/packlets/ingest/model.d.ts.map +1 -1
  90. package/lib/packlets/ingest/model.js.map +1 -1
  91. package/lib/packlets/ingest/orchestrator.d.ts +18 -4
  92. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  93. package/lib/packlets/ingest/orchestrator.js +93 -48
  94. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  95. package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -1
  96. package/lib/packlets/retrieve/hybridRetriever.js +12 -1
  97. package/lib/packlets/retrieve/hybridRetriever.js.map +1 -1
  98. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +18 -23
  99. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
  100. package/lib/packlets/retrieve/linkTraversalRetriever.js +45 -56
  101. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  102. package/lib/packlets/retrieve/recencyRetriever.js +2 -2
  103. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
  104. package/lib/packlets/retrieve/retriever.d.ts +70 -11
  105. package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
  106. package/lib/packlets/retrieve/retriever.js +52 -7
  107. package/lib/packlets/retrieve/retriever.js.map +1 -1
  108. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
  109. package/lib/packlets/retrieve/semanticRetriever.js +9 -3
  110. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
  111. package/lib/packlets/retrieve/structuredFilterRetriever.js +2 -2
  112. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  113. package/lib/packlets/retrieve/tagRetriever.js +2 -2
  114. package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
  115. package/lib/packlets/retrieve/temporalRetrievers.js +3 -3
  116. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
  117. package/lib/packlets/store/fileTreeMemoryStore.d.ts +57 -3
  118. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  119. package/lib/packlets/store/fileTreeMemoryStore.js +72 -16
  120. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  121. package/lib/packlets/tools/memoryTools.d.ts +24 -0
  122. package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
  123. package/lib/packlets/tools/memoryTools.js +104 -21
  124. package/lib/packlets/tools/memoryTools.js.map +1 -1
  125. package/lib/packlets/types/envelope.d.ts +61 -8
  126. package/lib/packlets/types/envelope.d.ts.map +1 -1
  127. package/lib/packlets/types/envelope.js +14 -0
  128. package/lib/packlets/types/envelope.js.map +1 -1
  129. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -5
  130. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  131. package/lib/packlets/vector/inMemoryCosineIndex.js +22 -17
  132. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  133. package/lib/packlets/vector/vectorIndex.d.ts +46 -20
  134. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  135. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  136. package/lib/test/unit/converters/antagonistRoundTrip.test.js +3 -3
  137. package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
  138. package/lib/test/unit/converters/envelopeConverter.test.js +124 -7
  139. package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -1
  140. package/lib/test/unit/index/memoryIndex.test.js +86 -24
  141. package/lib/test/unit/index/memoryIndex.test.js.map +1 -1
  142. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
  143. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
  144. package/lib/test/unit/ingest/cycleGuard.test.js +28 -1
  145. package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -1
  146. package/lib/test/unit/ingest/orchestrator.test.js +186 -44
  147. package/lib/test/unit/ingest/orchestrator.test.js.map +1 -1
  148. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
  149. package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
  150. package/lib/test/unit/retrieve/retrievers.test.js +298 -36
  151. package/lib/test/unit/retrieve/retrievers.test.js.map +1 -1
  152. package/lib/test/unit/store/embedOnWrite.test.js +68 -11
  153. package/lib/test/unit/store/embedOnWrite.test.js.map +1 -1
  154. package/lib/test/unit/store/listScoped.test.d.ts +2 -0
  155. package/lib/test/unit/store/listScoped.test.d.ts.map +1 -0
  156. package/lib/test/unit/store/listScoped.test.js +111 -0
  157. package/lib/test/unit/store/listScoped.test.js.map +1 -0
  158. package/lib/test/unit/store/rankAxis.test.d.ts +2 -0
  159. package/lib/test/unit/store/rankAxis.test.d.ts.map +1 -0
  160. package/lib/test/unit/store/rankAxis.test.js +256 -0
  161. package/lib/test/unit/store/rankAxis.test.js.map +1 -0
  162. package/lib/test/unit/tools/memoryTools.test.js +280 -11
  163. package/lib/test/unit/tools/memoryTools.test.js.map +1 -1
  164. package/lib/test/unit/types/writePolicy.test.js +9 -2
  165. package/lib/test/unit/types/writePolicy.test.js.map +1 -1
  166. package/lib/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
  167. package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
  168. package/lib/test/unit/vector/vectorIndex.test.js +24 -15
  169. package/lib/test/unit/vector/vectorIndex.test.js.map +1 -1
  170. package/package.json +7 -7
  171. package/rush-logs/ts-agent-memory.build.cache.log +1 -1
  172. package/rush-logs/ts-agent-memory.build.log +2 -2
  173. package/src/packlets/converters/envelopeConverter.ts +27 -4
  174. package/src/packlets/index/memoryIndex.ts +86 -22
  175. package/src/packlets/ingest/cycleGuard.ts +22 -11
  176. package/src/packlets/ingest/hostStages.ts +3 -3
  177. package/src/packlets/ingest/model.ts +25 -14
  178. package/src/packlets/ingest/orchestrator.ts +143 -67
  179. package/src/packlets/retrieve/hybridRetriever.ts +14 -1
  180. package/src/packlets/retrieve/linkTraversalRetriever.ts +51 -62
  181. package/src/packlets/retrieve/recencyRetriever.ts +3 -3
  182. package/src/packlets/retrieve/retriever.ts +97 -13
  183. package/src/packlets/retrieve/semanticRetriever.ts +10 -5
  184. package/src/packlets/retrieve/structuredFilterRetriever.ts +3 -3
  185. package/src/packlets/retrieve/tagRetriever.ts +3 -3
  186. package/src/packlets/retrieve/temporalRetrievers.ts +3 -3
  187. package/src/packlets/store/fileTreeMemoryStore.ts +117 -12
  188. package/src/packlets/tools/memoryTools.ts +152 -25
  189. package/src/packlets/types/envelope.ts +66 -8
  190. package/src/packlets/vector/inMemoryCosineIndex.ts +45 -22
  191. package/src/packlets/vector/vectorIndex.ts +47 -20
  192. package/src/test/unit/converters/antagonistRoundTrip.test.ts +3 -3
  193. package/src/test/unit/converters/envelopeConverter.test.ts +168 -11
  194. package/src/test/unit/index/memoryIndex.test.ts +99 -14
  195. package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +23 -18
  196. package/src/test/unit/ingest/cycleGuard.test.ts +44 -2
  197. package/src/test/unit/ingest/orchestrator.test.ts +234 -41
  198. package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +134 -35
  199. package/src/test/unit/retrieve/retrievers.test.ts +381 -25
  200. package/src/test/unit/store/embedOnWrite.test.ts +83 -11
  201. package/src/test/unit/store/listScoped.test.ts +138 -0
  202. package/src/test/unit/store/rankAxis.test.ts +349 -0
  203. package/src/test/unit/tools/memoryTools.test.ts +362 -13
  204. package/src/test/unit/types/writePolicy.test.ts +11 -2
  205. package/src/test/unit/vector/inMemoryCosineIndex.test.ts +115 -39
  206. package/src/test/unit/vector/vectorIndex.test.ts +33 -17
  207. package/temp/build/lint/_eslint-5eVG3S6w.json +41 -33
  208. package/temp/build/typescript/ts_8nwakTlr.json +1 -1
  209. package/temp/ts-agent-memory.api.json +1080 -136
  210. package/temp/ts-agent-memory.api.md +64 -22
@@ -91,10 +91,32 @@ function candidate(kind, entityId, body, opts = {}) {
91
91
  body
92
92
  };
93
93
  }
94
+ /**
95
+ * A scope-qualified target, authored as `scope#id` or a bare id defaulting to the
96
+ * knowledge scope (where the note/num fixtures live). `kt` is the assertion-side
97
+ * spelling of the same helper.
98
+ */
99
+ function scopedTarget(spec) {
100
+ const hash = spec.indexOf('#');
101
+ const scope = hash >= 0 ? spec.slice(0, hash) : 'knowledge';
102
+ const id = hash >= 0 ? spec.slice(hash + 1) : spec;
103
+ return { scope: scope, id: id };
104
+ }
105
+ function kt(id, scope = 'knowledge') {
106
+ return { scope: scope, id: id };
107
+ }
108
+ /** The scope-qualified reference of a candidate about to be written, by its entityId. */
109
+ function srcOf(ctx, entityId) {
110
+ const found = ctx.candidates.find((rc) => rc.candidate.envelope.entityId === entityId);
111
+ if (found === undefined) {
112
+ throw new Error(`test relater: no candidate with entityId '${entityId}'`);
113
+ }
114
+ return found.id;
115
+ }
94
116
  function candEdge(source, type, target, confidence) {
95
117
  return {
96
- source: source,
97
- edge: Object.assign({ type: type, target: target }, (confidence !== undefined ? { confidence } : {}))
118
+ source: scopedTarget(source),
119
+ edge: Object.assign({ type: type, target: scopedTarget(target) }, (confidence !== undefined ? { confidence } : {}))
98
120
  };
99
121
  }
100
122
  function buildStore(params = {}) {
@@ -165,7 +187,10 @@ describe('MemoryIngestOrchestrator', () => {
165
187
  test('classifies, extracts, and writes one record end-to-end with host-ingest provenance + derivedFrom', async () => {
166
188
  const store = buildStore();
167
189
  const orch = buildOrchestrator({ store });
168
- const sourceId = 'turn-3';
190
+ const sourceId = {
191
+ scope: 'conversations/c1',
192
+ id: 'turn-3'
193
+ };
169
194
  const result = await orch.ingestItem({ id: 'doc-1', content: 'the sky is blue', sourceId });
170
195
  expect(result).toSucceedAndSatisfy((r) => {
171
196
  var _a, _b, _c;
@@ -176,7 +201,7 @@ describe('MemoryIngestOrchestrator', () => {
176
201
  expect(rec.resolution.verdict).toBe('new');
177
202
  expect(rec.id).toBe('doc-1');
178
203
  expect((_a = rec.record) === null || _a === void 0 ? void 0 : _a.envelope.provenance.source).toBe(index_1.HOST_INGEST_PROVENANCE_SOURCE);
179
- expect((_b = rec.record) === null || _b === void 0 ? void 0 : _b.envelope.provenance.derivedFrom).toBe('turn-3');
204
+ expect((_b = rec.record) === null || _b === void 0 ? void 0 : _b.envelope.provenance.derivedFrom).toEqual(sourceId);
180
205
  expect((_c = rec.record) === null || _c === void 0 ? void 0 : _c.body).toBe('the sky is blue');
181
206
  expect(rec.interlock).toBeUndefined();
182
207
  });
@@ -251,6 +276,25 @@ describe('MemoryIngestOrchestrator', () => {
251
276
  });
252
277
  expect(await orch.ingestItem({ id: 'o-1', content: 'x' })).toFailWith(/no identity codec/);
253
278
  });
279
+ test('fails loudly when a pre-existing snapshot record cannot be scoped (no codec for its kind)', async () => {
280
+ // The store can persist a note (it has the note codec), but the orchestrator
281
+ // below is wired WITHOUT a note codec — so when it snapshots the store to run
282
+ // the edge path, it cannot resolve the seeded note's scope and fails loudly
283
+ // (never silently drops it from the cycle graph). This is the branch the
284
+ // scope-qualified edge change added.
285
+ const store = buildStore();
286
+ await putNote(store, 'legacy', 'anchor');
287
+ const orch = index_1.MemoryIngestOrchestrator.create({
288
+ store,
289
+ registry: registry(),
290
+ // Deliberately omit the note codec (and any default) that the seeded record needs.
291
+ codecs: new Map([[numKind, new index_1.KnowledgeIdentityCodec()]]),
292
+ classifier: classifier(() => (0, ts_utils_1.succeed)({ kind: numKind })),
293
+ extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(numKind, 'n-1', 7)])),
294
+ relationExtractor: noEdges
295
+ }).orThrow();
296
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/cannot resolve scope for stored record 'legacy'.*no identity codec registered for kind 'note'/i);
297
+ });
254
298
  });
255
299
  describe('stage 4 — exact dedup (layer 1)', () => {
256
300
  test('an exact {kind,body} match in scope is duplicate-of (no resolver)', async () => {
@@ -261,7 +305,7 @@ describe('MemoryIngestOrchestrator', () => {
261
305
  });
262
306
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
263
307
  expect(r.records[0].disposition).toBe('deduped');
264
- expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: 'doc-a' });
308
+ expect(r.records[0].resolution).toEqual({ verdict: 'duplicate-of', target: kt('doc-a') });
265
309
  expect(r.records[0].id).toBe('doc-a');
266
310
  });
267
311
  // doc-b was never written.
@@ -318,13 +362,17 @@ async function putNote(store, id, body) {
318
362
  (await store.put(rec)).orThrow();
319
363
  }
320
364
  function mockStore(overrides) {
321
- var _a, _b, _c, _d, _e;
365
+ var _a, _b, _c, _d, _e, _f, _g;
322
366
  return {
323
367
  get: (_a = overrides.get) !== null && _a !== void 0 ? _a : (() => Promise.resolve((0, ts_utils_1.succeed)(undefined))),
324
368
  getById: (_b = overrides.getById) !== null && _b !== void 0 ? _b : (() => Promise.resolve((0, ts_utils_1.succeed)(undefined))),
325
369
  list: (_c = overrides.list) !== null && _c !== void 0 ? _c : (() => Promise.resolve((0, ts_utils_1.succeed)([]))),
326
- put: (_d = overrides.put) !== null && _d !== void 0 ? _d : ((r) => Promise.resolve((0, ts_utils_1.succeed)(r))),
327
- delete: (_e = overrides.delete) !== null && _e !== void 0 ? _e : (() => Promise.resolve((0, ts_utils_1.fail)('n/a')))
370
+ listScoped: (_d = overrides.listScoped) !== null && _d !== void 0 ? _d : (() => Promise.resolve((0, ts_utils_1.succeed)([]))),
371
+ asRecordSource: (_e = overrides.asRecordSource) !== null && _e !== void 0 ? _e : (() => ({
372
+ list: () => Promise.resolve((0, ts_utils_1.succeed)([]))
373
+ })),
374
+ put: (_f = overrides.put) !== null && _f !== void 0 ? _f : ((r) => Promise.resolve((0, ts_utils_1.succeed)(r))),
375
+ delete: (_g = overrides.delete) !== null && _g !== void 0 ? _g : (() => Promise.resolve((0, ts_utils_1.fail)('n/a')))
328
376
  };
329
377
  }
330
378
  describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
@@ -352,7 +400,7 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
352
400
  store,
353
401
  vectorIndex,
354
402
  embed,
355
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'duplicate-of', target: similar[0].id })),
403
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'duplicate-of', target: similar[0].target })),
356
404
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
357
405
  });
358
406
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
@@ -369,12 +417,12 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
369
417
  store,
370
418
  vectorIndex,
371
419
  embed,
372
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'supersede', target: similar[0].id })),
420
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'supersede', target: similar[0].target })),
373
421
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
374
422
  });
375
423
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
376
424
  expect(r.records[0].disposition).toBe('written');
377
- expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: 'doc-a' });
425
+ expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: kt('doc-a') });
378
426
  expect(r.records[0].id).toBe('doc-b');
379
427
  });
380
428
  expect(await store.get(noteKind, 'doc-b')).toSucceedAndSatisfy((rec) => expect(rec === null || rec === void 0 ? void 0 : rec.body).toBe('apple tart'));
@@ -387,7 +435,7 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
387
435
  store,
388
436
  vectorIndex,
389
437
  embed,
390
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].id })),
438
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].target })),
391
439
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
392
440
  });
393
441
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
@@ -406,10 +454,10 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
406
454
  store,
407
455
  vectorIndex,
408
456
  embed,
409
- entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: 'ghost' })),
457
+ entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: kt('ghost') })),
410
458
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
411
459
  });
412
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'ghost' does not exist/);
460
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'knowledge\/ghost' does not exist/);
413
461
  });
414
462
  test('a below-threshold neighbor is not surfaced (resolver not called)', async () => {
415
463
  const vectorIndex = index_1.InMemoryCosineIndex.create().orThrow();
@@ -446,9 +494,9 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
446
494
  });
447
495
  test('a surfaced hit with no backing record is skipped', async () => {
448
496
  const ghostIndex = {
449
- add: (id) => Promise.resolve((0, ts_utils_1.succeed)(id)),
450
- remove: (id) => Promise.resolve((0, ts_utils_1.succeed)(id)),
451
- query: () => Promise.resolve((0, ts_utils_1.succeed)([{ id: 'ghost', score: 0.99 }]))
497
+ add: (t) => Promise.resolve((0, ts_utils_1.succeed)(t.id)),
498
+ remove: (t) => Promise.resolve((0, ts_utils_1.succeed)(t)),
499
+ query: () => Promise.resolve((0, ts_utils_1.succeed)([{ target: kt('ghost'), score: 0.99 }]))
452
500
  };
453
501
  const orch = buildOrchestrator({
454
502
  store: buildStore(),
@@ -474,8 +522,8 @@ describe('MemoryIngestOrchestrator — stage 4 similarity (layer 2)', () => {
474
522
  });
475
523
  test('a similarity-query failure surfaces loudly', async () => {
476
524
  const failingIndex = {
477
- add: (id) => Promise.resolve((0, ts_utils_1.succeed)(id)),
478
- remove: (id) => Promise.resolve((0, ts_utils_1.succeed)(id)),
525
+ add: (t) => Promise.resolve((0, ts_utils_1.succeed)(t.id)),
526
+ remove: (t) => Promise.resolve((0, ts_utils_1.succeed)(t)),
479
527
  query: () => Promise.resolve((0, ts_utils_1.fail)('query kaput'))
480
528
  };
481
529
  const orch = buildOrchestrator({
@@ -547,7 +595,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
547
595
  var _a;
548
596
  expect(r.records[0].edges).toHaveLength(1);
549
597
  expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
550
- { type: 'mentions', target: 'doc-a', confidence: 0.7 }
598
+ { type: 'mentions', target: kt('doc-a'), confidence: 0.7 }
551
599
  ]);
552
600
  });
553
601
  });
@@ -560,7 +608,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
560
608
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
561
609
  var _a;
562
610
  expect(r.records).toHaveLength(2);
563
- expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
611
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
564
612
  expect(r.records[1].edges).toEqual([]);
565
613
  });
566
614
  });
@@ -570,7 +618,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
570
618
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'x')])),
571
619
  relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('ghost', 'rel', 'doc-b')]))
572
620
  });
573
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge source 'ghost' is not a candidate being written/);
621
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge source 'knowledge\/ghost' is not a candidate being written/);
574
622
  });
575
623
  test('rejects an edge whose target resolves to nothing', async () => {
576
624
  const orch = buildOrchestrator({
@@ -578,7 +626,7 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
578
626
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'x')])),
579
627
  relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('doc-b', 'rel', 'ghost')]))
580
628
  });
581
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'ghost' resolves to neither/);
629
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'knowledge\/ghost' resolves to neither/);
582
630
  });
583
631
  test('rejects a cycle-inducing edge (default guard)', async () => {
584
632
  const orch = buildOrchestrator({
@@ -597,8 +645,8 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
597
645
  });
598
646
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
599
647
  var _a, _b;
600
- expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-c' }]);
601
- expect((_b = r.records[1].record) === null || _b === void 0 ? void 0 : _b.envelope.links).toEqual([{ type: 'rel', target: 'doc-b' }]);
648
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-c') }]);
649
+ expect((_b = r.records[1].record) === null || _b === void 0 ? void 0 : _b.envelope.links).toEqual([{ type: 'rel', target: kt('doc-b') }]);
602
650
  });
603
651
  });
604
652
  test('a contradicts edge on a NON-temporal kind persists as a plain link (no interlock)', async () => {
@@ -612,7 +660,89 @@ describe('MemoryIngestOrchestrator — stage 5 relate + cycle guard', () => {
612
660
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
613
661
  var _a;
614
662
  expect(r.records[0].interlock).toBeUndefined();
615
- expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'contradicts', target: 'doc-a' }]);
663
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'contradicts', target: kt('doc-a') }]);
664
+ });
665
+ });
666
+ describe('_validateEdges cross-scope target disambiguation', () => {
667
+ // Two EXISTING records share the idStem 'turn-3' across two conversations
668
+ // (the MTM codec mints per-scope stems). A stage-5 edge target must resolve
669
+ // to the CORRECT (scope, id) — with bare-id keying both would alias and a
670
+ // wrong-scope target would silently validate. Guards the ingest-validation seam.
671
+ const mtmKind = 'mtm';
672
+ function mtmRegistry() {
673
+ const reg = registry();
674
+ reg.register(mtmKind, ts_utils_1.Converters.string);
675
+ return reg;
676
+ }
677
+ const mtmCodecs = new Map([
678
+ [mtmKind, new index_1.MtmIdentityCodec()]
679
+ ]);
680
+ async function seedTurn(store, entityId, body) {
681
+ const addr = new index_1.MtmIdentityCodec().encode(entityId).orThrow();
682
+ const rec = {
683
+ envelope: {
684
+ id: addr.idStem,
685
+ entityId: entityId,
686
+ kind: mtmKind,
687
+ tags: [],
688
+ links: [],
689
+ created: 0,
690
+ updated: 0,
691
+ seq: 0,
692
+ contentHash: '',
693
+ provenance: { source: 'agent' }
694
+ },
695
+ body
696
+ };
697
+ (await store.put(rec)).orThrow();
698
+ }
699
+ function buildMtmOrchestrator(store, relationExtractor) {
700
+ return index_1.MemoryIngestOrchestrator.create({
701
+ store,
702
+ registry: mtmRegistry(),
703
+ codecs: mtmCodecs,
704
+ classifier: classifier(() => (0, ts_utils_1.succeed)({ kind: mtmKind })),
705
+ // The candidate is a fresh turn-5 under conv-a (distinct stem from the seeds).
706
+ extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(mtmKind, 'conv-a:5', 'a new turn')])),
707
+ relationExtractor
708
+ }).orThrow();
709
+ }
710
+ async function seededStore() {
711
+ const store = index_1.FileTreeMemoryStore.create({
712
+ root: mutableRoot(),
713
+ registry: mtmRegistry(),
714
+ codecs: mtmCodecs,
715
+ clock
716
+ }).orThrow();
717
+ await seedTurn(store, 'conv-a:3', 'anchor in conversation a');
718
+ await seedTurn(store, 'conv-b:3', 'anchor in conversation b');
719
+ return store;
720
+ }
721
+ test('accepts a same-stem edge target that resolves to the correct scope', async () => {
722
+ const store = await seededStore();
723
+ const orch = buildMtmOrchestrator(store, relater((ctx) => (0, ts_utils_1.succeed)([
724
+ {
725
+ source: srcOf(ctx, 'conv-a:5'),
726
+ edge: { type: 'rel', target: kt('turn-3', 'conversations/conv-a') }
727
+ }
728
+ ])));
729
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
730
+ var _a;
731
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
732
+ { type: 'rel', target: kt('turn-3', 'conversations/conv-a') }
733
+ ]);
734
+ });
735
+ });
736
+ test('rejects a same-stem edge target whose scope has no matching record', async () => {
737
+ const store = await seededStore();
738
+ const orch = buildMtmOrchestrator(store, relater((ctx) => (0, ts_utils_1.succeed)([
739
+ {
740
+ source: srcOf(ctx, 'conv-a:5'),
741
+ // turn-3 exists under conv-a and conv-b, but NOT conv-z.
742
+ edge: { type: 'rel', target: kt('turn-3', 'conversations/conv-z') }
743
+ }
744
+ ])));
745
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/edge target 'conversations\/conv-z\/turn-3' resolves to neither a sibling candidate nor an existing record/);
616
746
  });
617
747
  });
618
748
  });
@@ -633,13 +763,20 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
633
763
  store,
634
764
  classifier: classifyFact,
635
765
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(factKind, 'fact-1', 'the sky is grey')])),
636
- relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('fact-1', 'contradicts', v1Id)]))
766
+ relationExtractor: relater((ctx) => (0, ts_utils_1.succeed)([
767
+ {
768
+ source: srcOf(ctx, 'fact-1'),
769
+ edge: { type: index_1.CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
770
+ }
771
+ ]))
637
772
  });
638
773
  expect(await secondOrch.ingestItem({ id: 'fact-1', content: 'the sky is grey' })).toSucceedAndSatisfy((r) => {
639
774
  var _a;
640
775
  expect(r.records[0].interlock).toBe('temporal-versioned');
641
776
  expect(r.records[0].disposition).toBe('written');
642
- expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: index_1.CONTRADICTS_LINK_TYPE, target: v1Id }]);
777
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([
778
+ { type: index_1.CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
779
+ ]);
643
780
  });
644
781
  // The current version is the new (contradicting) one.
645
782
  expect(await store.get(factKind, 'fact-1')).toSucceedAndSatisfy((rec) => expect(rec === null || rec === void 0 ? void 0 : rec.body).toBe('the sky is grey'));
@@ -661,7 +798,12 @@ describe('MemoryIngestOrchestrator — contradicts→temporal interlock', () =>
661
798
  store,
662
799
  classifier: classifyFact,
663
800
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(factKind, 'fact-1', 'grey')])),
664
- relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('fact-1', 'contradicts', v1Id)]))
801
+ relationExtractor: relater((ctx) => (0, ts_utils_1.succeed)([
802
+ {
803
+ source: srcOf(ctx, 'fact-1'),
804
+ edge: { type: index_1.CONTRADICTS_LINK_TYPE, target: kt(v1Id, 'facts/entities/fact-1') }
805
+ }
806
+ ]))
665
807
  });
666
808
  (await o2.ingestItem({ id: 'fact-1', content: 'grey' })).orThrow();
667
809
  // Re-introduce the ORIGINAL body: the invalidated v1 must be skipped by exact
@@ -792,7 +934,7 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
792
934
  });
793
935
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r) => {
794
936
  var _a;
795
- expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: 'doc-a' }]);
937
+ expect((_a = r.records[0].record) === null || _a === void 0 ? void 0 : _a.envelope.links).toEqual([{ type: 'rel', target: kt('doc-a') }]);
796
938
  });
797
939
  });
798
940
  test('two candidates merging into the same target do not duplicate a shared edge', async () => {
@@ -804,13 +946,13 @@ describe('MemoryIngestOrchestrator — review-punch-list fixes', () => {
804
946
  store,
805
947
  vectorIndex,
806
948
  embed,
807
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].id })),
949
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].target })),
808
950
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart'), candidate(noteKind, 'doc-c', 'apple tart')])),
809
951
  relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('doc-a', 'rel', 'doc-x')]))
810
952
  });
811
953
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceed();
812
954
  expect(await store.get(noteKind, 'doc-a')).toSucceedAndSatisfy((rec) => {
813
- expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([{ type: 'rel', target: 'doc-x' }]);
955
+ expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([{ type: 'rel', target: kt('doc-x') }]);
814
956
  });
815
957
  });
816
958
  });
@@ -840,18 +982,18 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
840
982
  const store = buildStore({ vectorIndex, embed });
841
983
  await putFull(store, noteKind, 'doc-a', 'apple pie', {
842
984
  tags: ['old-tag'],
843
- links: [{ type: 'existing', target: 'doc-z' }]
985
+ links: [{ type: 'existing', target: kt('doc-z') }]
844
986
  });
845
987
  await putFull(store, noteKind, 'doc-x', 'target of stage-5');
846
988
  const orch = buildOrchestrator({
847
989
  store,
848
990
  vectorIndex,
849
991
  embed,
850
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].id })),
992
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].target })),
851
993
  extractor: extractor(() => (0, ts_utils_1.succeed)([
852
994
  candidate(noteKind, 'doc-b', 'apple tart', {
853
995
  tags: ['new-tag'],
854
- links: [{ type: 'candlink', target: 'doc-z' }]
996
+ links: [{ type: 'candlink', target: kt('doc-z') }]
855
997
  })
856
998
  ])),
857
999
  relationExtractor: relater(() => (0, ts_utils_1.succeed)([candEdge('doc-a', 'stage5', 'doc-x')]))
@@ -865,9 +1007,9 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
865
1007
  expect([...((_a = rec === null || rec === void 0 ? void 0 : rec.envelope.tags) !== null && _a !== void 0 ? _a : [])].sort()).toEqual(['new-tag', 'old-tag']);
866
1008
  // Original link + candidate link + stage-5 edge, each exactly once.
867
1009
  expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([
868
- { type: 'existing', target: 'doc-z' },
869
- { type: 'candlink', target: 'doc-z' },
870
- { type: 'stage5', target: 'doc-x' }
1010
+ { type: 'existing', target: kt('doc-z') },
1011
+ { type: 'candlink', target: kt('doc-z') },
1012
+ { type: 'stage5', target: kt('doc-x') }
871
1013
  ]);
872
1014
  });
873
1015
  });
@@ -880,10 +1022,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
880
1022
  store,
881
1023
  vectorIndex,
882
1024
  embed,
883
- entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].id })),
1025
+ entityResolver: resolver((__c, similar) => (0, ts_utils_1.succeed)({ verdict: 'merge-into', target: similar[0].target })),
884
1026
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
885
1027
  });
886
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'conv-1' is kind 'ltm' but the candidate is kind 'note'/);
1028
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/merge-into target 'conversations\/conv-1' is kind 'ltm' but the candidate is kind 'note'/);
887
1029
  });
888
1030
  test('supersede with a non-existent target fails loudly', async () => {
889
1031
  const vectorIndex = index_1.InMemoryCosineIndex.create().orThrow();
@@ -893,10 +1035,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
893
1035
  store,
894
1036
  vectorIndex,
895
1037
  embed,
896
- entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'supersede', target: 'ghost' })),
1038
+ entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'supersede', target: kt('ghost') })),
897
1039
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
898
1040
  });
899
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/supersede target 'ghost' does not exist/);
1041
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/supersede target 'knowledge\/ghost' does not exist/);
900
1042
  });
901
1043
  test('duplicate-of with a non-existent target fails loudly', async () => {
902
1044
  const vectorIndex = index_1.InMemoryCosineIndex.create().orThrow();
@@ -906,10 +1048,10 @@ describe('MemoryIngestOrchestrator — merge-into safety (second review)', () =>
906
1048
  store,
907
1049
  vectorIndex,
908
1050
  embed,
909
- entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'duplicate-of', target: 'ghost' })),
1051
+ entityResolver: resolver(() => (0, ts_utils_1.succeed)({ verdict: 'duplicate-of', target: kt('ghost') })),
910
1052
  extractor: extractor(() => (0, ts_utils_1.succeed)([candidate(noteKind, 'doc-b', 'apple tart')]))
911
1053
  });
912
- expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/duplicate-of target 'ghost' does not exist/);
1054
+ expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(/duplicate-of target 'knowledge\/ghost' does not exist/);
913
1055
  });
914
1056
  });
915
1057
  //# sourceMappingURL=orchestrator.test.js.map