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

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 (26) hide show
  1. package/dist/packlets/store/fileTreeMemoryStore.js +24 -100
  2. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  3. package/dist/packlets/store/storeFileAccess.js +99 -0
  4. package/dist/packlets/store/storeFileAccess.js.map +1 -0
  5. package/dist/packlets/store/storeIdentity.js +26 -0
  6. package/dist/packlets/store/storeIdentity.js.map +1 -1
  7. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +0 -19
  8. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  9. package/dist/ts-agent-memory.d.ts +0 -36
  10. package/lib/packlets/store/fileTreeMemoryStore.d.ts +0 -17
  11. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  12. package/lib/packlets/store/fileTreeMemoryStore.js +23 -99
  13. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  14. package/lib/packlets/store/storeFileAccess.d.ts +40 -0
  15. package/lib/packlets/store/storeFileAccess.d.ts.map +1 -0
  16. package/lib/packlets/store/storeFileAccess.js +105 -0
  17. package/lib/packlets/store/storeFileAccess.js.map +1 -0
  18. package/lib/packlets/store/storeIdentity.d.ts +25 -0
  19. package/lib/packlets/store/storeIdentity.d.ts.map +1 -1
  20. package/lib/packlets/store/storeIdentity.js +27 -0
  21. package/lib/packlets/store/storeIdentity.js.map +1 -1
  22. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +0 -19
  23. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  24. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +0 -19
  25. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  26. package/package.json +7 -7
@@ -6,12 +6,12 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.FileTreeMemoryStore = void 0;
8
8
  const ts_utils_1 = require("@fgv/ts-utils");
9
- const ts_json_base_1 = require("@fgv/ts-json-base");
10
9
  const types_1 = require("../types");
11
10
  const converters_1 = require("../converters");
12
11
  const vectorMaintenance_1 = require("./vectorMaintenance");
13
12
  const storeCoverage_1 = require("./storeCoverage");
14
13
  const storeIdentity_1 = require("./storeIdentity");
14
+ const storeFileAccess_1 = require("./storeFileAccess");
15
15
  const storeReconcile_1 = require("./storeReconcile");
16
16
  const index_1 = require("../index");
17
17
  const scopeEncoding_1 = require("./scopeEncoding");
@@ -132,9 +132,9 @@ class FileTreeMemoryStore {
132
132
  // version whose `invalid_at` is null/absent) from the entity subtree,
133
133
  // read off the derived index. `asOf` resolution is via `list({ asOf })`
134
134
  // and the temporal retrievers.
135
- return this._readVersionedCurrent(addr.scope);
135
+ return this._readVersionedCurrent(addr.scope, kind);
136
136
  }
137
- return this._readRecord(addr.scope, addr.idStem);
137
+ return this._readRecord(addr.scope, addr.idStem, kind);
138
138
  }));
139
139
  await this._fireObservation('read', kind, entityId, {
140
140
  outcome: result.isSuccess() ? 'success' : 'failure',
@@ -493,7 +493,7 @@ class FileTreeMemoryStore {
493
493
  return (0, ts_utils_1.succeed)({ record: duplicate.value, evicted: [] });
494
494
  }
495
495
  }
496
- return this._readRecord(scope, idStem).thenOnSuccess((existing) => {
496
+ return this._readRecord(scope, idStem, record.envelope.kind).thenOnSuccess((existing) => {
497
497
  // Same-id re-put is a no-op ONLY when the content hash matches AND the
498
498
  // mutable metadata is also unchanged. The content hash covers
499
499
  // { kind, body, links }; a matching hash with revised tags/provenance is a
@@ -619,7 +619,7 @@ class FileTreeMemoryStore {
619
619
  /** Serialize and write a fully-stamped record, then patch the index. */
620
620
  _persist(record, scope, idStem) {
621
621
  return (0, converters_1.serializeMemoryFile)(record.envelope, record.body)
622
- .onSuccess((raw) => this._writeFile(scope, idStem, raw))
622
+ .onSuccess((raw) => (0, storeFileAccess_1.writeRecordFile)(this._root, this._scopeEncoding, scope, idStem, raw))
623
623
  .onSuccess(() => this._index.patch('put', { scope, record }))
624
624
  .onSuccess(() => (0, ts_utils_1.succeed)(record));
625
625
  }
@@ -629,9 +629,9 @@ class FileTreeMemoryStore {
629
629
  if (!(0, types_1.isTemporalIdentityCodec)(codec)) {
630
630
  return Promise.resolve((0, ts_utils_1.fail)(`memory delete '${entityId}': codec for versioned kind '${kind}' does not implement the temporal codec interface`));
631
631
  }
632
- return this._deleteVersioned(entityId, addr.scope);
632
+ return this._deleteVersioned(entityId, addr.scope, kind);
633
633
  }
634
- return this._deleteFlat(entityId, addr.scope, addr.idStem);
634
+ return this._deleteFlat(entityId, addr.scope, addr.idStem, kind);
635
635
  }));
636
636
  }
637
637
  /**
@@ -639,15 +639,15 @@ class FileTreeMemoryStore {
639
639
  * entry, then prune the vector best-effort. Structurally unchanged from the
640
640
  * pre-temporal delete path.
641
641
  */
642
- async _deleteFlat(entityId, scope, idStem) {
643
- return this._readRecord(scope, idStem).thenOnSuccess((existing) => {
642
+ async _deleteFlat(entityId, scope, idStem, kind) {
643
+ return this._readRecord(scope, idStem, kind).thenOnSuccess((existing) => {
644
644
  if (existing === undefined) {
645
645
  return Promise.resolve((0, ts_utils_1.fail)(`memory delete '${entityId}': no record found`));
646
646
  }
647
647
  // Delete the record file + index entry (authoritative), then prune the
648
648
  // vector best-effort: a committed delete must not fail because the
649
649
  // derived index could not be pruned.
650
- return this._deleteFile(scope, idStem)
650
+ return (0, storeFileAccess_1.deleteRecordFile)(this._root, this._scopeEncoding, scope, idStem)
651
651
  .onSuccess(() => this._index.patch('delete', { scope, record: existing }))
652
652
  .thenOnSuccess(async () => {
653
653
  await this._vectors.removeAll({ scope, id: existing.envelope.id });
@@ -661,7 +661,7 @@ class FileTreeMemoryStore {
661
661
  * null/absent. `undefined` when the entity has no current version (never
662
662
  * written, or fully invalidated / soft-deleted).
663
663
  */
664
- _readVersionedCurrent(scope) {
664
+ _readVersionedCurrent(scope, expectedKind) {
665
665
  // Select over ENVELOPES, then materialize the one winner — which is what
666
666
  // `IMemoryStore.get`'s docstring promises and what `selectCurrentVersion`
667
667
  // being generic over `IEnvelopeCarrier` exists for. Materializing every
@@ -671,21 +671,21 @@ class FileTreeMemoryStore {
671
671
  if (current === undefined) {
672
672
  return (0, ts_utils_1.succeed)(undefined);
673
673
  }
674
- return this._resolveRequired(current);
674
+ return this._resolveRequired(current).onSuccess((materialized) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, materialized.envelope.id, materialized));
675
675
  }
676
676
  /**
677
677
  * Every persisted version of the entity whose subtree is `scope`. All version
678
678
  * files for one entity live under exactly that scope (which encodes the
679
679
  * entityId), so a scope filter over the index isolates one entity's versions.
680
680
  */
681
- _versionsForEntity(scope) {
681
+ _versionsForEntity(scope, expectedKind) {
682
682
  // Selected on the envelope (scope), materialized after — so a versioned write
683
683
  // reads only that entity's versions, never the vault. Bounded by the entity's
684
684
  // version count.
685
685
  return (0, ts_utils_1.mapResults)(this._index
686
686
  .entries()
687
687
  .filter((entry) => entry.scope === scope)
688
- .map((entry) => this._resolveRequired(entry)));
688
+ .map((entry) => this._resolveRequired(entry).onSuccess((r) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, r.envelope.id, r))));
689
689
  }
690
690
  /**
691
691
  * Versioned write (invalidate-don't-delete). Builds the new version's content
@@ -707,7 +707,7 @@ class FileTreeMemoryStore {
707
707
  // still-current version at snapshot time — normally one, but two-or-more if a
708
708
  // prior invalidation partially failed; invalidating all of them lets the write
709
709
  // self-heal a stuck state (P2-7).
710
- const snapshot = this._versionsForEntity(scope);
710
+ const snapshot = this._versionsForEntity(scope, kind);
711
711
  if (snapshot.isFailure()) {
712
712
  return (0, ts_utils_1.fail)(snapshot.message);
713
713
  }
@@ -836,8 +836,8 @@ class FileTreeMemoryStore {
836
836
  * kinds exist to preserve the audit trail (and the L3 `contradicts` interlock
837
837
  * builds on it), so a hard delete would defeat the purpose.
838
838
  */
839
- async _deleteVersioned(entityId, scope) {
840
- const snapshot = this._versionsForEntity(scope);
839
+ async _deleteVersioned(entityId, scope, kind) {
840
+ const snapshot = this._versionsForEntity(scope, kind);
841
841
  if (snapshot.isFailure()) {
842
842
  return (0, ts_utils_1.fail)(snapshot.message);
843
843
  }
@@ -860,7 +860,7 @@ class FileTreeMemoryStore {
860
860
  if (existing === undefined) {
861
861
  return (0, ts_utils_1.fail)(`memory put: cannot evict '${id}' in scope '${scope}': not found`);
862
862
  }
863
- return this._deleteFile(scope, id)
863
+ return (0, storeFileAccess_1.deleteRecordFile)(this._root, this._scopeEncoding, scope, id)
864
864
  .onSuccess(() => this._index.patch('delete', { scope, record: existing }))
865
865
  .onSuccess(() => (0, ts_utils_1.succeed)(id));
866
866
  });
@@ -1010,7 +1010,7 @@ class FileTreeMemoryStore {
1010
1010
  * already-consistent store touches no files.
1011
1011
  */
1012
1012
  _rewriteEnvelope(scope, id, mutate) {
1013
- return this._resolveScopeDir(scope).onSuccess((scopeDir) => {
1013
+ return (0, storeFileAccess_1.resolveScopeDir)(this._root, this._scopeEncoding, scope).onSuccess((scopeDir) => {
1014
1014
  /* c8 ignore next 3 - defensive: the scope dir exists for any indexed record */
1015
1015
  if (scopeDir === undefined) {
1016
1016
  return (0, ts_utils_1.fail)(`'${id}': scope '${scope}' not found`);
@@ -1105,8 +1105,8 @@ class FileTreeMemoryStore {
1105
1105
  * when the scope directory or file is absent. Verifies the on-disk id ↔
1106
1106
  * filename round-trip on every load.
1107
1107
  */
1108
- _readRecord(scope, idStem) {
1109
- return this._resolveScopeDir(scope).onSuccess((scopeDir) => {
1108
+ _readRecord(scope, idStem, expectedKind) {
1109
+ return (0, storeFileAccess_1.resolveScopeDir)(this._root, this._scopeEncoding, scope).onSuccess((scopeDir) => {
1110
1110
  if (scopeDir === undefined) {
1111
1111
  return (0, ts_utils_1.succeed)(undefined);
1112
1112
  }
@@ -1119,7 +1119,8 @@ class FileTreeMemoryStore {
1119
1119
  return file
1120
1120
  .getRawContents()
1121
1121
  .onSuccess((raw) => (0, converters_1.parseMemoryFile)(raw, this._registry))
1122
- .onSuccess((parsedRecord) => this._verifyLoaded(scope, file, parsedRecord));
1122
+ .onSuccess((parsedRecord) => this._verifyLoaded(scope, file, parsedRecord))
1123
+ .onSuccess((loaded) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, idStem, loaded));
1123
1124
  });
1124
1125
  });
1125
1126
  }
@@ -1135,83 +1136,6 @@ class FileTreeMemoryStore {
1135
1136
  _verifyLoaded(scope, file, record) {
1136
1137
  return (0, storeIdentity_1.verifyLoadedIdentity)(this._codec(record.envelope.kind), scope, file, record);
1137
1138
  }
1138
- /**
1139
- * Resolve the directory for a scope, returning `undefined` when it does not
1140
- * exist. Navigation only — does not create. Folds the path segments through
1141
- * `getChildren` so an absent segment short-circuits to `undefined`.
1142
- */
1143
- _resolveScopeDir(scope) {
1144
- return this._scopeEncoding(scope).onSuccess((encoded) => {
1145
- const segments = encoded.split('/').filter((s) => s.length > 0);
1146
- return segments.reduce((acc, segment) => acc.onSuccess((current) => {
1147
- if (current === undefined) {
1148
- return (0, ts_utils_1.succeed)(undefined);
1149
- }
1150
- return current
1151
- .getChildren()
1152
- .onSuccess((children) => (0, ts_utils_1.succeed)(children.find((c) => c.type === 'directory' && c.name === segment)));
1153
- }), (0, ts_utils_1.succeed)(this._root));
1154
- });
1155
- }
1156
- /** Ensure the scope directory exists, creating segments as needed. */
1157
- _ensureScopeDir(scope) {
1158
- return this._scopeEncoding(scope).onSuccess((encoded) => {
1159
- const segments = encoded.split('/').filter((s) => s.length > 0);
1160
- return segments.reduce((acc, segment) => acc.onSuccess((current) => current.getChildren().onSuccess((children) => {
1161
- const existing = children.find((c) => c.type === 'directory' && c.name === segment);
1162
- if (existing === undefined) {
1163
- return current.createChildDirectory(segment);
1164
- }
1165
- /* c8 ignore next 3 -- defensive: a child of a mutable in-memory/fs tree is itself mutable; the guard protects against a read-only adapter handed in as root */
1166
- if (!ts_json_base_1.FileTree.isMutableDirectoryItem(existing)) {
1167
- return (0, ts_utils_1.fail)(`${existing.absolutePath}: directory is not mutable`);
1168
- }
1169
- return (0, ts_utils_1.succeed)(existing);
1170
- })), (0, ts_utils_1.succeed)(this._root));
1171
- });
1172
- }
1173
- /** Write (create or overwrite) `<scope>/<idStem>.md` with `raw`. */
1174
- _writeFile(scope, idStem, raw) {
1175
- return this._ensureScopeDir(scope).onSuccess((scopeDir) => scopeDir.getChildren().onSuccess((children) => {
1176
- const fileName = `${idStem}${MEMORY_FILE_EXTENSION}`;
1177
- const existing = children.find((c) => c.type === 'file' && c.name === fileName);
1178
- if (existing === undefined) {
1179
- return scopeDir.createChildFile(fileName, raw).onSuccess(() => (0, ts_utils_1.succeed)(true));
1180
- }
1181
- /* c8 ignore next 3 -- defensive: a file in a mutable tree is mutable; guards a read-only adapter */
1182
- if (!ts_json_base_1.FileTree.isMutableFileItem(existing)) {
1183
- return (0, ts_utils_1.fail)(`${existing.absolutePath}: file is not mutable`);
1184
- }
1185
- return existing.setRawContents(raw).onSuccess(() => (0, ts_utils_1.succeed)(true));
1186
- }));
1187
- }
1188
- /**
1189
- * Physically delete `<scope>/<idStem>.md`. The scope-missing and file-missing
1190
- * guards are unreachable through the callers (`delete` / `_evict` both read the
1191
- * record first, so the directory and file exist) but are kept so a future
1192
- * direct caller degrades loudly rather than silently.
1193
- */
1194
- _deleteFile(scope, idStem) {
1195
- return this._resolveScopeDir(scope).onSuccess((scopeDir) => {
1196
- /* c8 ignore next 3 -- unreachable: callers read the record (hence the scope dir) first */
1197
- if (scopeDir === undefined) {
1198
- return (0, ts_utils_1.fail)(`memory delete: scope '${scope}' not found`);
1199
- }
1200
- const fileName = `${idStem}${MEMORY_FILE_EXTENSION}`;
1201
- return scopeDir.getChildren().onSuccess((children) => {
1202
- const file = children.find((c) => c.type === 'file' && c.name === fileName);
1203
- /* c8 ignore next 3 -- unreachable: callers read the record (hence the file) first */
1204
- if (file === undefined) {
1205
- return (0, ts_utils_1.fail)(`memory delete: file '${fileName}' not found in scope '${scope}'`);
1206
- }
1207
- /* c8 ignore next 3 -- defensive: a file in a mutable tree is mutable; guards a read-only adapter */
1208
- if (!ts_json_base_1.FileTree.isMutableFileItem(file)) {
1209
- return (0, ts_utils_1.fail)(`${file.absolutePath}: file is not mutable`);
1210
- }
1211
- return file.delete().onSuccess(() => (0, ts_utils_1.succeed)(true));
1212
- });
1213
- });
1214
- }
1215
1139
  /**
1216
1140
  * Walk the FileTree once and rebuild the index. Also resumes the `seq`
1217
1141
  * counter past the highest persisted `seq` so new writes stay monotonic.