@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
@@ -46,7 +46,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
46
46
  links: [
47
47
  {
48
48
  type: 'rel' as never,
49
- target: 'doc-2' as never,
49
+ target: { scope: 'knowledge', id: 'doc-2' } as never,
50
50
  confidence: 0.42,
51
51
  provenance: { source: 'agent', by: 'curator', extra: { nested: true } },
52
52
  valid_at: 111,
@@ -63,7 +63,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
63
63
  by: 'erik',
64
64
  model: 'gpt-5',
65
65
  confidence: 0.87,
66
- derivedFrom: 'turn-3' as never,
66
+ derivedFrom: { scope: 'conversations/c1', id: 'turn-3' } as never,
67
67
  // Opaque extension keys (per IProvenance's `[key: string]: unknown` arm).
68
68
  sentiment: { score: 0.5 },
69
69
  epistemic: 'belief'
@@ -89,7 +89,7 @@ describe('antagonist — full on-disk round-trip preserves every optional field'
89
89
  entityId: 'doc-2' as IMemoryEnvelope['entityId'],
90
90
  kind,
91
91
  tags: [],
92
- links: [{ type: 'rel' as never, target: 'doc-3' as never }],
92
+ links: [{ type: 'rel' as never, target: { scope: 'knowledge', id: 'doc-3' } as never }],
93
93
  created: 0,
94
94
  updated: 0,
95
95
  seq: 0,
@@ -8,6 +8,7 @@ import { Converters } from '@fgv/ts-utils';
8
8
  import {
9
9
  BodyConverterRegistry,
10
10
  edgeConverter,
11
+ edgeTargetConverter,
11
12
  envelopeConverter,
12
13
  envelopeYamlConverter,
13
14
  joinFrontmatter,
@@ -24,7 +25,7 @@ const validEnvelopeObject: Record<string, unknown> = {
24
25
  entityId: 'intro-to-rust',
25
26
  kind: 'knowledge',
26
27
  tags: ['rust', 'systems'],
27
- links: [{ type: 'related', target: 'ownership' }],
28
+ links: [{ type: 'related', target: { scope: 'knowledge', id: 'ownership' } }],
28
29
  created: 1000,
29
30
  updated: 2000,
30
31
  seq: 5,
@@ -53,6 +54,33 @@ describe('provenanceConverter', () => {
53
54
  expect(provenanceConverter.convert({ source: 'host-ingest' })).toSucceedWith({ source: 'host-ingest' });
54
55
  });
55
56
 
57
+ test('validates a scope-qualified derivedFrom (nested { scope, id })', () => {
58
+ expect(
59
+ provenanceConverter.convert({
60
+ source: 'host-ingest',
61
+ derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }
62
+ })
63
+ ).toSucceedAndSatisfy((p) => {
64
+ expect(p.derivedFrom).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
65
+ });
66
+ });
67
+
68
+ test('rejects a bare-string derivedFrom (the pre-scoped format)', () => {
69
+ expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: 'turn-3' })).toFail();
70
+ });
71
+
72
+ test('rejects a derivedFrom missing scope', () => {
73
+ expect(provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { id: 'turn-3' } })).toFailWith(
74
+ /scope/i
75
+ );
76
+ });
77
+
78
+ test('rejects a derivedFrom missing id', () => {
79
+ expect(
80
+ provenanceConverter.convert({ source: 'host-ingest', derivedFrom: { scope: 'conversations/conv-a' } })
81
+ ).toFailWith(/id/i);
82
+ });
83
+
56
84
  test('fails when source is missing', () => {
57
85
  expect(provenanceConverter.convert({ by: 'erik' })).toFail();
58
86
  });
@@ -62,12 +90,34 @@ describe('provenanceConverter', () => {
62
90
  });
63
91
  });
64
92
 
93
+ describe('edgeTargetConverter', () => {
94
+ test('validates a scope-qualified target', () => {
95
+ expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a', id: 'turn-3' })).toSucceedAndSatisfy(
96
+ (t) => {
97
+ expect(t).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
98
+ }
99
+ );
100
+ });
101
+
102
+ test('rejects a target missing scope', () => {
103
+ expect(edgeTargetConverter.convert({ id: 'turn-3' })).toFailWith(/scope/i);
104
+ });
105
+
106
+ test('rejects a target missing id', () => {
107
+ expect(edgeTargetConverter.convert({ scope: 'conversations/conv-a' })).toFailWith(/id/i);
108
+ });
109
+
110
+ test('rejects a bare-string target (the pre-scoped format)', () => {
111
+ expect(edgeTargetConverter.convert('turn-3')).toFail();
112
+ });
113
+ });
114
+
65
115
  describe('edgeConverter', () => {
66
116
  test('validates a full attributed edge', () => {
67
117
  expect(
68
118
  edgeConverter.convert({
69
119
  type: 'mtm-ref',
70
- target: 'turn-3',
120
+ target: { scope: 'conversations/conv-a', id: 'turn-3' },
71
121
  confidence: 0.8,
72
122
  provenance: { source: 'agent' },
73
123
  valid_at: 100,
@@ -75,7 +125,7 @@ describe('edgeConverter', () => {
75
125
  })
76
126
  ).toSucceedAndSatisfy((edge) => {
77
127
  expect(edge.type).toBe('mtm-ref');
78
- expect(edge.target).toBe('turn-3');
128
+ expect(edge.target).toEqual({ scope: 'conversations/conv-a', id: 'turn-3' });
79
129
  expect(edge.confidence).toBe(0.8);
80
130
  expect(edge.provenance).toEqual({ source: 'agent' });
81
131
  expect(edge.valid_at).toBe(100);
@@ -84,28 +134,36 @@ describe('edgeConverter', () => {
84
134
  });
85
135
 
86
136
  test('validates a minimal edge', () => {
87
- expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toSucceedAndSatisfy((edge) => {
137
+ expect(
138
+ edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'ownership' } })
139
+ ).toSucceedAndSatisfy((edge) => {
88
140
  expect(edge.type).toBe('related');
89
- expect(edge.target).toBe('ownership');
141
+ expect(edge.target).toEqual({ scope: 'knowledge', id: 'ownership' });
90
142
  expect(edge.confidence).toBeUndefined();
91
143
  });
92
144
  });
93
145
 
94
146
  test('accepts a null invalid_at (still valid)', () => {
95
- expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: null })).toSucceedAndSatisfy(
96
- (edge) => {
97
- expect(edge.invalid_at).toBeNull();
98
- }
99
- );
147
+ expect(
148
+ edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: null })
149
+ ).toSucceedAndSatisfy((edge) => {
150
+ expect(edge.invalid_at).toBeNull();
151
+ });
100
152
  });
101
153
 
102
154
  test('fails when invalid_at is neither a number nor null', () => {
103
- expect(edgeConverter.convert({ type: 'related', target: 'x', invalid_at: 'soon' })).toFail();
155
+ expect(
156
+ edgeConverter.convert({ type: 'related', target: { scope: 'knowledge', id: 'x' }, invalid_at: 'soon' })
157
+ ).toFail();
104
158
  });
105
159
 
106
160
  test('fails when target is missing', () => {
107
161
  expect(edgeConverter.convert({ type: 'related' })).toFail();
108
162
  });
163
+
164
+ test('fails when target is a bare string (rejects the pre-scoped format)', () => {
165
+ expect(edgeConverter.convert({ type: 'related', target: 'ownership' })).toFail();
166
+ });
109
167
  });
110
168
 
111
169
  describe('temporalConverter', () => {
@@ -157,6 +215,34 @@ describe('envelopeConverter', () => {
157
215
  expect(envelopeConverter.convert({ ...validEnvelopeObject, embeddingRef: 42 })).toFail();
158
216
  });
159
217
 
218
+ test('rank is absent when not present', () => {
219
+ expect(envelopeConverter.convert(validEnvelopeObject)).toSucceedAndSatisfy((envelope) => {
220
+ expect(envelope.rank).toBeUndefined();
221
+ });
222
+ });
223
+
224
+ test('validates and preserves a numeric rank', () => {
225
+ expect(envelopeConverter.convert({ ...validEnvelopeObject, rank: 42 })).toSucceedAndSatisfy(
226
+ (envelope) => {
227
+ expect(envelope.rank).toBe(42);
228
+ }
229
+ );
230
+ });
231
+
232
+ test('serialize/parse round-trips rank through frontmatter', () => {
233
+ const registry = BodyConverterRegistry.create().orThrow();
234
+ registry.register('knowledge' as Kind, Converters.string);
235
+ const envelope = envelopeConverter.convert({ ...validEnvelopeObject, rank: 7 }).orThrow();
236
+ const file = serializeMemoryFile(envelope, 'body').orThrow();
237
+ expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
238
+ expect(record.envelope.rank).toBe(7);
239
+ });
240
+ });
241
+
242
+ test('fails when rank is not a number', () => {
243
+ expect(envelopeConverter.convert({ ...validEnvelopeObject, rank: 'high' })).toFail();
244
+ });
245
+
160
246
  test('fails when a required field is missing', () => {
161
247
  const missing: Record<string, unknown> = { ...validEnvelopeObject };
162
248
  delete missing.contentHash;
@@ -239,6 +325,77 @@ describe('parseMemoryFile / serializeMemoryFile', () => {
239
325
  });
240
326
  });
241
327
 
328
+ test('round-trips a scope-qualified edge target through the frontmatter intact', () => {
329
+ const envelope = envelopeConverter
330
+ .convert({
331
+ ...validEnvelopeObject,
332
+ links: [{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }]
333
+ })
334
+ .orThrow();
335
+ const file = serializeMemoryFile(envelope, 'body').orThrow();
336
+ // On-wire the target is a nested object, NOT a bare scalar.
337
+ expect(file).toMatch(/scope: conversations\/conv-a/);
338
+ expect(file).toMatch(/id: turn-3/);
339
+ expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
340
+ expect(record.envelope.links).toEqual([
341
+ { type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }
342
+ ]);
343
+ // Serializing the reparsed record reproduces byte-identical output.
344
+ expect(serializeMemoryFile(record.envelope, record.body as string)).toSucceedWith(file);
345
+ });
346
+ });
347
+
348
+ test('round-trips a scope-qualified provenance.derivedFrom through the frontmatter intact', () => {
349
+ const envelope = envelopeConverter
350
+ .convert({
351
+ ...validEnvelopeObject,
352
+ provenance: {
353
+ source: 'host-ingest',
354
+ derivedFrom: { scope: 'conversations/conv-a', id: 'turn-3' }
355
+ }
356
+ })
357
+ .orThrow();
358
+ const file = serializeMemoryFile(envelope, 'body').orThrow();
359
+ // On-wire the back-reference is a nested object, NOT a bare scalar.
360
+ expect(file).toMatch(/scope: conversations\/conv-a/);
361
+ expect(parseMemoryFile(file, registry)).toSucceedAndSatisfy((record) => {
362
+ expect(record.envelope.provenance.derivedFrom).toEqual({
363
+ scope: 'conversations/conv-a',
364
+ id: 'turn-3'
365
+ });
366
+ expect(serializeMemoryFile(record.envelope, record.body as string)).toSucceedWith(file);
367
+ });
368
+ });
369
+
370
+ test('two records whose derivedFrom share a stem across scopes round-trip as distinct scoped refs', () => {
371
+ // Same idStem ('turn-3'), different scopes — exactly the ambiguity a bare
372
+ // MemoryId could not disambiguate. Each must round-trip to ITS OWN scope.
373
+ const makeFile = (scope: string): string => {
374
+ const envelope = envelopeConverter
375
+ .convert({
376
+ ...validEnvelopeObject,
377
+ provenance: { source: 'host-ingest', derivedFrom: { scope, id: 'turn-3' } }
378
+ })
379
+ .orThrow();
380
+ return serializeMemoryFile(envelope, 'body').orThrow();
381
+ };
382
+ const fileA = makeFile('conversations/conv-a');
383
+ const fileB = makeFile('conversations/conv-b');
384
+ expect(fileA).not.toBe(fileB);
385
+ expect(parseMemoryFile(fileA, registry)).toSucceedAndSatisfy((record) => {
386
+ expect(record.envelope.provenance.derivedFrom).toEqual({
387
+ scope: 'conversations/conv-a',
388
+ id: 'turn-3'
389
+ });
390
+ });
391
+ expect(parseMemoryFile(fileB, registry)).toSucceedAndSatisfy((record) => {
392
+ expect(record.envelope.provenance.derivedFrom).toEqual({
393
+ scope: 'conversations/conv-b',
394
+ id: 'turn-3'
395
+ });
396
+ });
397
+ });
398
+
242
399
  test('fails when the frontmatter cannot be split', () => {
243
400
  expect(parseMemoryFile('no delimiters', registry)).toFailWith(/missing opening frontmatter delimiter/i);
244
401
  });
@@ -5,6 +5,7 @@
5
5
 
6
6
  import '@fgv/ts-utils-jest';
7
7
  import {
8
+ IEdgeTarget,
8
9
  IIndexedMemoryRecord,
9
10
  IMemoryRecord,
10
11
  Kind,
@@ -12,9 +13,27 @@ import {
12
13
  MemoryIndex,
13
14
  MemoryScopeKey,
14
15
  Tag,
15
- envelopeConverter
16
+ envelopeConverter,
17
+ rankCompare
16
18
  } from '../../../index';
17
19
 
20
+ /**
21
+ * The scope every link target in this fixture lives under. Links are authored as
22
+ * bare id strings and resolved to `{ scope: LINK_TARGET_SCOPE, id }`, so the
23
+ * cross-scope source tests can share one target while their sources differ.
24
+ */
25
+ const LINK_TARGET_SCOPE: string = 'knowledge';
26
+
27
+ /** A scope-qualified backlink-target query for the fixed link-target scope. */
28
+ function bt(id: string): IEdgeTarget {
29
+ return { scope: LINK_TARGET_SCOPE as MemoryScopeKey, id: id as MemoryId };
30
+ }
31
+
32
+ /** Extract the bare ids from a set of scope-qualified targets (backlink results). */
33
+ function targetIds(targets: ReadonlyArray<IEdgeTarget>): string[] {
34
+ return targets.map((t) => t.id as string);
35
+ }
36
+
18
37
  interface IRecordSpec {
19
38
  readonly id: string;
20
39
  readonly scope?: string;
@@ -23,6 +42,7 @@ interface IRecordSpec {
23
42
  readonly links?: ReadonlyArray<string>;
24
43
  readonly updated?: number;
25
44
  readonly seq?: number;
45
+ readonly rank?: number;
26
46
  }
27
47
 
28
48
  function makeEntry(spec: IRecordSpec): IIndexedMemoryRecord {
@@ -33,11 +53,15 @@ function makeEntry(spec: IRecordSpec): IIndexedMemoryRecord {
33
53
  entityId: spec.id,
34
54
  kind: spec.kind ?? 'knowledge',
35
55
  tags: spec.tags ?? [],
36
- links: (spec.links ?? []).map((target) => ({ type: 'rel', target })),
56
+ links: (spec.links ?? []).map((target) => ({
57
+ type: 'rel',
58
+ target: { scope: LINK_TARGET_SCOPE, id: target }
59
+ })),
37
60
  created: 0,
38
61
  updated: spec.updated ?? 0,
39
62
  seq: spec.seq ?? 0,
40
63
  contentHash: 'h',
64
+ ...(spec.rank !== undefined ? { rank: spec.rank } : {}),
41
65
  provenance: { source: 'agent' }
42
66
  })
43
67
  .orThrow(),
@@ -63,7 +87,7 @@ describe('MemoryIndex', () => {
63
87
  expect(index.byKind('knowledge' as Kind)).toHaveLength(0);
64
88
  expect(index.byTag('any' as Tag)).toHaveLength(0);
65
89
  expect(index.byRecency()).toHaveLength(0);
66
- expect(index.backlinks('none' as MemoryId)).toHaveLength(0);
90
+ expect(index.backlinks(bt('none'))).toHaveLength(0);
67
91
  });
68
92
  });
69
93
 
@@ -78,7 +102,7 @@ describe('MemoryIndex', () => {
78
102
  expect([...ids(index.byKind('knowledge' as Kind))].sort()).toEqual(['a', 'b']);
79
103
  expect([...ids(index.byTag('x' as Tag))].sort()).toEqual(['a', 'b']);
80
104
  expect(ids(index.byTag('y' as Tag))).toEqual(['b']);
81
- expect(index.backlinks('b' as MemoryId)).toEqual(['a']);
105
+ expect(targetIds(index.backlinks(bt('b')))).toEqual(['a']);
82
106
  });
83
107
 
84
108
  test('clears prior state', () => {
@@ -94,7 +118,7 @@ describe('MemoryIndex', () => {
94
118
  const entry = makeEntry({ id: 'a', tags: ['x'], links: ['b'] });
95
119
  expect(index.patch('put', entry)).toSucceedWith(entry);
96
120
  expect(ids(index.byKind('knowledge' as Kind))).toEqual(['a']);
97
- expect(index.backlinks('b' as MemoryId)).toEqual(['a']);
121
+ expect(targetIds(index.backlinks(bt('b')))).toEqual(['a']);
98
122
  });
99
123
 
100
124
  test('put on an existing key replaces stale associations', () => {
@@ -105,8 +129,8 @@ describe('MemoryIndex', () => {
105
129
  expect(ids(index.byTag('new' as Tag))).toEqual(['a']);
106
130
  expect(index.byKind('knowledge' as Kind)).toHaveLength(0);
107
131
  expect(ids(index.byKind('note' as Kind))).toEqual(['a']);
108
- expect(index.backlinks('b' as MemoryId)).toHaveLength(0);
109
- expect(index.backlinks('c' as MemoryId)).toEqual(['a']);
132
+ expect(index.backlinks(bt('b'))).toHaveLength(0);
133
+ expect(targetIds(index.backlinks(bt('c')))).toEqual(['a']);
110
134
  });
111
135
 
112
136
  test('delete removes the entry and its associations', () => {
@@ -115,7 +139,7 @@ describe('MemoryIndex', () => {
115
139
  expect(index.patch('delete', entry)).toSucceedWith(entry);
116
140
  expect(index.entries()).toHaveLength(0);
117
141
  expect(index.byTag('x' as Tag)).toHaveLength(0);
118
- expect(index.backlinks('b' as MemoryId)).toHaveLength(0);
142
+ expect(index.backlinks(bt('b'))).toHaveLength(0);
119
143
  });
120
144
 
121
145
  test('delete of an absent key is a no-op that still succeeds', () => {
@@ -145,19 +169,80 @@ describe('MemoryIndex', () => {
145
169
  });
146
170
  });
147
171
 
172
+ describe('byRank', () => {
173
+ test('orders by rank descending, absent-rank records last, recency tiebreak', () => {
174
+ index
175
+ .rebuild([
176
+ makeEntry({ id: 'mid', rank: 5, updated: 100, seq: 1 }),
177
+ makeEntry({ id: 'top', rank: 9, updated: 100, seq: 2 }),
178
+ makeEntry({ id: 'tie-lo', rank: 3, updated: 200, seq: 4 }),
179
+ makeEntry({ id: 'tie-hi', rank: 3, updated: 200, seq: 9 }),
180
+ makeEntry({ id: 'unranked-old', updated: 100, seq: 5 }),
181
+ makeEntry({ id: 'unranked-new', updated: 400, seq: 6 })
182
+ ])
183
+ .orThrow();
184
+ // ranked (desc): top(9), mid(5), tie-hi(3,seq9), tie-lo(3,seq4);
185
+ // then unranked by recency: unranked-new(updated400), unranked-old(updated100).
186
+ expect(ids(index.byRank())).toEqual(['top', 'mid', 'tie-hi', 'tie-lo', 'unranked-new', 'unranked-old']);
187
+ });
188
+
189
+ test('is empty on an empty index', () => {
190
+ expect(index.byRank()).toHaveLength(0);
191
+ });
192
+
193
+ test('sorts a ranked record ahead of an earlier-listed absent-rank record', () => {
194
+ // Input lists the absent-rank record first so the sort compares (ranked, absent),
195
+ // exercising the ranked-before-absent direction of the comparator.
196
+ index
197
+ .rebuild([
198
+ makeEntry({ id: 'unranked', updated: 100, seq: 1 }),
199
+ makeEntry({ id: 'ranked', rank: 5, updated: 100, seq: 2 })
200
+ ])
201
+ .orThrow();
202
+ expect(ids(index.byRank())).toEqual(['ranked', 'unranked']);
203
+ });
204
+
205
+ test('orders two absent-rank records by recency alone', () => {
206
+ index
207
+ .rebuild([makeEntry({ id: 'a', updated: 100, seq: 1 }), makeEntry({ id: 'b', updated: 300, seq: 2 })])
208
+ .orThrow();
209
+ expect(ids(index.byRank())).toEqual(['b', 'a']);
210
+ });
211
+
212
+ test("index byRank agrees pairwise with the retrieve packlet's rankCompare", () => {
213
+ // Guard against the two hand-duplicated comparators (index `_compareByRank`
214
+ // and retrieve `rankCompare`) drifting: a future tie-break edit to one that
215
+ // diverges from the other is caught here on a shared mixed fixture.
216
+ const fixture = [
217
+ makeEntry({ id: 'r-top', rank: 9, updated: 100, seq: 1 }),
218
+ makeEntry({ id: 'r-mid', rank: 5, updated: 400, seq: 2 }),
219
+ makeEntry({ id: 'r-tie-a', rank: 3, updated: 200, seq: 3 }),
220
+ makeEntry({ id: 'r-tie-b', rank: 3, updated: 200, seq: 8 }),
221
+ makeEntry({ id: 'absent-old', updated: 100, seq: 4 }),
222
+ makeEntry({ id: 'absent-new', updated: 500, seq: 5 })
223
+ ];
224
+ index.rebuild(fixture).orThrow();
225
+ const viaIndex = ids(index.byRank());
226
+ const viaRankCompare = ids(fixture.map((e) => e.record).sort(rankCompare));
227
+ expect(viaIndex).toEqual(viaRankCompare);
228
+ // And the shared expected ordering is what both must produce.
229
+ expect(viaIndex).toEqual(['r-top', 'r-mid', 'r-tie-b', 'r-tie-a', 'absent-new', 'absent-old']);
230
+ });
231
+ });
232
+
148
233
  describe('backlinks', () => {
149
234
  test('aggregates multiple sources pointing at one target', () => {
150
235
  index
151
236
  .rebuild([makeEntry({ id: 'a', links: ['target'] }), makeEntry({ id: 'b', links: ['target'] })])
152
237
  .orThrow();
153
- expect([...index.backlinks('target' as MemoryId)].sort()).toEqual(['a', 'b']);
238
+ expect(targetIds(index.backlinks(bt('target'))).sort()).toEqual(['a', 'b']);
154
239
  });
155
240
 
156
241
  test('drops the target set once the last source is removed', () => {
157
242
  const a = makeEntry({ id: 'a', links: ['target'] });
158
243
  index.patch('put', a).orThrow();
159
244
  index.patch('delete', a).orThrow();
160
- expect(index.backlinks('target' as MemoryId)).toHaveLength(0);
245
+ expect(index.backlinks(bt('target'))).toHaveLength(0);
161
246
  });
162
247
 
163
248
  test('tracks same-id sources across scopes independently', () => {
@@ -167,9 +252,9 @@ describe('MemoryIndex', () => {
167
252
  const a2 = makeEntry({ id: 'a', scope: 'conversations/c2', links: ['target'] });
168
253
  index.patch('put', a1).orThrow();
169
254
  index.patch('put', a2).orThrow();
170
- expect(index.backlinks('target' as MemoryId)).toEqual(['a', 'a']);
255
+ expect(targetIds(index.backlinks(bt('target')))).toEqual(['a', 'a']);
171
256
  index.patch('delete', a1).orThrow();
172
- expect(index.backlinks('target' as MemoryId)).toEqual(['a']);
257
+ expect(targetIds(index.backlinks(bt('target')))).toEqual(['a']);
173
258
  });
174
259
  });
175
260
 
@@ -178,10 +263,10 @@ describe('MemoryIndex', () => {
178
263
  const entry = makeEntry({ id: 'a', tags: ['dup', 'dup'], links: ['t', 't'] });
179
264
  index.patch('put', entry).orThrow();
180
265
  expect(ids(index.byTag('dup' as Tag))).toEqual(['a']);
181
- expect(index.backlinks('t' as MemoryId)).toEqual(['a']);
266
+ expect(targetIds(index.backlinks(bt('t')))).toEqual(['a']);
182
267
  index.patch('delete', entry).orThrow();
183
268
  expect(index.byTag('dup' as Tag)).toHaveLength(0);
184
- expect(index.backlinks('t' as MemoryId)).toHaveLength(0);
269
+ expect(index.backlinks(bt('t'))).toHaveLength(0);
185
270
  });
186
271
  });
187
272
  });
@@ -21,6 +21,7 @@ import {
21
21
  IBodyConverterRegistry,
22
22
  ICandidateEdge,
23
23
  ICandidateRecord,
24
+ IEdgeTarget,
24
25
  IEntityResolutionCandidate,
25
26
  IEntityResolver,
26
27
  IFactExtractor,
@@ -38,12 +39,18 @@ import {
38
39
  KnowledgeIdentityCodec,
39
40
  MemoryId,
40
41
  MemoryIngestOrchestrator,
42
+ MemoryScopeKey,
41
43
  ResolutionVerdict,
42
44
  Tag
43
45
  } from '../../../index';
44
46
 
45
47
  const noteKind: Kind = 'note' as Kind;
46
48
 
49
+ /** A scope-qualified target for the note fixtures (all under the knowledge scope). */
50
+ function kt(id: string): IEdgeTarget {
51
+ return { scope: 'knowledge' as MemoryScopeKey, id: id as MemoryId };
52
+ }
53
+
47
54
  function mutableRoot(): FileTree.IMutableFileTreeDirectoryItem {
48
55
  const tree = FileTree.inMemory([], { mutable: true }).orThrow();
49
56
  const root = tree.getDirectory('/').orThrow();
@@ -131,7 +138,7 @@ function candidate(
131
138
  }
132
139
 
133
140
  function candEdge(source: string, type: string, target: string): ICandidateEdge {
134
- return { source: source as MemoryId, edge: { type: type as never, target: target as MemoryId } };
141
+ return { source: kt(source), edge: { type: type as never, target: kt(target) } };
135
142
  }
136
143
 
137
144
  function buildOrchestrator(params: {
@@ -159,7 +166,7 @@ describe('antagonist — cycle guard through EXISTING store edges (integration)'
159
166
  test('a proposed edge that closes a cycle through a pre-existing on-disk edge is rejected', async () => {
160
167
  const store = buildStore();
161
168
  // Existing on-disk edge: doc-a -> doc-b (persisted before this ingest runs).
162
- await putFull(store, 'doc-a', 'anchor-a', [{ type: 'rel' as never, target: 'doc-b' as MemoryId }]);
169
+ await putFull(store, 'doc-a', 'anchor-a', [{ type: 'rel' as never, target: kt('doc-b') }]);
163
170
  await putFull(store, 'doc-b', 'anchor-b');
164
171
 
165
172
  // This ingest re-writes doc-b (a 'new'-verdict same-id update, since no
@@ -183,9 +190,9 @@ describe('antagonist — cycle guard through EXISTING store edges (integration)'
183
190
  const store = buildStore();
184
191
  // Existing chain: a -> b -> c -> d (four pre-existing on-disk edges spanning
185
192
  // three hops), persisted before this ingest.
186
- await putFull(store, 'chain-a', 'a', [{ type: 'rel' as never, target: 'chain-b' as MemoryId }]);
187
- await putFull(store, 'chain-b', 'b', [{ type: 'rel' as never, target: 'chain-c' as MemoryId }]);
188
- await putFull(store, 'chain-c', 'c', [{ type: 'rel' as never, target: 'chain-d' as MemoryId }]);
193
+ await putFull(store, 'chain-a', 'a', [{ type: 'rel' as never, target: kt('chain-b') }]);
194
+ await putFull(store, 'chain-b', 'b', [{ type: 'rel' as never, target: kt('chain-c') }]);
195
+ await putFull(store, 'chain-c', 'c', [{ type: 'rel' as never, target: kt('chain-d') }]);
189
196
  await putFull(store, 'chain-d', 'd');
190
197
 
191
198
  // Proposed: chain-d -> chain-a, closing the whole loop through the existing chain.
@@ -199,7 +206,7 @@ describe('antagonist — cycle guard through EXISTING store edges (integration)'
199
206
 
200
207
  test('cycleGuard "off" admits a cycle that closes through an existing edge', async () => {
201
208
  const store = buildStore();
202
- await putFull(store, 'doc-a', 'anchor-a', [{ type: 'rel' as never, target: 'doc-b' as MemoryId }]);
209
+ await putFull(store, 'doc-a', 'anchor-a', [{ type: 'rel' as never, target: kt('doc-b') }]);
203
210
  await putFull(store, 'doc-b', 'anchor-b');
204
211
  const orch = MemoryIngestOrchestrator.create({
205
212
  store,
@@ -211,7 +218,7 @@ describe('antagonist — cycle guard through EXISTING store edges (integration)'
211
218
  cycleGuard: 'off'
212
219
  }).orThrow();
213
220
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toSucceedAndSatisfy((r: IIngestItemResult) => {
214
- expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: 'doc-a' }]);
221
+ expect(r.records[0].record?.envelope.links).toEqual([{ type: 'rel', target: kt('doc-a') }]);
215
222
  });
216
223
  });
217
224
  });
@@ -236,7 +243,7 @@ describe('antagonist — resolver target need not be a surfaced `similar` candid
236
243
  Promise.resolve(succeed(Float32Array.from([1, 0, 0])));
237
244
  // Only doc-a is embedded/indexed; doc-q is never surfaced by similarity —
238
245
  // it can only be reached by the resolver's own out-of-band choice.
239
- (await vectorIndex.add('doc-a' as MemoryId, Float32Array.from([1, 0, 0]))).orThrow();
246
+ (await vectorIndex.add(kt('doc-a'), Float32Array.from([1, 0, 0]))).orThrow();
240
247
 
241
248
  const orch = MemoryIngestOrchestrator.create({
242
249
  store,
@@ -247,7 +254,7 @@ describe('antagonist — resolver target need not be a surfaced `similar` candid
247
254
  relationExtractor: relater(() => succeed([])),
248
255
  vectorIndex,
249
256
  embed,
250
- entityResolver: resolver(() => succeed({ verdict, target: 'doc-q' as MemoryId } as ResolutionVerdict))
257
+ entityResolver: resolver(() => succeed({ verdict, target: kt('doc-q') } as ResolutionVerdict))
251
258
  }).orThrow();
252
259
 
253
260
  const result = await orch.ingestItem({ id: 'i', content: 'x' });
@@ -259,7 +266,7 @@ describe('antagonist — resolver target need not be a surfaced `similar` candid
259
266
  } else if (verdict === 'supersede') {
260
267
  expect(result).toSucceedAndSatisfy((r: IIngestItemResult) => {
261
268
  expect(r.records[0].disposition).toBe('written');
262
- expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: 'doc-q' });
269
+ expect(r.records[0].resolution).toEqual({ verdict: 'supersede', target: kt('doc-q') });
263
270
  });
264
271
  } else {
265
272
  expect(result).toSucceedAndSatisfy((r: IIngestItemResult) => {
@@ -297,11 +304,11 @@ describe('antagonist — enum-branch parity: uniform structural rejection across
297
304
  relationExtractor: relater(() => succeed([])),
298
305
  vectorIndex,
299
306
  embed,
300
- entityResolver: resolver(() => succeed({ verdict, target: 'ghost' as MemoryId } as ResolutionVerdict))
307
+ entityResolver: resolver(() => succeed({ verdict, target: kt('ghost') } as ResolutionVerdict))
301
308
  }).orThrow();
302
- (await vectorIndex.add('doc-a' as MemoryId, Float32Array.from([1, 0, 0]))).orThrow();
309
+ (await vectorIndex.add(kt('doc-a'), Float32Array.from([1, 0, 0]))).orThrow();
303
310
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
304
- `ingest 'i': ${verdict} target 'ghost' does not exist in the store`
311
+ `ingest 'i': ${verdict} target 'knowledge/ghost' does not exist in the store`
305
312
  );
306
313
  }
307
314
  );
@@ -349,13 +356,11 @@ describe('antagonist — enum-branch parity: uniform structural rejection across
349
356
  relationExtractor: relater(() => succeed([])),
350
357
  vectorIndex,
351
358
  embed,
352
- entityResolver: resolver(() =>
353
- succeed({ verdict, target: 'foreign-1' as MemoryId } as ResolutionVerdict)
354
- )
359
+ entityResolver: resolver(() => succeed({ verdict, target: kt('foreign-1') } as ResolutionVerdict))
355
360
  }).orThrow();
356
- (await vectorIndex.add('foreign-1' as MemoryId, Float32Array.from([1, 0, 0]))).orThrow();
361
+ (await vectorIndex.add(kt('foreign-1'), Float32Array.from([1, 0, 0]))).orThrow();
357
362
  expect(await orch.ingestItem({ id: 'i', content: 'x' })).toFailWith(
358
- `ingest 'i': ${verdict} target 'foreign-1' is kind 'other' but the candidate is kind 'note'`
363
+ `ingest 'i': ${verdict} target 'knowledge/foreign-1' is kind 'other' but the candidate is kind 'note'`
359
364
  );
360
365
  }
361
366
  );