@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-51

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 (182) hide show
  1. package/dist/packlets/index/memoryIndex.js +23 -16
  2. package/dist/packlets/index/memoryIndex.js.map +1 -1
  3. package/dist/packlets/ingest/orchestrator.js +13 -1
  4. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  5. package/dist/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
  6. package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  7. package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
  8. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  9. package/dist/packlets/retrieve/recencyRetriever.js +7 -7
  10. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
  11. package/dist/packlets/retrieve/retriever.js +91 -10
  12. package/dist/packlets/retrieve/retriever.js.map +1 -1
  13. package/dist/packlets/retrieve/semanticRetriever.js +16 -16
  14. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
  15. package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
  16. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  17. package/dist/packlets/retrieve/tagRetriever.js +7 -7
  18. package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
  19. package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
  20. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
  21. package/dist/packlets/store/coverage.js +6 -0
  22. package/dist/packlets/store/coverage.js.map +1 -0
  23. package/dist/packlets/store/fileTreeMemoryStore.js +234 -103
  24. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  25. package/dist/packlets/store/index.js +4 -0
  26. package/dist/packlets/store/index.js.map +1 -1
  27. package/dist/packlets/store/listSelection.js +36 -0
  28. package/dist/packlets/store/listSelection.js.map +1 -0
  29. package/dist/packlets/store/memoryStore.js +6 -0
  30. package/dist/packlets/store/memoryStore.js.map +1 -0
  31. package/dist/packlets/store/reconcile.js +6 -0
  32. package/dist/packlets/store/reconcile.js.map +1 -0
  33. package/dist/packlets/store/storeCoverage.js +102 -0
  34. package/dist/packlets/store/storeCoverage.js.map +1 -0
  35. package/dist/packlets/store/storeIdentity.js +62 -0
  36. package/dist/packlets/store/storeIdentity.js.map +1 -0
  37. package/dist/packlets/store/storeReconcile.js +122 -0
  38. package/dist/packlets/store/storeReconcile.js.map +1 -0
  39. package/dist/packlets/store/vectorMaintenance.js +116 -8
  40. package/dist/packlets/store/vectorMaintenance.js.map +1 -1
  41. package/dist/packlets/store/vectorRecordSource.js +44 -0
  42. package/dist/packlets/store/vectorRecordSource.js.map +1 -0
  43. package/dist/packlets/tools/memoryTools.js +25 -2
  44. package/dist/packlets/tools/memoryTools.js.map +1 -1
  45. package/dist/packlets/types/envelope.js +25 -0
  46. package/dist/packlets/types/envelope.js.map +1 -1
  47. package/dist/packlets/types/identityResolver.js +6 -0
  48. package/dist/packlets/types/identityResolver.js.map +1 -0
  49. package/dist/packlets/types/index.js +2 -0
  50. package/dist/packlets/types/index.js.map +1 -1
  51. package/dist/packlets/types/recordResolver.js +6 -0
  52. package/dist/packlets/types/recordResolver.js.map +1 -0
  53. package/dist/packlets/types/temporal.js.map +1 -1
  54. package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
  55. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  56. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +109 -14
  57. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  58. package/dist/packlets/vector/rebuildHelpers.js +38 -0
  59. package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
  60. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  61. package/dist/ts-agent-memory.d.ts +1226 -114
  62. package/lib/packlets/index/memoryIndex.d.ts +118 -27
  63. package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
  64. package/lib/packlets/index/memoryIndex.js +23 -16
  65. package/lib/packlets/index/memoryIndex.js.map +1 -1
  66. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  67. package/lib/packlets/ingest/orchestrator.js +13 -1
  68. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  69. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +79 -1
  70. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -1
  71. package/lib/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
  72. package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  73. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
  74. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
  75. package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
  76. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  77. package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
  78. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
  79. package/lib/packlets/retrieve/recencyRetriever.js +6 -6
  80. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
  81. package/lib/packlets/retrieve/retriever.d.ts +88 -7
  82. package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
  83. package/lib/packlets/retrieve/retriever.js +94 -9
  84. package/lib/packlets/retrieve/retriever.js.map +1 -1
  85. package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
  86. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
  87. package/lib/packlets/retrieve/semanticRetriever.js +15 -15
  88. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
  89. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
  90. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
  91. package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
  92. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  93. package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
  94. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
  95. package/lib/packlets/retrieve/tagRetriever.js +6 -6
  96. package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
  97. package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
  98. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
  99. package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
  100. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
  101. package/lib/packlets/store/coverage.d.ts +102 -0
  102. package/lib/packlets/store/coverage.d.ts.map +1 -0
  103. package/lib/packlets/store/coverage.js +7 -0
  104. package/lib/packlets/store/coverage.js.map +1 -0
  105. package/lib/packlets/store/fileTreeMemoryStore.d.ts +56 -167
  106. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  107. package/lib/packlets/store/fileTreeMemoryStore.js +234 -103
  108. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  109. package/lib/packlets/store/index.d.ts +4 -0
  110. package/lib/packlets/store/index.d.ts.map +1 -1
  111. package/lib/packlets/store/index.js +4 -0
  112. package/lib/packlets/store/index.js.map +1 -1
  113. package/lib/packlets/store/listSelection.d.ts +101 -0
  114. package/lib/packlets/store/listSelection.d.ts.map +1 -0
  115. package/lib/packlets/store/listSelection.js +40 -0
  116. package/lib/packlets/store/listSelection.js.map +1 -0
  117. package/lib/packlets/store/memoryStore.d.ts +237 -0
  118. package/lib/packlets/store/memoryStore.d.ts.map +1 -0
  119. package/lib/packlets/store/memoryStore.js +7 -0
  120. package/lib/packlets/store/memoryStore.js.map +1 -0
  121. package/lib/packlets/store/reconcile.d.ts +82 -0
  122. package/lib/packlets/store/reconcile.d.ts.map +1 -0
  123. package/lib/packlets/store/reconcile.js +7 -0
  124. package/lib/packlets/store/reconcile.js.map +1 -0
  125. package/lib/packlets/store/storeCoverage.d.ts +45 -0
  126. package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
  127. package/lib/packlets/store/storeCoverage.js +105 -0
  128. package/lib/packlets/store/storeCoverage.js.map +1 -0
  129. package/lib/packlets/store/storeIdentity.d.ts +38 -0
  130. package/lib/packlets/store/storeIdentity.d.ts.map +1 -0
  131. package/lib/packlets/store/storeIdentity.js +67 -0
  132. package/lib/packlets/store/storeIdentity.js.map +1 -0
  133. package/lib/packlets/store/storeReconcile.d.ts +41 -0
  134. package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
  135. package/lib/packlets/store/storeReconcile.js +125 -0
  136. package/lib/packlets/store/storeReconcile.js.map +1 -0
  137. package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
  138. package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
  139. package/lib/packlets/store/vectorMaintenance.js +117 -8
  140. package/lib/packlets/store/vectorMaintenance.js.map +1 -1
  141. package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
  142. package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
  143. package/lib/packlets/store/vectorRecordSource.js +47 -0
  144. package/lib/packlets/store/vectorRecordSource.js.map +1 -0
  145. package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
  146. package/lib/packlets/tools/memoryTools.js +25 -2
  147. package/lib/packlets/tools/memoryTools.js.map +1 -1
  148. package/lib/packlets/types/envelope.d.ts +24 -2
  149. package/lib/packlets/types/envelope.d.ts.map +1 -1
  150. package/lib/packlets/types/envelope.js +26 -0
  151. package/lib/packlets/types/envelope.js.map +1 -1
  152. package/lib/packlets/types/identityResolver.d.ts +42 -0
  153. package/lib/packlets/types/identityResolver.d.ts.map +1 -0
  154. package/lib/packlets/types/identityResolver.js +7 -0
  155. package/lib/packlets/types/identityResolver.js.map +1 -0
  156. package/lib/packlets/types/index.d.ts +2 -0
  157. package/lib/packlets/types/index.d.ts.map +1 -1
  158. package/lib/packlets/types/index.js +2 -0
  159. package/lib/packlets/types/index.js.map +1 -1
  160. package/lib/packlets/types/recordResolver.d.ts +39 -0
  161. package/lib/packlets/types/recordResolver.d.ts.map +1 -0
  162. package/lib/packlets/types/recordResolver.js +7 -0
  163. package/lib/packlets/types/recordResolver.js.map +1 -0
  164. package/lib/packlets/types/temporal.d.ts +26 -6
  165. package/lib/packlets/types/temporal.d.ts.map +1 -1
  166. package/lib/packlets/types/temporal.js.map +1 -1
  167. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
  168. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  169. package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
  170. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  171. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +18 -4
  172. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  173. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +108 -13
  174. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  175. package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
  176. package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
  177. package/lib/packlets/vector/rebuildHelpers.js +42 -0
  178. package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
  179. package/lib/packlets/vector/vectorIndex.d.ts +326 -20
  180. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  181. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  182. package/package.json +7 -7
@@ -10,8 +10,13 @@ const ts_json_base_1 = require("@fgv/ts-json-base");
10
10
  const types_1 = require("../types");
11
11
  const converters_1 = require("../converters");
12
12
  const vectorMaintenance_1 = require("./vectorMaintenance");
13
+ const storeCoverage_1 = require("./storeCoverage");
14
+ const storeIdentity_1 = require("./storeIdentity");
15
+ const storeReconcile_1 = require("./storeReconcile");
13
16
  const index_1 = require("../index");
14
17
  const scopeEncoding_1 = require("./scopeEncoding");
18
+ const listSelection_1 = require("./listSelection");
19
+ const vectorRecordSource_1 = require("./vectorRecordSource");
15
20
  /** The on-disk extension for a memory record file. */
16
21
  const MEMORY_FILE_EXTENSION = '.md';
17
22
  /**
@@ -118,7 +123,7 @@ class FileTreeMemoryStore {
118
123
  /** {@inheritDoc IMemoryStore.get} */
119
124
  async get(kind, entityId) {
120
125
  var _a;
121
- const result = this._codecFor(kind).onSuccess((codec) => codec.encode(entityId).onSuccess((addr) => {
126
+ const result = (0, storeIdentity_1.codecFor)(this._codecs, this._defaultCodec, kind).onSuccess((codec) => codec.encode(entityId).onSuccess((addr) => {
122
127
  if (addr.isVersioned) {
123
128
  if (!(0, types_1.isTemporalIdentityCodec)(codec)) {
124
129
  return (0, ts_utils_1.fail)(`memory get '${entityId}': codec for versioned kind '${kind}' does not implement the temporal codec interface`);
@@ -127,7 +132,7 @@ class FileTreeMemoryStore {
127
132
  // version whose `invalid_at` is null/absent) from the entity subtree,
128
133
  // read off the derived index. `asOf` resolution is via `list({ asOf })`
129
134
  // and the temporal retrievers.
130
- return (0, ts_utils_1.succeed)(this._readVersionedCurrent(addr.scope));
135
+ return this._readVersionedCurrent(addr.scope);
131
136
  }
132
137
  return this._readRecord(addr.scope, addr.idStem);
133
138
  }));
@@ -143,50 +148,137 @@ class FileTreeMemoryStore {
143
148
  return this._readRecord(scope, id);
144
149
  }
145
150
  /** {@inheritDoc IMemoryStore.list} */
146
- async list(filter) {
147
- const matches = this._index
148
- .entries()
149
- .filter((entry) => {
150
- if ((filter === null || filter === void 0 ? void 0 : filter.scope) !== undefined && entry.scope !== filter.scope) {
151
+ async list(selection) {
152
+ // Deliberately NOT annotated `: boolean`. An explicit annotation discards the
153
+ // type predicate, and TypeScript's aliased-condition narrowing is what lets
154
+ // the ternary below see `selection` as a filter on the false branch. With the
155
+ // annotation this line compiles and the next one does not.
156
+ const scan = (0, listSelection_1.isWholeVaultScan)(selection);
157
+ const filter = scan ? {} : selection;
158
+ if (!scan && filter.scope === undefined && filter.kind === undefined && filter.tag === undefined) {
159
+ // `asOf` alone does not narrow — it collapses versions, it does not exclude
160
+ // entities — so a selection carrying only `asOf` lands here too.
161
+ return (0, ts_utils_1.fail)('memory list: a selection must narrow by scope, kind or tag; ' +
162
+ 'pass scanEveryRecord() to read every record in the vault deliberately');
163
+ }
164
+ // Select over envelopes, project temporally, and materialize LAST — so a
165
+ // narrowed list reads only the files it is going to return.
166
+ const selected = this._index.entries().filter((entry) => {
167
+ if (filter.scope !== undefined && entry.scope !== filter.scope) {
151
168
  return false;
152
169
  }
153
- if ((filter === null || filter === void 0 ? void 0 : filter.kind) !== undefined && entry.record.envelope.kind !== filter.kind) {
170
+ if (filter.kind !== undefined && entry.envelope.kind !== filter.kind) {
154
171
  return false;
155
172
  }
156
- if ((filter === null || filter === void 0 ? void 0 : filter.tag) !== undefined && !entry.record.envelope.tags.includes(filter.tag)) {
173
+ if (filter.tag !== undefined && !entry.envelope.tags.includes(filter.tag)) {
157
174
  return false;
158
175
  }
159
176
  return true;
160
- })
161
- .map((entry) => entry.record);
162
- if ((filter === null || filter === void 0 ? void 0 : filter.asOf) === undefined) {
163
- // No temporal projection requested: byte-identical to the pre-temporal
164
- // behavior (the flat-path guarantee — every version is returned).
165
- return (0, ts_utils_1.succeed)(matches);
166
- }
167
- return (0, ts_utils_1.succeed)(FileTreeMemoryStore._projectAsOf(matches, filter.asOf));
177
+ });
178
+ const projected = selection.asOf === undefined
179
+ ? // No temporal projection requested: the flat-path guarantee every
180
+ // version is returned.
181
+ selected
182
+ : FileTreeMemoryStore._projectAsOf(selected, selection.asOf);
183
+ return this._materialize(projected);
184
+ }
185
+ /** {@inheritDoc IMemoryStore.coverage} */
186
+ coverage() {
187
+ return Promise.resolve((0, storeCoverage_1.computeCoverage)({
188
+ entries: this._index.entries(),
189
+ hasRankProjector: (kind) => this._rankProjectors.has(kind),
190
+ anyRankProjector: this._rankProjectors.size > 0,
191
+ embedsKind: (kind) => this.embedsKind(kind),
192
+ vectorIndex: this._vectors.vectorIndex,
193
+ fragmentIndex: this._vectors.fragmentIndex
194
+ }));
195
+ }
196
+ /** {@inheritDoc IMemoryStore.listEntries} */
197
+ async listEntries() {
198
+ return (0, ts_utils_1.succeed)(this._index.entries());
168
199
  }
169
200
  /** {@inheritDoc IMemoryStore.listScoped} */
170
201
  async listScoped() {
171
- // The derived index already carries each record's scope
172
- // ({@link IIndexedMemoryRecord.scope}), so the scoped projection is a direct
173
- // map the record's `(scope, id)` is exactly the address the vector index
174
- // keys on. No filter/temporal projection: the seam re-embeds the whole vault.
175
- return (0, ts_utils_1.succeed)(this._index.entries().map((entry) => ({
176
- target: { scope: entry.scope, id: entry.record.envelope.id },
177
- record: entry.record
178
- })));
202
+ // The derived index already carries each record's scope, so the scoped
203
+ // projection keys straight off it — `(scope, id)` is exactly the address the
204
+ // vector index uses. Bodies ARE materialized here: this seam feeds an
205
+ // embedder, which is the one consumer that genuinely needs every body. No
206
+ // filter/temporal projection it re-embeds the whole vault by contract.
207
+ //
208
+ // **Complete or failed — never quietly short.** This call is the sole feed
209
+ // for `asRecordSource()`, and therefore for `IVectorIndex.rebuild`'s coverage
210
+ // report. A record dropped here would land in none of `records` / `excluded`
211
+ // / `indexed` / `declined` / `skipped`, so a caller computing coverage would
212
+ // undercount *in the direction of looking healthier* — which is precisely the
213
+ // failure `vectorRecordSource`'s own tally exists to prevent, reintroduced one
214
+ // layer down. A loud failure is recoverable; a silent undercount is not.
215
+ //
216
+ // Two consequences worth stating rather than discovering. Before the index
217
+ // was projected this method read nothing and could not fail, because the
218
+ // index held the records themselves; it now costs a full-vault read and is
219
+ // fallible. And it is NOT write-locked, so a concurrent `put` whose cap-cull
220
+ // physically evicts a record between the snapshot and the read will fail it.
221
+ // That window is real and is tracked in `docs/FUTURE.md` — the answer is to
222
+ // make the loss *countable*, not to swallow it.
223
+ return (0, ts_utils_1.mapResults)(this._index.entries().map((entry) => {
224
+ const target = { scope: entry.scope, id: entry.envelope.id };
225
+ return this._resolveRequired(entry).onSuccess((record) => (0, ts_utils_1.succeed)({ target, record }));
226
+ }));
227
+ }
228
+ /** {@inheritDoc IMemoryRecordResolver.resolveRecord} */
229
+ resolveRecord(scope, id) {
230
+ return this._readRecord(scope, id);
231
+ }
232
+ _codec(kind) {
233
+ return (0, storeIdentity_1.codecFor)(this._codecs, this._defaultCodec, kind);
234
+ }
235
+ /** {@inheritDoc IIdentityResolver.resolveIdentity} */
236
+ resolveIdentity(kind, entityId) {
237
+ return (0, storeIdentity_1.resolveIdentity)(this._codecs, this._defaultCodec, kind, entityId);
238
+ }
239
+ /**
240
+ * Materialize a selected set of entries into records, dropping any that have
241
+ * vanished since selection.
242
+ *
243
+ * @remarks
244
+ * A miss is not a failure. Selection reads the in-memory index and
245
+ * materialization reads storage, so a record deleted in between is a legitimate
246
+ * race and yields a shorter list rather than an error. A read that FAILS is a
247
+ * real fault and propagates.
248
+ */
249
+ _materialize(entries) {
250
+ return (0, ts_utils_1.mapResults)(entries.map((entry) => this._readRecord(entry.scope, entry.envelope.id))).onSuccess((records) => (0, ts_utils_1.succeed)(records.filter((r) => r !== undefined)));
251
+ }
252
+ /**
253
+ * Materialize one entry, treating "gone" as a fault rather than a miss.
254
+ *
255
+ * @remarks
256
+ * For paths where a vanished record really does mean the index and the vault
257
+ * disagree, rather than that something legitimately removed it in between.
258
+ *
259
+ * Three of the four callers hold the write lock, so nothing can have removed
260
+ * the record since the entry was read. `get()`'s versioned path does not, and
261
+ * is safe only because temporal kinds never physically delete a version — they
262
+ * invalidate in place, and cap-cull does not apply to them. **If eviction is
263
+ * ever added to the temporal path, that caller must change**, or it
264
+ * reintroduces the race this method exists to detect.
265
+ *
266
+ * `listScoped` also does not hold the lock, and uses this deliberately anyway:
267
+ * it feeds a coverage report, so a silent drop there is worse than a loud
268
+ * failure. See its comment, and `docs/FUTURE.md` for the eviction window.
269
+ *
270
+ * The drop-tolerant counterpart is {@link FileTreeMemoryStore._materialize},
271
+ * for readers where a record that vanished between selection and
272
+ * materialization is a miss rather than a fault.
273
+ */
274
+ _resolveRequired(entry) {
275
+ return this._readRecord(entry.scope, entry.envelope.id).onSuccess((record) => record === undefined
276
+ ? (0, ts_utils_1.fail)(`memory: index entry '${entry.scope}/${entry.envelope.id}' has no record in the vault`)
277
+ : (0, ts_utils_1.succeed)(record));
179
278
  }
180
279
  /** {@inheritDoc IMemoryStore.asRecordSource} */
181
280
  asRecordSource() {
182
- return {
183
- // Filtered to the kinds that participate in the record vector index. This
184
- // source exists to drive `IVectorIndex` rebuilds, so a kind excluded from the
185
- // index has no business being re-embedded on open — which is where the cost
186
- // is worst, since a rebuild embeds the whole vault serially. With no
187
- // `embedKinds` declaration every kind passes and this is the identity filter.
188
- list: async () => (await this.listScoped()).onSuccess((scoped) => (0, ts_utils_1.succeed)(scoped.filter((s) => this.embedsKind(s.record.envelope.kind))))
189
- };
281
+ return (0, vectorRecordSource_1.vectorRecordSource)(this);
190
282
  }
191
283
  /**
192
284
  * Collapse temporal records to the single version valid at `asOf` per entity;
@@ -195,21 +287,21 @@ class FileTreeMemoryStore {
195
287
  * filtering is deferred — OQ-9). An entity with no version valid at `asOf`
196
288
  * contributes nothing.
197
289
  */
198
- static _projectAsOf(records, asOf) {
290
+ static _projectAsOf(entries, asOf) {
199
291
  const passthrough = [];
200
292
  const groups = new Map();
201
- for (const record of records) {
202
- if (!(0, types_1.isTemporalRecord)(record)) {
203
- passthrough.push(record);
293
+ for (const entry of entries) {
294
+ if (!(0, types_1.isTemporalRecord)(entry)) {
295
+ passthrough.push(entry);
204
296
  continue;
205
297
  }
206
- const key = `${record.envelope.kind}\0${record.envelope.entityId}`;
298
+ const key = `${entry.envelope.kind}\0${entry.envelope.entityId}`;
207
299
  const existing = groups.get(key);
208
300
  if (existing === undefined) {
209
- groups.set(key, [record]);
301
+ groups.set(key, [entry]);
210
302
  }
211
303
  else {
212
- existing.push(record);
304
+ existing.push(entry);
213
305
  }
214
306
  }
215
307
  const result = [...passthrough];
@@ -221,9 +313,9 @@ class FileTreeMemoryStore {
221
313
  }
222
314
  return result;
223
315
  }
224
- /** {@inheritDoc IMemoryStore.reconcileRank} */
225
- async reconcileRank(kind) {
226
- return this._enqueue(() => this._reconcileRankLocked(kind));
316
+ /** {@inheritDoc IMemoryStore.reconcile} */
317
+ async reconcile(kind, artifact) {
318
+ return this._enqueue(() => this._reconcileLocked(kind, artifact));
227
319
  }
228
320
  /** {@inheritDoc IMemoryStore.put} */
229
321
  async put(record) {
@@ -333,7 +425,7 @@ class FileTreeMemoryStore {
333
425
  }
334
426
  /** Resolve a scope for an observation, best-effort (undefined when unresolvable). */
335
427
  _scopeBestEffort(kind, entityId) {
336
- return this._codecFor(kind)
428
+ return this._codec(kind)
337
429
  .onSuccess((codec) => codec.encode(entityId))
338
430
  .onSuccess((addr) => (0, ts_utils_1.succeed)(addr.scope))
339
431
  .orDefault();
@@ -351,7 +443,7 @@ class FileTreeMemoryStore {
351
443
  return this._registry
352
444
  .convert(envelope.kind, body)
353
445
  .withErrorFormat((msg) => `memory put '${envelope.id}': invalid body: ${msg}`)
354
- .onSuccess(() => this._codecFor(envelope.kind))
446
+ .onSuccess(() => this._codec(envelope.kind))
355
447
  .thenOnSuccess((codec) => codec.encode(envelope.entityId).thenOnSuccess((addr) => {
356
448
  if (addr.isVersioned) {
357
449
  if (!(0, types_1.isTemporalIdentityCodec)(codec)) {
@@ -394,8 +486,11 @@ class FileTreeMemoryStore {
394
486
  // mutableFields) actually applies. Content-dedup must never shadow LWW for
395
487
  // the same entity.
396
488
  const duplicate = this._findByContentHash(scope, hash, record.envelope.id);
397
- if (duplicate !== undefined) {
398
- return (0, ts_utils_1.succeed)({ record: duplicate, evicted: [] });
489
+ if (duplicate.isFailure()) {
490
+ return (0, ts_utils_1.fail)(duplicate.message);
491
+ }
492
+ if (duplicate.value !== undefined) {
493
+ return (0, ts_utils_1.succeed)({ record: duplicate.value, evicted: [] });
399
494
  }
400
495
  }
401
496
  return this._readRecord(scope, idStem).thenOnSuccess((existing) => {
@@ -416,9 +511,8 @@ class FileTreeMemoryStore {
416
511
  // (a replace, not a grow). The same-id `existing` record is threaded
417
512
  // separately into `_buildRecord` for the merge-patch. Knowledge LWW ignores
418
513
  // this argument, so its behavior is unchanged by the wider cohort.
419
- const cohort = this._admissionCohort(scope, record.envelope.kind, idStem);
420
- return policy
421
- .admit(record, cohort)
514
+ return this._admissionCohort(scope, record.envelope.kind, idStem)
515
+ .onSuccess((cohort) => policy.admit(record, cohort))
422
516
  .thenOnSuccess((decision) => this._admitWrite(record, body, scope, idStem, hash, policy, existing, decision));
423
517
  });
424
518
  }
@@ -530,7 +624,7 @@ class FileTreeMemoryStore {
530
624
  .onSuccess(() => (0, ts_utils_1.succeed)(record));
531
625
  }
532
626
  async _deleteLocked(kind, entityId) {
533
- return this._codecFor(kind).thenOnSuccess((codec) => codec.encode(entityId).thenOnSuccess((addr) => {
627
+ return this._codec(kind).thenOnSuccess((codec) => codec.encode(entityId).thenOnSuccess((addr) => {
534
628
  if (addr.isVersioned) {
535
629
  if (!(0, types_1.isTemporalIdentityCodec)(codec)) {
536
630
  return Promise.resolve((0, ts_utils_1.fail)(`memory delete '${entityId}': codec for versioned kind '${kind}' does not implement the temporal codec interface`));
@@ -568,7 +662,16 @@ class FileTreeMemoryStore {
568
662
  * written, or fully invalidated / soft-deleted).
569
663
  */
570
664
  _readVersionedCurrent(scope) {
571
- return (0, types_1.selectCurrentVersion)(this._versionsForEntity(scope));
665
+ // Select over ENVELOPES, then materialize the one winner — which is what
666
+ // `IMemoryStore.get`'s docstring promises and what `selectCurrentVersion`
667
+ // being generic over `IEnvelopeCarrier` exists for. Materializing every
668
+ // version first (as this did) cost N file reads and N body validations to
669
+ // return one record, and made that docstring false.
670
+ const current = (0, types_1.selectCurrentVersion)(this._index.entries().filter((entry) => entry.scope === scope));
671
+ if (current === undefined) {
672
+ return (0, ts_utils_1.succeed)(undefined);
673
+ }
674
+ return this._resolveRequired(current);
572
675
  }
573
676
  /**
574
677
  * Every persisted version of the entity whose subtree is `scope`. All version
@@ -576,10 +679,13 @@ class FileTreeMemoryStore {
576
679
  * entityId), so a scope filter over the index isolates one entity's versions.
577
680
  */
578
681
  _versionsForEntity(scope) {
579
- return this._index
682
+ // Selected on the envelope (scope), materialized after — so a versioned write
683
+ // reads only that entity's versions, never the vault. Bounded by the entity's
684
+ // version count.
685
+ return (0, ts_utils_1.mapResults)(this._index
580
686
  .entries()
581
687
  .filter((entry) => entry.scope === scope)
582
- .map((entry) => entry.record);
688
+ .map((entry) => this._resolveRequired(entry)));
583
689
  }
584
690
  /**
585
691
  * Versioned write (invalidate-don't-delete). Builds the new version's content
@@ -601,7 +707,11 @@ class FileTreeMemoryStore {
601
707
  // still-current version at snapshot time — normally one, but two-or-more if a
602
708
  // prior invalidation partially failed; invalidating all of them lets the write
603
709
  // self-heal a stuck state (P2-7).
604
- const versions = this._versionsForEntity(scope);
710
+ const snapshot = this._versionsForEntity(scope);
711
+ if (snapshot.isFailure()) {
712
+ return (0, ts_utils_1.fail)(snapshot.message);
713
+ }
714
+ const versions = snapshot.value;
605
715
  const priorCurrents = versions.filter(types_1.isVersionCurrent);
606
716
  const current = (0, types_1.selectCurrentVersion)(versions);
607
717
  const policy = this._policyFor(kind);
@@ -727,7 +837,11 @@ class FileTreeMemoryStore {
727
837
  * builds on it), so a hard delete would defeat the purpose.
728
838
  */
729
839
  async _deleteVersioned(entityId, scope) {
730
- const versions = this._versionsForEntity(scope);
840
+ const snapshot = this._versionsForEntity(scope);
841
+ if (snapshot.isFailure()) {
842
+ return (0, ts_utils_1.fail)(snapshot.message);
843
+ }
844
+ const versions = snapshot.value;
731
845
  const currents = versions.filter(types_1.isVersionCurrent);
732
846
  const current = (0, types_1.selectCurrentVersion)(versions);
733
847
  if (current === undefined) {
@@ -774,10 +888,12 @@ class FileTreeMemoryStore {
774
888
  * across first-writes and updates.
775
889
  */
776
890
  _admissionCohort(scope, kind, idStem) {
777
- return this._index
891
+ // Envelope-only selection; the cohort is per-(scope, kind) and bounded by the
892
+ // cull cap, so materializing it is small by construction.
893
+ return (0, ts_utils_1.mapResults)(this._index
778
894
  .entries()
779
- .filter((entry) => entry.scope === scope && entry.record.envelope.kind === kind && entry.record.envelope.id !== idStem)
780
- .map((entry) => entry.record);
895
+ .filter((entry) => entry.scope === scope && entry.envelope.kind === kind && entry.envelope.id !== idStem)
896
+ .map((entry) => this._resolveRequired(entry)));
781
897
  }
782
898
  /**
783
899
  * Find a record in `scope` whose `contentHash` equals `hash`, if any,
@@ -785,12 +901,13 @@ class FileTreeMemoryStore {
785
901
  * dedup skip the same-id record so it does not shadow the LWW update path.
786
902
  */
787
903
  _findByContentHash(scope, hash, excludeId) {
904
+ // The clearest win of the projection: the hash lives on the envelope, so this
905
+ // scans envelopes and reads exactly ONE file — the match — where it used to
906
+ // hold every body in the scope to look at one field.
788
907
  const match = this._index
789
908
  .entries()
790
- .find((entry) => entry.scope === scope &&
791
- entry.record.envelope.contentHash === hash &&
792
- entry.record.envelope.id !== excludeId);
793
- return match === null || match === void 0 ? void 0 : match.record;
909
+ .find((entry) => entry.scope === scope && entry.envelope.contentHash === hash && entry.envelope.id !== excludeId);
910
+ return match === undefined ? (0, ts_utils_1.succeed)(undefined) : this._resolveRequired(match);
794
911
  }
795
912
  /**
796
913
  * True when `incoming`'s caller-authored mutable metadata (`tags` / `provenance`)
@@ -816,7 +933,7 @@ class FileTreeMemoryStore {
816
933
  return this._hasher.computeHash({ kind, body, links });
817
934
  }
818
935
  /**
819
- * The locked body of {@link FileTreeMemoryStore.reconcileRank}.
936
+ * The rank branch of {@link FileTreeMemoryStore.reconcile}, under the write lock.
820
937
  *
821
938
  * @remarks
822
939
  * Re-reads each record's file rather than trusting the in-memory index, for
@@ -845,31 +962,54 @@ class FileTreeMemoryStore {
845
962
  * another on a reconcile. `_stampRank` itself is reused verbatim, which also
846
963
  * inherits its throw semantics (logged at `warn`, `rank` cleared).
847
964
  */
848
- async _reconcileRankLocked(kind) {
849
- if (!this._rankProjectors.has(kind)) {
850
- return (0, ts_utils_1.fail)(`memory reconcileRank '${kind}': no rank projector is registered for this kind`);
851
- }
965
+ async _reconcileLocked(kind, artifact) {
966
+ // Envelope-only selection: the walk needs `(scope, id)` and the kind, and
967
+ // each branch materializes only the records it decides to repair.
852
968
  const targets = this._index
853
969
  .entries()
854
- .filter((entry) => entry.record.envelope.kind === kind);
855
- let restamped = 0;
970
+ .filter((entry) => entry.envelope.kind === kind);
971
+ if (artifact === 'rank') {
972
+ return this._reconcileRankLocked(kind, targets);
973
+ }
974
+ return (0, storeReconcile_1.reconcileVectors)({
975
+ kind,
976
+ artifact,
977
+ targets,
978
+ maintenance: this._vectors,
979
+ embedsKind: (k) => this.embedsKind(k),
980
+ resolve: (scope, id) => this._readRecord(scope, id),
981
+ stampRef: (scope, id, ref) => this._rewriteEnvelope(scope, id, (r) => r.envelope.embeddingRef === ref
982
+ ? undefined
983
+ : { envelope: Object.assign(Object.assign({}, r.envelope), { embeddingRef: ref }), body: r.body })
984
+ });
985
+ }
986
+ /** The rank branch of {@link FileTreeMemoryStore._reconcileLocked}. */
987
+ async _reconcileRankLocked(kind, targets) {
988
+ if (!this._rankProjectors.has(kind)) {
989
+ return (0, ts_utils_1.fail)(`memory reconcile '${kind}' rank: no rank projector is registered for this kind`);
990
+ }
991
+ // No materialization at all: `_rewriteEnvelope` re-reads each record itself.
992
+ let repaired = 0;
856
993
  for (const target of targets) {
857
- const applied = this._restampOne(target.scope, target.record.envelope.id);
994
+ const applied = this._rewriteEnvelope(target.scope, target.envelope.id, (r) => {
995
+ const stamped = this._stampRank(r);
996
+ return stamped.envelope.rank === r.envelope.rank ? undefined : stamped;
997
+ });
858
998
  if (applied.isFailure()) {
859
- return (0, ts_utils_1.fail)(`memory reconcileRank '${kind}': ${applied.message}`);
999
+ return (0, ts_utils_1.fail)(`memory reconcile '${kind}' rank: ${applied.message}`);
860
1000
  }
861
1001
  if (applied.value) {
862
- restamped++;
1002
+ repaired++;
863
1003
  }
864
1004
  }
865
- return (0, ts_utils_1.succeed)(restamped);
1005
+ return (0, ts_utils_1.succeed)({ artifact: 'rank', kind, examined: targets.length, repaired, failed: [] });
866
1006
  }
867
1007
  /**
868
1008
  * Re-apply the rank projector to one record on disk. Returns whether `rank`
869
1009
  * actually changed — an unchanged rank writes nothing, so a reconcile over an
870
1010
  * already-consistent store touches no files.
871
1011
  */
872
- _restampOne(scope, id) {
1012
+ _rewriteEnvelope(scope, id, mutate) {
873
1013
  return this._resolveScopeDir(scope).onSuccess((scopeDir) => {
874
1014
  /* c8 ignore next 3 - defensive: the scope dir exists for any indexed record */
875
1015
  if (scopeDir === undefined) {
@@ -897,12 +1037,16 @@ class FileTreeMemoryStore {
897
1037
  .onSuccess((parsed) => (0, converters_1.splitFrontmatter)(raw)
898
1038
  .withErrorFormat((msg) => `'${id}': ${msg}`)
899
1039
  .onSuccess((parts) => {
900
- const before = parsed.envelope.rank;
901
- const stamped = this._stampRank({
1040
+ // The mutator says "nothing to change" with `undefined` rather
1041
+ // than by returning an equal record: persisting unconditionally
1042
+ // would bump `updated` on every record of the kind, trading a
1043
+ // wrong value for a wrong timestamp, and a deep comparison here
1044
+ // would have to know which fields each caller touches.
1045
+ const stamped = mutate({
902
1046
  envelope: parsed.envelope,
903
1047
  body: parts.body
904
1048
  });
905
- if (stamped.envelope.rank === before) {
1049
+ if (stamped === undefined) {
906
1050
  return (0, ts_utils_1.succeed)(false);
907
1051
  }
908
1052
  return this._persist(stamped, scope, id).onSuccess(() => (0, ts_utils_1.succeed)(true));
@@ -941,14 +1085,6 @@ class FileTreeMemoryStore {
941
1085
  return { envelope: Object.assign(Object.assign({}, record.envelope), { rank: undefined }), body: record.body };
942
1086
  }
943
1087
  }
944
- _codecFor(kind) {
945
- var _a;
946
- const codec = (_a = this._codecs.get(kind)) !== null && _a !== void 0 ? _a : this._defaultCodec;
947
- if (codec === undefined) {
948
- return (0, ts_utils_1.fail)(`no identity codec registered for kind '${kind}'`);
949
- }
950
- return (0, ts_utils_1.succeed)(codec);
951
- }
952
1088
  _policyFor(kind) {
953
1089
  var _a;
954
1090
  return (_a = this._writePolicies.get(kind)) !== null && _a !== void 0 ? _a : this._defaultPolicy;
@@ -997,18 +1133,7 @@ class FileTreeMemoryStore {
997
1133
  * verbatim downstream (e.g. merge-into re-addressing). Cross-check it here.
998
1134
  */
999
1135
  _verifyLoaded(scope, file, record) {
1000
- if (record.envelope.id !== file.baseName) {
1001
- return (0, ts_utils_1.fail)(`memory file '${file.absolutePath}': envelope id '${record.envelope.id}' does not match filename stem '${file.baseName}'`);
1002
- }
1003
- return this._codecFor(record.envelope.kind)
1004
- .onSuccess((codec) => codec.verifyRoundTrip(scope, file.baseName).onSuccess(() => codec.decode(scope, file.baseName)))
1005
- .withErrorFormat((msg) => `memory file '${file.absolutePath}': ${msg}`)
1006
- .onSuccess((decodedEntityId) => {
1007
- if (decodedEntityId !== record.envelope.entityId) {
1008
- return (0, ts_utils_1.fail)(`memory file '${file.absolutePath}': envelope entityId '${record.envelope.entityId}' does not match scope-derived entityId '${decodedEntityId}'`);
1009
- }
1010
- return (0, ts_utils_1.succeed)(record);
1011
- });
1136
+ return (0, storeIdentity_1.verifyLoadedIdentity)(this._codec(record.envelope.kind), scope, file, record);
1012
1137
  }
1013
1138
  /**
1014
1139
  * Resolve the directory for a scope, returning `undefined` when it does not
@@ -1098,8 +1223,8 @@ class FileTreeMemoryStore {
1098
1223
  _initialIndex(onRecordError) {
1099
1224
  return this._collectEntries(this._root, [], onRecordError).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
1100
1225
  for (const entry of entries) {
1101
- if (entry.record.envelope.seq > this._seq) {
1102
- this._seq = entry.record.envelope.seq;
1226
+ if (entry.envelope.seq > this._seq) {
1227
+ this._seq = entry.envelope.seq;
1103
1228
  }
1104
1229
  }
1105
1230
  return (0, ts_utils_1.succeed)(true);
@@ -1140,11 +1265,17 @@ class FileTreeMemoryStore {
1140
1265
  * failure passes through untouched so the historical error is byte-identical.
1141
1266
  */
1142
1267
  _loadRecordFile(scope, child, onRecordError) {
1143
- return child
1268
+ return (child
1144
1269
  .getRawContents()
1145
1270
  .onSuccess((raw) => (0, converters_1.parseMemoryFile)(raw, this._registry))
1146
1271
  .onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
1147
- .onSuccess((verified) => (0, ts_utils_1.succeed)([{ scope, record: verified }]))
1272
+ // Parse → validate → PROJECT → discard, per file. The body is read and
1273
+ // fully validated (which is what gives `onRecordError` its meaning), then
1274
+ // dropped here rather than carried into the index. Peak body residency
1275
+ // across the whole open is therefore ONE record, not N — which is the
1276
+ // resident-memory moment this whole surface exists to fix, and it is why
1277
+ // the projection had to reach `rebuild` and not just the read methods.
1278
+ .onSuccess((verified) => (0, ts_utils_1.succeed)([{ scope, envelope: verified.envelope }]))
1148
1279
  .onFailure((message) => {
1149
1280
  if (onRecordError === 'skip') {
1150
1281
  const path = `${scope}/${child.name}`;
@@ -1153,7 +1284,7 @@ class FileTreeMemoryStore {
1153
1284
  this._warnSwallowed(error);
1154
1285
  }
1155
1286
  return (0, ts_utils_1.fail)(message);
1156
- });
1287
+ }));
1157
1288
  }
1158
1289
  }
1159
1290
  exports.FileTreeMemoryStore = FileTreeMemoryStore;