@fgv/ts-agent-memory 5.1.0-45 → 5.1.0-47

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 (120) hide show
  1. package/.rush/temp/{bc5b0f2098f7df2a5c498ea77b0de13316349b96.tar.log → 5667e36f1c0cc503a5c20a3ebff76e7b9a702d41.tar.log} +26 -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/README.md +134 -4
  7. package/dist/packlets/ingest/orchestrator.js +104 -19
  8. package/dist/packlets/ingest/orchestrator.js.map +1 -1
  9. package/dist/packlets/retrieve/fragmentSemanticRetriever.js +10 -7
  10. package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  11. package/dist/packlets/store/fileTreeMemoryStore.js +31 -8
  12. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  13. package/dist/packlets/types/writePolicy.js +10 -2
  14. package/dist/packlets/types/writePolicy.js.map +1 -1
  15. package/dist/packlets/vector/fragmentConverters.js +41 -0
  16. package/dist/packlets/vector/fragmentConverters.js.map +1 -0
  17. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +17 -9
  18. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  19. package/dist/packlets/vector/index.js +1 -0
  20. package/dist/packlets/vector/index.js.map +1 -1
  21. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  22. package/dist/test/unit/ingest/dedupScope.test.js +434 -0
  23. package/dist/test/unit/ingest/dedupScope.test.js.map +1 -0
  24. package/dist/test/unit/ingest/orchestrator.test.js +7 -4
  25. package/dist/test/unit/ingest/orchestrator.test.js.map +1 -1
  26. package/dist/test/unit/store/fileTreeMemoryStore.test.js +64 -0
  27. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  28. package/dist/test/unit/store/indexInjection.test.js +432 -0
  29. package/dist/test/unit/store/indexInjection.test.js.map +1 -0
  30. package/dist/test/unit/tools/memoryTools.test.js +1 -0
  31. package/dist/test/unit/tools/memoryTools.test.js.map +1 -1
  32. package/dist/test/unit/types/temporalPolicy.test.js +21 -0
  33. package/dist/test/unit/types/temporalPolicy.test.js.map +1 -1
  34. package/dist/test/unit/types/writePolicy.test.js +83 -0
  35. package/dist/test/unit/types/writePolicy.test.js.map +1 -1
  36. package/dist/test/unit/vector/fragmentConverters.test.js +58 -0
  37. package/dist/test/unit/vector/fragmentConverters.test.js.map +1 -0
  38. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js +65 -0
  39. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -1
  40. package/dist/ts-agent-memory.d.ts +332 -42
  41. package/etc/ts-agent-memory.api.md +12 -1
  42. package/lib/packlets/ingest/orchestrator.d.ts +62 -5
  43. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  44. package/lib/packlets/ingest/orchestrator.js +104 -19
  45. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  46. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +10 -7
  47. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -1
  48. package/lib/packlets/retrieve/fragmentSemanticRetriever.js +10 -7
  49. package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
  50. package/lib/packlets/store/fileTreeMemoryStore.d.ts +96 -4
  51. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  52. package/lib/packlets/store/fileTreeMemoryStore.js +31 -8
  53. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  54. package/lib/packlets/types/writePolicy.d.ts +40 -2
  55. package/lib/packlets/types/writePolicy.d.ts.map +1 -1
  56. package/lib/packlets/types/writePolicy.js +10 -2
  57. package/lib/packlets/types/writePolicy.js.map +1 -1
  58. package/lib/packlets/vector/fragmentConverters.d.ts +25 -0
  59. package/lib/packlets/vector/fragmentConverters.d.ts.map +1 -0
  60. package/lib/packlets/vector/fragmentConverters.js +44 -0
  61. package/lib/packlets/vector/fragmentConverters.js.map +1 -0
  62. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +5 -3
  63. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  64. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +17 -9
  65. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  66. package/lib/packlets/vector/index.d.ts +1 -0
  67. package/lib/packlets/vector/index.d.ts.map +1 -1
  68. package/lib/packlets/vector/index.js +1 -0
  69. package/lib/packlets/vector/index.js.map +1 -1
  70. package/lib/packlets/vector/vectorIndex.d.ts +97 -22
  71. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  72. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  73. package/lib/test/unit/ingest/dedupScope.test.d.ts +2 -0
  74. package/lib/test/unit/ingest/dedupScope.test.d.ts.map +1 -0
  75. package/lib/test/unit/ingest/dedupScope.test.js +436 -0
  76. package/lib/test/unit/ingest/dedupScope.test.js.map +1 -0
  77. package/lib/test/unit/ingest/orchestrator.test.js +7 -4
  78. package/lib/test/unit/ingest/orchestrator.test.js.map +1 -1
  79. package/lib/test/unit/store/fileTreeMemoryStore.test.js +64 -0
  80. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  81. package/lib/test/unit/store/indexInjection.test.d.ts +2 -0
  82. package/lib/test/unit/store/indexInjection.test.d.ts.map +1 -0
  83. package/lib/test/unit/store/indexInjection.test.js +434 -0
  84. package/lib/test/unit/store/indexInjection.test.js.map +1 -0
  85. package/lib/test/unit/tools/memoryTools.test.js +1 -0
  86. package/lib/test/unit/tools/memoryTools.test.js.map +1 -1
  87. package/lib/test/unit/types/temporalPolicy.test.js +21 -0
  88. package/lib/test/unit/types/temporalPolicy.test.js.map +1 -1
  89. package/lib/test/unit/types/writePolicy.test.js +83 -0
  90. package/lib/test/unit/types/writePolicy.test.js.map +1 -1
  91. package/lib/test/unit/vector/fragmentConverters.test.d.ts +2 -0
  92. package/lib/test/unit/vector/fragmentConverters.test.d.ts.map +1 -0
  93. package/lib/test/unit/vector/fragmentConverters.test.js +60 -0
  94. package/lib/test/unit/vector/fragmentConverters.test.js.map +1 -0
  95. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js +65 -0
  96. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -1
  97. package/package.json +7 -7
  98. package/rush-logs/ts-agent-memory.build.cache.log +1 -1
  99. package/rush-logs/ts-agent-memory.build.log +2 -2
  100. package/src/packlets/ingest/orchestrator.ts +116 -21
  101. package/src/packlets/retrieve/fragmentSemanticRetriever.ts +10 -7
  102. package/src/packlets/store/fileTreeMemoryStore.ts +114 -6
  103. package/src/packlets/types/writePolicy.ts +40 -2
  104. package/src/packlets/vector/fragmentConverters.ts +55 -0
  105. package/src/packlets/vector/inMemoryFragmentCosineIndex.ts +35 -8
  106. package/src/packlets/vector/index.ts +1 -0
  107. package/src/packlets/vector/vectorIndex.ts +97 -22
  108. package/src/test/unit/ingest/dedupScope.test.ts +542 -0
  109. package/src/test/unit/ingest/orchestrator.test.ts +4 -0
  110. package/src/test/unit/store/fileTreeMemoryStore.test.ts +75 -0
  111. package/src/test/unit/store/indexInjection.test.ts +550 -0
  112. package/src/test/unit/tools/memoryTools.test.ts +1 -0
  113. package/src/test/unit/types/temporalPolicy.test.ts +28 -0
  114. package/src/test/unit/types/writePolicy.test.ts +112 -0
  115. package/src/test/unit/vector/fragmentConverters.test.ts +80 -0
  116. package/src/test/unit/vector/inMemoryFragmentCosineIndex.test.ts +88 -0
  117. package/temp/build/lint/_eslint-5eVG3S6w.json +30 -14
  118. package/temp/build/typescript/ts_8nwakTlr.json +1 -1
  119. package/temp/ts-agent-memory.api.json +274 -21
  120. package/temp/ts-agent-memory.api.md +12 -1
@@ -17,16 +17,19 @@ exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index
17
17
  * The sub-document semantic-search retriever — the "discovery" half of a
18
18
  * search-then-read contract. It embeds a fragment query, queries the
19
19
  * {@link IFragmentVectorIndex}, and returns the raw per-fragment
20
- * {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched
21
- * `locator`), NOT resolved records: the consumer re-reads each record and slices it
22
- * by the locator on its own read side.
20
+ * {@link IVectorQueryHit | hits} (each carrying a record `target` AND whichever of
21
+ * `locator` / `fragmentId` the stored fragment was indexed with), NOT resolved
22
+ * records: the consumer re-reads each record and resolves the fragment on its own
23
+ * read side. Note the `locator` span is advisory — see {@link IFragmentLocator}; it
24
+ * is not a slice guaranteed to reproduce the fragment's text.
23
25
  *
24
26
  * @remarks
25
27
  * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and
26
- * returns records; fragment discovery is span-granular and returns locators. Keeping
27
- * it a distinct surface matches the consumer contract (memory stays record-granular;
28
- * sub-document knowledge uses a separate fragment index) and avoids overloading the
29
- * record retriever's return type with a locator that only makes sense here.
28
+ * returns records; fragment discovery is fragment-granular and returns fragment
29
+ * identities. Keeping it a distinct surface matches the consumer contract (memory
30
+ * stays record-granular; sub-document knowledge uses a separate fragment index) and
31
+ * avoids overloading the record retriever's return type with identity fields that
32
+ * only make sense here.
30
33
  *
31
34
  * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment
32
35
  * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER
@@ -1 +1 @@
1
- {"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAsD;AAItD;;;;;GAKG;AACU,QAAA,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,yBAAyB;IAGpC,YAAoB,OAA6C;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,IAAW,YAAY;QACrB,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;IACjE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAEpB;QACC,OAAO,IAAA,kBAAO,EAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAqB;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,IAAA,eAAI,EAAC,yCAAiC,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAA6B,IAAI,CAAC,QAAQ,CAAC;QACxD,4EAA4E;QAC5E,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,QAAQ,GAAyB,MAAM,yBAAyB,CAAC,YAAY,CACjF,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CACzC,CAAC;QACF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,IAAA,eAAI,EAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,yBAAyB,CAAC,YAAY,CAAC,gBAAgB,EAAE,GAAG,EAAE,WACnE,OAAA,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA,EAAA,CAClF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAK,CAAC,YAAY,CAAI,KAAa,EAAE,EAA4B;QAC9E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAA,eAAI,EAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF;AAvDD,8DAuDC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched\n * `locator`), NOT resolved records: the consumer re-reads each record and slices it\n * by the locator on its own read side.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is span-granular and returns locators. Keeping\n * it a distinct surface matches the consumer contract (memory stays record-granular;\n * sub-document knowledge uses a separate fragment index) and avoids overloading the\n * record retriever's return type with a locator that only makes sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n\n private constructor(backend: IFragmentSemanticBackend | undefined) {\n this._backend = backend;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /** Family-convention factory. */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, query.maxPerRecord)\n );\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAsD;AAItD;;;;;GAKG;AACU,QAAA,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,yBAAyB;IAGpC,YAAoB,OAA6C;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,IAAW,YAAY;QACrB,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;IACjE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAEpB;QACC,OAAO,IAAA,kBAAO,EAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAqB;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,IAAA,eAAI,EAAC,yCAAiC,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAA6B,IAAI,CAAC,QAAQ,CAAC;QACxD,4EAA4E;QAC5E,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,QAAQ,GAAyB,MAAM,yBAAyB,CAAC,YAAY,CACjF,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CACzC,CAAC;QACF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,IAAA,eAAI,EAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,yBAAyB,CAAC,YAAY,CAAC,gBAAgB,EAAE,GAAG,EAAE,WACnE,OAAA,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA,EAAA,CAClF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAK,CAAC,YAAY,CAAI,KAAa,EAAE,EAA4B;QAC9E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAA,eAAI,EAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF;AAvDD,8DAuDC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND whichever of\n * `locator` / `fragmentId` the stored fragment was indexed with), NOT resolved\n * records: the consumer re-reads each record and resolves the fragment on its own\n * read side. Note the `locator` span is advisory see {@link IFragmentLocator}; it\n * is not a slice guaranteed to reproduce the fragment's text.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is fragment-granular and returns fragment\n * identities. Keeping it a distinct surface matches the consumer contract (memory\n * stays record-granular; sub-document knowledge uses a separate fragment index) and\n * avoids overloading the record retriever's return type with identity fields that\n * only make sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n\n private constructor(backend: IFragmentSemanticBackend | undefined) {\n this._backend = backend;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /** Family-convention factory. */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, query.maxPerRecord)\n );\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
@@ -1,7 +1,8 @@
1
1
  import { Logging, Result } from '@fgv/ts-utils';
2
2
  import { FileTree } from '@fgv/ts-json-base';
3
- import { EntityId, IIdentityCodec, IMemoryRecord, IWritePolicy, Kind, MemoryId, MemoryScopeKey, RankProjector, Tag } from '../types';
3
+ import { DedupScope, EntityId, IIdentityCodec, IMemoryRecord, IWritePolicy, Kind, MemoryId, MemoryScopeKey, RankProjector, Tag } from '../types';
4
4
  import { IBodyConverterRegistry as IRegistry } from '../converters';
5
+ import { IMemoryIndex } from '../index';
5
6
  import { IMemoryObserver } from '../observe';
6
7
  import { FragmentEmbedder, IFragmentVectorIndex, IMemoryRecordSource, IScopedMemoryRecord, IVectorIndex, MemoryEmbedder } from '../vector';
7
8
  /**
@@ -92,6 +93,34 @@ export interface IMemoryStore {
92
93
  * seam's `list()` returns scope-qualified records.
93
94
  */
94
95
  asRecordSource(): IMemoryRecordSource;
96
+ /**
97
+ * The EFFECTIVE {@link DedupScope} for `kind` — the granularity at which a
98
+ * write for this kind deduplicates against the existing vault.
99
+ *
100
+ * @remarks
101
+ * This is a **read accessor over the store's already-injected write policies**,
102
+ * and it is the single place any caller — the store's own write path included —
103
+ * asks what a kind's dedup granularity is. It resolves the full chain the store
104
+ * applies on write: the kind's registered {@link IWritePolicy}, falling back to
105
+ * the store's default policy, then that policy's
106
+ * {@link IWritePolicy.dedupScope | dedupScope}, falling back to
107
+ * {@link DEFAULT_DEDUP_SCOPE}. Note the store's default policy is a
108
+ * {@link KnowledgeLwwPolicy}, which declares `'content'` — so a kind with NO
109
+ * registered policy resolves to `'content'`, not to `DEFAULT_DEDUP_SCOPE`.
110
+ *
111
+ * It exists so a caller that must agree with the store about dedup granularity
112
+ * — notably the ingest orchestrator's stage-4 layer-1 exact match — can read the
113
+ * declaration through this seam instead of being handed a second copy of the
114
+ * policy map. A second declaration site is precisely the defect this accessor
115
+ * was added to remove.
116
+ *
117
+ * Deliberately synchronous, total, and NOT `Result`-returning: it reads
118
+ * constructor-injected configuration, touches no I/O, and cannot fail (every
119
+ * link in the fallback chain has a total default). It exposes only the scope,
120
+ * never the {@link IWritePolicy} itself, so it can never become a back door for
121
+ * invoking admission or merge logic out of band.
122
+ */
123
+ dedupScopeFor(kind: Kind): DedupScope;
95
124
  /**
96
125
  * Write a record. Validates the body, computes a content hash, deduplicates
97
126
  * (scope-wide, before policy), applies the kind's {@link IWritePolicy}, stamps
@@ -137,6 +166,59 @@ export interface IFileTreeMemoryStoreCreateParams {
137
166
  readonly rankProjectors?: ReadonlyMap<Kind, RankProjector>;
138
167
  /** Default codec for kinds without an explicit entry. */
139
168
  readonly defaultCodec?: IIdentityCodec;
169
+ /**
170
+ * Optional derived-index implementation. Defaults to a fresh {@link MemoryIndex} —
171
+ * omitting this parameter is byte-identical to the store's behavior before the
172
+ * parameter existed. When supplied, the store uses it for EVERY index operation
173
+ * it performs and never holds a second index, so an injected index is the store's
174
+ * only view of its own records:
175
+ *
176
+ * - `rebuild` — once, from the initial vault walk in `create()`.
177
+ * - `patch` — on every persisted write, delete, version invalidation, and
178
+ * cap-cull eviction.
179
+ * - `entries` — behind {@link IMemoryStore.list | list} /
180
+ * {@link IMemoryStore.listScoped | listScoped}, the keyed temporal reads, the
181
+ * write path's content-hash dedup and write-policy admission cohort, AND the
182
+ * temporal (versioned) write and delete paths, which resolve an entity's
183
+ * version history entirely from the index.
184
+ *
185
+ * That last group is the one to weigh before injecting anything other than a
186
+ * pass-through decorator: an index that filters, reorders, or otherwise reshapes
187
+ * `entries()` changes WRITE semantics, not just what reads return. Concretely,
188
+ * on a versioned kind the store derives an entity's whole version history from
189
+ * `entries()` filtered by scope, and that derivation decides which version a
190
+ * `put` treats as current (so what it dedups against and what it merges its
191
+ * patch over), which prior versions it stamps `invalid_at` on, what the
192
+ * admission cohort is, and which versions a `delete` tombstones. An index that
193
+ * hides a version makes it invisible to all of those — the FileTree still holds
194
+ * it, but the store will not supersede, invalidate, or tombstone it. On flat
195
+ * kinds the same reshaping changes what dedups and what a cap-cull policy
196
+ * evicts. A faithful delegating decorator — the intended use below — has no such
197
+ * effect. Note the store's keyed reads ({@link IMemoryStore.get | get} on a flat
198
+ * kind, and {@link IMemoryStore.getById | getById}) go to the FileTree, not the
199
+ * index; the FileTree remains the source of truth and the index stays a derived
200
+ * view.
201
+ *
202
+ * @remarks
203
+ * **This is an instrumentation seam, NOT a resident-memory fix.** The intended
204
+ * use is wrapping the shipped {@link MemoryIndex} in a decorator that counts and
205
+ * times the calls the store makes — resident bytes by kind, open cost against
206
+ * vault size, where the curve actually bends — so a decision about a partial-read
207
+ * redesign can be driven by measurements instead of estimates.
208
+ *
209
+ * It does NOT lower the store's resident-memory ceiling, and injecting a
210
+ * "persisted" or "lazy" index will not change that. {@link IMemoryIndex}'s read
211
+ * surface returns whole records by construction: `entries()` yields
212
+ * {@link IIndexedMemoryRecord}s and `byKind` / `byTag` / `byRecency` / `byRank`
213
+ * yield `IMemoryRecord<unknown>` — `{ envelope, body }` pairs with the body
214
+ * materialized. Any implementation satisfying the current contract must therefore
215
+ * be able to produce every body on demand. An injected index changes WHERE records
216
+ * come from; it does not change WHETHER bodies are held. Lowering the ceiling
217
+ * requires a partial-read (id-or-envelope-only) redesign of `IMemoryIndex` itself,
218
+ * which is separate, breaking, design-first work and is deliberately not part of
219
+ * this seam.
220
+ */
221
+ readonly index?: IMemoryIndex;
140
222
  /** Scope encoding. Defaults to {@link defaultMemoryScopeEncoding}. */
141
223
  readonly scopeEncoding?: (scope: MemoryScopeKey) => Result<string>;
142
224
  /**
@@ -283,11 +365,19 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
283
365
  */
284
366
  get skippedRecords(): ReadonlyArray<ISkippedRecord>;
285
367
  /**
286
- * Family-convention factory. Builds the derived index and a default LWW
287
- * policy, then performs an initial FileTree walk so an existing vault is
288
- * indexed (and the `seq` counter resumes past the highest persisted `seq`).
368
+ * Family-convention factory. Resolves the derived index (the caller's
369
+ * {@link IFileTreeMemoryStoreCreateParams.index | index} when supplied, a fresh
370
+ * {@link MemoryIndex} otherwise) and a default LWW policy, then performs an
371
+ * initial FileTree walk so an existing vault is indexed (and the `seq` counter
372
+ * resumes past the highest persisted `seq`).
289
373
  */
290
374
  static create(params: IFileTreeMemoryStoreCreateParams): Result<FileTreeMemoryStore>;
375
+ /**
376
+ * Resolve the derived index for a `create()`: the caller's injected
377
+ * {@link IMemoryIndex} verbatim, or a fresh {@link MemoryIndex} when none was
378
+ * supplied (the default that keeps an omitting caller byte-identical).
379
+ */
380
+ private static _resolveIndex;
291
381
  /** {@inheritDoc IMemoryStore.get} */
292
382
  get(kind: Kind, entityId: EntityId): Promise<Result<IMemoryRecord<unknown> | undefined>>;
293
383
  /** {@inheritDoc IMemoryStore.getById} */
@@ -548,6 +638,8 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
548
638
  private _stampRank;
549
639
  private _codecFor;
550
640
  private _policyFor;
641
+ /** {@inheritDoc IMemoryStore.dedupScopeFor} */
642
+ dedupScopeFor(kind: Kind): DedupScope;
551
643
  /**
552
644
  * Read and validate the record at `<scope>/<idStem>.md`, returning `undefined`
553
645
  * when the scope directory or file is absent. Verifies the on-disk id ↔
@@ -1 +1 @@
1
- {"version":3,"file":"fileTreeMemoryStore.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/fileTreeMemoryStore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fileTreeMemoryStore.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/fileTreeMemoryStore.ts"],"names":[],"mappings":"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"}
@@ -63,12 +63,14 @@ class FileTreeMemoryStore {
63
63
  return this._skippedRecords;
64
64
  }
65
65
  /**
66
- * Family-convention factory. Builds the derived index and a default LWW
67
- * policy, then performs an initial FileTree walk so an existing vault is
68
- * indexed (and the `seq` counter resumes past the highest persisted `seq`).
66
+ * Family-convention factory. Resolves the derived index (the caller's
67
+ * {@link IFileTreeMemoryStoreCreateParams.index | index} when supplied, a fresh
68
+ * {@link MemoryIndex} otherwise) and a default LWW policy, then performs an
69
+ * initial FileTree walk so an existing vault is indexed (and the `seq` counter
70
+ * resumes past the highest persisted `seq`).
69
71
  */
70
72
  static create(params) {
71
- return types_1.KnowledgeLwwPolicy.create().onSuccess((defaultPolicy) => index_1.MemoryIndex.create().onSuccess((index) => {
73
+ return types_1.KnowledgeLwwPolicy.create().onSuccess((defaultPolicy) => FileTreeMemoryStore._resolveIndex(params.index).onSuccess((index) => {
72
74
  var _a, _b, _c, _d, _e, _f, _g, _h;
73
75
  const store = new FileTreeMemoryStore({
74
76
  root: params.root,
@@ -91,6 +93,20 @@ class FileTreeMemoryStore {
91
93
  return store._initialIndex((_h = params.onRecordError) !== null && _h !== void 0 ? _h : 'fail').onSuccess(() => (0, ts_utils_1.succeed)(store));
92
94
  }));
93
95
  }
96
+ /**
97
+ * Resolve the derived index for a `create()`: the caller's injected
98
+ * {@link IMemoryIndex} verbatim, or a fresh {@link MemoryIndex} when none was
99
+ * supplied (the default that keeps an omitting caller byte-identical).
100
+ */
101
+ static _resolveIndex(index) {
102
+ // Nullish rather than strictly-undefined, matching how every sibling optional
103
+ // param in `create()` handles absence (`params.codecs ?? new Map()`, and so on).
104
+ // A JS caller — or a TS caller arriving through an `unknown` escape hatch —
105
+ // passing `null` otherwise gets `null` installed as the store's index and fails
106
+ // later inside `entries()` with a message that names neither the param nor the
107
+ // cause.
108
+ return index ? (0, ts_utils_1.succeed)(index) : index_1.MemoryIndex.create();
109
+ }
94
110
  /** {@inheritDoc IMemoryStore.get} */
95
111
  async get(kind, entityId) {
96
112
  var _a;
@@ -335,9 +351,11 @@ class FileTreeMemoryStore {
335
351
  * vector index, the on-disk file, and the derived index never interleave.
336
352
  */
337
353
  async _writeResolved(record, body, scope, idStem, hash) {
338
- var _a;
339
354
  const policy = this._policyFor(record.envelope.kind);
340
- const dedupScope = (_a = policy.dedupScope) !== null && _a !== void 0 ? _a : types_1.DEFAULT_DEDUP_SCOPE;
355
+ // Read through the public accessor so the write path and every external
356
+ // caller (the ingest orchestrator's layer-1) resolve dedup granularity from
357
+ // ONE place — there is no second declaration site.
358
+ const dedupScope = this.dedupScopeFor(record.envelope.kind);
341
359
  // Content-hash dedup runs BEFORE policy. Its granularity is the kind's
342
360
  // `dedupScope`:
343
361
  // - 'content': an identical { kind, body, links } triple ANYWHERE in the
@@ -661,7 +679,6 @@ class FileTreeMemoryStore {
661
679
  * version's `valid_at` lower-bounds its interval.
662
680
  */
663
681
  async _putVersioned(record, body, codec, scope) {
664
- var _a;
665
682
  const envelope = record.envelope;
666
683
  const entityId = envelope.entityId;
667
684
  const kind = envelope.kind;
@@ -673,7 +690,8 @@ class FileTreeMemoryStore {
673
690
  const priorCurrents = versions.filter(types_1.isVersionCurrent);
674
691
  const current = (0, types_1.selectCurrentVersion)(versions);
675
692
  const policy = this._policyFor(kind);
676
- const dedupScope = (_a = policy.dedupScope) !== null && _a !== void 0 ? _a : types_1.DEFAULT_DEDUP_SCOPE;
693
+ // Same single-owner read as the flat path (see `_writeResolved`).
694
+ const dedupScope = this.dedupScopeFor(kind);
677
695
  return this._contentHash(kind, body, envelope.links).thenOnSuccess((hash) => {
678
696
  // Entity-scoped dedup: a re-put is a no-op only when the CURRENT content AND
679
697
  // its mutable metadata are unchanged (does not spawn a redundant version).
@@ -921,6 +939,11 @@ class FileTreeMemoryStore {
921
939
  var _a;
922
940
  return (_a = this._writePolicies.get(kind)) !== null && _a !== void 0 ? _a : this._defaultPolicy;
923
941
  }
942
+ /** {@inheritDoc IMemoryStore.dedupScopeFor} */
943
+ dedupScopeFor(kind) {
944
+ var _a;
945
+ return (_a = this._policyFor(kind).dedupScope) !== null && _a !== void 0 ? _a : types_1.DEFAULT_DEDUP_SCOPE;
946
+ }
924
947
  /**
925
948
  * Read and validate the record at `<scope>/<idStem>.md`, returning `undefined`
926
949
  * when the scope directory or file is absent. Verifies the on-disk id ↔