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

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 (164) 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/linkTraversalRetriever.js +12 -26
  6. package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  7. package/dist/packlets/retrieve/recencyRetriever.js +7 -7
  8. package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
  9. package/dist/packlets/retrieve/retriever.js +91 -10
  10. package/dist/packlets/retrieve/retriever.js.map +1 -1
  11. package/dist/packlets/retrieve/semanticRetriever.js +16 -16
  12. package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
  13. package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
  14. package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  15. package/dist/packlets/retrieve/tagRetriever.js +7 -7
  16. package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
  17. package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
  18. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
  19. package/dist/packlets/store/coverage.js +6 -0
  20. package/dist/packlets/store/coverage.js.map +1 -0
  21. package/dist/packlets/store/fileTreeMemoryStore.js +221 -79
  22. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  23. package/dist/packlets/store/index.js +4 -0
  24. package/dist/packlets/store/index.js.map +1 -1
  25. package/dist/packlets/store/listSelection.js +36 -0
  26. package/dist/packlets/store/listSelection.js.map +1 -0
  27. package/dist/packlets/store/memoryStore.js +6 -0
  28. package/dist/packlets/store/memoryStore.js.map +1 -0
  29. package/dist/packlets/store/reconcile.js +6 -0
  30. package/dist/packlets/store/reconcile.js.map +1 -0
  31. package/dist/packlets/store/storeCoverage.js +102 -0
  32. package/dist/packlets/store/storeCoverage.js.map +1 -0
  33. package/dist/packlets/store/storeReconcile.js +122 -0
  34. package/dist/packlets/store/storeReconcile.js.map +1 -0
  35. package/dist/packlets/store/vectorMaintenance.js +116 -8
  36. package/dist/packlets/store/vectorMaintenance.js.map +1 -1
  37. package/dist/packlets/store/vectorRecordSource.js +44 -0
  38. package/dist/packlets/store/vectorRecordSource.js.map +1 -0
  39. package/dist/packlets/tools/memoryTools.js +25 -2
  40. package/dist/packlets/tools/memoryTools.js.map +1 -1
  41. package/dist/packlets/types/envelope.js +25 -0
  42. package/dist/packlets/types/envelope.js.map +1 -1
  43. package/dist/packlets/types/index.js +1 -0
  44. package/dist/packlets/types/index.js.map +1 -1
  45. package/dist/packlets/types/recordResolver.js +6 -0
  46. package/dist/packlets/types/recordResolver.js.map +1 -0
  47. package/dist/packlets/types/temporal.js.map +1 -1
  48. package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
  49. package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  50. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +67 -12
  51. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  52. package/dist/packlets/vector/rebuildHelpers.js +38 -0
  53. package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
  54. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  55. package/dist/ts-agent-memory.d.ts +1035 -106
  56. package/lib/packlets/index/memoryIndex.d.ts +118 -27
  57. package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
  58. package/lib/packlets/index/memoryIndex.js +23 -16
  59. package/lib/packlets/index/memoryIndex.js.map +1 -1
  60. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
  61. package/lib/packlets/ingest/orchestrator.js +13 -1
  62. package/lib/packlets/ingest/orchestrator.js.map +1 -1
  63. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
  64. package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
  65. package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
  66. package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
  67. package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
  68. package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
  69. package/lib/packlets/retrieve/recencyRetriever.js +6 -6
  70. package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
  71. package/lib/packlets/retrieve/retriever.d.ts +88 -7
  72. package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
  73. package/lib/packlets/retrieve/retriever.js +94 -9
  74. package/lib/packlets/retrieve/retriever.js.map +1 -1
  75. package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
  76. package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
  77. package/lib/packlets/retrieve/semanticRetriever.js +15 -15
  78. package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
  79. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
  80. package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
  81. package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
  82. package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
  83. package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
  84. package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
  85. package/lib/packlets/retrieve/tagRetriever.js +6 -6
  86. package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
  87. package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
  88. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
  89. package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
  90. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
  91. package/lib/packlets/store/coverage.d.ts +102 -0
  92. package/lib/packlets/store/coverage.d.ts.map +1 -0
  93. package/lib/packlets/store/coverage.js +7 -0
  94. package/lib/packlets/store/coverage.js.map +1 -0
  95. package/lib/packlets/store/fileTreeMemoryStore.d.ts +53 -166
  96. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  97. package/lib/packlets/store/fileTreeMemoryStore.js +221 -79
  98. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  99. package/lib/packlets/store/index.d.ts +4 -0
  100. package/lib/packlets/store/index.d.ts.map +1 -1
  101. package/lib/packlets/store/index.js +4 -0
  102. package/lib/packlets/store/index.js.map +1 -1
  103. package/lib/packlets/store/listSelection.d.ts +101 -0
  104. package/lib/packlets/store/listSelection.d.ts.map +1 -0
  105. package/lib/packlets/store/listSelection.js +40 -0
  106. package/lib/packlets/store/listSelection.js.map +1 -0
  107. package/lib/packlets/store/memoryStore.d.ts +237 -0
  108. package/lib/packlets/store/memoryStore.d.ts.map +1 -0
  109. package/lib/packlets/store/memoryStore.js +7 -0
  110. package/lib/packlets/store/memoryStore.js.map +1 -0
  111. package/lib/packlets/store/reconcile.d.ts +82 -0
  112. package/lib/packlets/store/reconcile.d.ts.map +1 -0
  113. package/lib/packlets/store/reconcile.js +7 -0
  114. package/lib/packlets/store/reconcile.js.map +1 -0
  115. package/lib/packlets/store/storeCoverage.d.ts +45 -0
  116. package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
  117. package/lib/packlets/store/storeCoverage.js +105 -0
  118. package/lib/packlets/store/storeCoverage.js.map +1 -0
  119. package/lib/packlets/store/storeReconcile.d.ts +41 -0
  120. package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
  121. package/lib/packlets/store/storeReconcile.js +125 -0
  122. package/lib/packlets/store/storeReconcile.js.map +1 -0
  123. package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
  124. package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
  125. package/lib/packlets/store/vectorMaintenance.js +117 -8
  126. package/lib/packlets/store/vectorMaintenance.js.map +1 -1
  127. package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
  128. package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
  129. package/lib/packlets/store/vectorRecordSource.js +47 -0
  130. package/lib/packlets/store/vectorRecordSource.js.map +1 -0
  131. package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
  132. package/lib/packlets/tools/memoryTools.js +25 -2
  133. package/lib/packlets/tools/memoryTools.js.map +1 -1
  134. package/lib/packlets/types/envelope.d.ts +24 -2
  135. package/lib/packlets/types/envelope.d.ts.map +1 -1
  136. package/lib/packlets/types/envelope.js +26 -0
  137. package/lib/packlets/types/envelope.js.map +1 -1
  138. package/lib/packlets/types/index.d.ts +1 -0
  139. package/lib/packlets/types/index.d.ts.map +1 -1
  140. package/lib/packlets/types/index.js +1 -0
  141. package/lib/packlets/types/index.js.map +1 -1
  142. package/lib/packlets/types/recordResolver.d.ts +39 -0
  143. package/lib/packlets/types/recordResolver.d.ts.map +1 -0
  144. package/lib/packlets/types/recordResolver.js +7 -0
  145. package/lib/packlets/types/recordResolver.js.map +1 -0
  146. package/lib/packlets/types/temporal.d.ts +26 -6
  147. package/lib/packlets/types/temporal.d.ts.map +1 -1
  148. package/lib/packlets/types/temporal.js.map +1 -1
  149. package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
  150. package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
  151. package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
  152. package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
  153. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +6 -3
  154. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
  155. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +66 -11
  156. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
  157. package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
  158. package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
  159. package/lib/packlets/vector/rebuildHelpers.js +42 -0
  160. package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
  161. package/lib/packlets/vector/vectorIndex.d.ts +270 -15
  162. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  163. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  164. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"vectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport { IEdgeTarget, IMemoryRecord } from '../types';\n\n/**\n * A half-open `[start, end)` span into a record's body — the in-record locator a\n * {@link IFragmentVectorIndex} may carry on a fragment. `start` is inclusive,\n * `end` exclusive. The unit (character / byte / token offsets) is the consumer's\n * choice: the index stores the two integers opaquely and never interprets them,\n * so they line up with whatever locator the consumer's own read side uses.\n *\n * @remarks\n * **The span is advisory.** It names the region of the body a fragment was\n * *derived from*; it is NOT a slice guaranteed to reproduce the fragment's text.\n * `body.slice(start, end)` round-trips only under a segmenter that merely chooses\n * boundaries. Under a **rewriting** segmenter — one that turns a span into a\n * curated block, an increasingly common ingestion shape when a model both selects\n * and rewrites — the fragment text is not a substring of the body at all, and the\n * fragmentation is not re-derivable from the body. Treat the span as a pointer for\n * locating context, never as an extraction recipe.\n *\n * A fragment whose provenance cannot honestly be expressed as a body span should\n * omit the locator entirely and carry an {@link IEmbeddedFragment.fragmentId}\n * instead.\n * @public\n */\nexport interface IFragmentLocator {\n /** Inclusive start offset into the record body. */\n readonly start: number;\n /** Exclusive end offset into the record body. */\n readonly end: number;\n}\n\n/**\n * A single hit returned by {@link IVectorIndex.query} (or\n * {@link IFragmentVectorIndex.query}): the matched record's scope-qualified\n * {@link IEdgeTarget | address} and the backend's similarity score (higher = more\n * similar; the exact scale is backend-defined). Hits are returned in descending\n * score order.\n *\n * @remarks\n * The address is a `(scope, id)` pair, NOT a bare {@link MemoryId} — per-scope\n * codecs (e.g. the medium-term codec's `turn-<n>` stems) legally mint the same\n * stem under different scopes, so a bare id could not disambiguate two records\n * that share a stem. The caller re-resolves the hit against the record index by\n * the same scoped address.\n *\n * **No single field discriminates a fragment hit from a record-granular hit.** A\n * record hit carries neither `locator` nor `fragmentId`; a fragment hit carries at\n * least one of the two, but not necessarily any particular one — a fragment with a\n * body span but no consumer-minted id, and a fragment with an id but no honest span,\n * are both legal. Testing one field for presence therefore cannot tell you which\n * kind of hit you hold.\n *\n * That \"at least one\" requirement is enforced on the upsert side by\n * {@link embeddedFragmentConverter} — a different boundary from this type — and is\n * deliberately NOT offered here as a discriminator either. A caller keyed off it\n * would be coupled to an invariant this type does not own, and would fail silently\n * if the invariant were ever relaxed.\n *\n * **The robust rule is that fragment-ness is determined by which index produced the\n * hit**: {@link IFragmentVectorIndex.query} returns fragment hits and\n * {@link IVectorIndex.query} returns record hits. The caller chose the index it\n * queried, so it already knows which kind it is holding.\n *\n * Note in particular that an absent `locator` now carries **two** distinct meanings\n * — a record-granular hit, or a fragment with no honest body span (see\n * {@link IFragmentLocator}) — which is precisely why presence-branching is unsafe.\n * @public\n */\nexport interface IVectorQueryHit {\n /** The scope-qualified address of the matched record. */\n readonly target: IEdgeTarget;\n /** Backend similarity score; higher is more similar. */\n readonly score: number;\n /**\n * The advisory in-record span the matched fragment was derived from, when the\n * producing fragment carried one. Absent on record-granular hits AND on fragment\n * hits with no honest span — see the remarks above; do not branch on its presence.\n */\n readonly locator?: IFragmentLocator;\n /**\n * The opaque identity the producing fragment was stored with, carried back\n * verbatim. Absent on record-granular hits AND on fragment hits stored without\n * one — see the remarks above; do not branch on its presence.\n */\n readonly fragmentId?: string;\n}\n\n/**\n * The vector-index seam an embedding backend implements to make\n * {@link SemanticRetriever | semantic recall} operational.\n *\n * @remarks\n * Vectors cross this seam as `Float32Array` (the in-memory representation an\n * embedding model produces); `number[]` is reserved for the JSON-wire edges\n * (e.g. a provider's embedding response). The in-package brute-force cosine\n * implementation is {@link InMemoryCosineIndex}; a consumer can swap an external\n * ANN backend behind the same seam once N grows beyond the in-memory regime.\n *\n * Every operation returns a `Result` (async, since a real backend does I/O) so\n * failure is explicit and never throws across the seam.\n * @public\n */\nexport interface IVectorIndex {\n /**\n * Add (or replace) the embedding for the scope-qualified `target`. Returns the\n * opaque {@link IMemoryEnvelope.embeddingRef | embeddingRef} the store stamps\n * onto the envelope so a later read knows the record is embedded. Keying on the\n * `(scope, id)` address (not a bare id) is load-bearing: two records that share\n * a filename stem across scopes must not clobber each other's embedding.\n */\n add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>>;\n\n /**\n * Remove the embedding for the scope-qualified `target`. Returns the removed\n * target. Idempotent — removing a target with no embedding still succeeds\n * (returns the target).\n */\n remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;\n\n /**\n * Return the `topK` nearest records to `vector`, in descending score order.\n */\n query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;\n\n /**\n * The number of vectors currently held.\n *\n * @remarks\n * On the contract because without it a caller cannot distinguish *\"the index is\n * empty\"* from *\"nothing matched\"*: {@link IVectorIndex.query} answers an empty\n * index with `succeed([])`, which is indistinguishable from a genuine miss. The\n * only other check available to a caller — \"is a vector index wired?\" — tests the\n * **wiring**, and that stays true while the index holds nothing.\n *\n * Note the narrow scope: this answers *how many vectors are held*, **not** how\n * many there ought to be. Full coverage — \"is every record that should be indexed\n * actually indexed?\" — still requires comparing against the record source and\n * {@link IMemoryStore.embedsKind}.\n *\n * Synchronous and non-`Result` because both shipped implementations can answer it\n * without I/O that can fail — the in-memory index reads a `Map`'s size, and the\n * SQLite-backed one a prepared `COUNT` against an open connection it already owns.\n */\n readonly size: number;\n\n /**\n * Re-embed every record from `source` and rebuild the index from scratch — the\n * **backfill / reconcile** operation.\n *\n * @remarks\n * On the contract because a persisted index is unusable without it. Records\n * written while the index was unwired, a re-embed after a dimension change (where\n * the backend supports one — a `vec0`-backed table's dimension is fixed at\n * creation, so there it needs a drop-and-re-index instead), and reconciliation\n * after a swallowed embed-on-write failure are all unreachable otherwise — and the store's own docstring already promises *\"the derived index\n * is reconciled by a later `rebuild`\"*, a promise the contract could not keep for\n * any index but the bundled one. A caller moving from the bundled implementation\n * to a persistent one found the swap type-checked everywhere **except** the one\n * place it backfills, which is the place that mattered.\n *\n * See {@link IVectorRebuildReport} for what it reports and\n * {@link IVectorRebuildOptions} for the failure mode.\n */\n rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<Result<IVectorRebuildReport>>;\n}\n\n/**\n * One embedded fragment of a record: the fragment's vector, plus at least one of the\n * two ways to identify it — its advisory in-record {@link IFragmentLocator | span}\n * and/or an opaque consumer-minted {@link IEmbeddedFragment.fragmentId | fragmentId}.\n * Produced by a {@link FragmentEmbedder} and stored via\n * {@link IFragmentVectorIndex.addFragments}.\n *\n * @remarks\n * Both identity fields are optional **in the type**, but the \"at least one\"\n * requirement is real — a fragment carrying neither is unidentifiable at the read\n * side. It is enforced by {@link embeddedFragmentConverter} (and re-checked by the\n * in-package index implementations) rather than by a conditional-required union\n * (`{ locator; fragmentId? } | { locator?; fragmentId }`), which was considered and\n * declined: the union costs at every construction site and buys nothing at the read\n * site, where each field reads as `… | undefined` either way.\n * @public\n */\nexport interface IEmbeddedFragment {\n /**\n * The region of the record body this fragment was derived from, when one can be\n * stated honestly. Advisory — see {@link IFragmentLocator}; it is NOT a slice that\n * reproduces the fragment text. Omit it for a fragment with no honest body span (a\n * rewriting segmenter), in which case `fragmentId` must be supplied.\n */\n readonly locator?: IFragmentLocator;\n /**\n * An opaque, consumer-minted identity for this fragment, carried verbatim through\n * the index and returned on the corresponding {@link IVectorQueryHit}. The index\n * **never parses it, never filters on it, and never assigns meaning to it** — it is\n * a bytestring, not part of the query path. It exists so a fragment stays\n * identifiable when its text is not re-derivable from the record body.\n *\n * The guarantee is \"we never parse it\", NOT \"we keep it stable\". Because\n * `addFragments` is whole-record-replace, an updated record re-emits its entire\n * fragment set, so **any stability of a fragment id across re-embeds is the\n * consumer's responsibility**, not the index's.\n */\n readonly fragmentId?: string;\n /** The embedding vector for this fragment. */\n readonly vector: Float32Array;\n}\n\n/**\n * The fragment-granular sibling of {@link IVectorIndex}: instead of one vector per\n * record it holds many vectors per record, each tagged with the identity its\n * {@link IEmbeddedFragment} carried, and its `query` returns per-fragment hits\n * carrying that identity back. This is the seam behind sub-document semantic search\n * — the \"discovery\" half of a search-then-read contract, where a hit tells the\n * consumer which record AND which fragment of it to read.\n *\n * @remarks\n * Deliberately NOT `extends IVectorIndex`: an index holding many vectors per record\n * has no well-defined single-vector `add(target, vector)`. It is a parallel contract\n * with three operations — `addFragments`, `remove`, `query` — reusing\n * {@link IVectorQueryHit}, on which both `locator` and `fragmentId` are optional. A\n * fragment hit populates whichever of the two its stored fragment carried; see\n * {@link IVectorQueryHit} for why that is not a discriminator and why fragment-ness\n * is determined by the index queried, not by field presence. Kept distinct from the\n * record-granular index per the consumer contract: memory recall stays\n * record-granular; sub-document knowledge uses a separate fragment index.\n * @public\n */\nexport interface IFragmentVectorIndex {\n /**\n * Add (or replace) all fragments for the scope-qualified `target`. Whole-record\n * semantics: every fragment previously held for `target` is dropped and replaced\n * by `fragments`, so a re-authored document never leaves stale fragments behind.\n * Returns the number of fragments now held for the record.\n */\n addFragments(target: IEdgeTarget, fragments: ReadonlyArray<IEmbeddedFragment>): Promise<Result<number>>;\n\n /**\n * Remove every fragment for the scope-qualified `target`. Returns the removed\n * target. Idempotent — removing a target with no fragments still succeeds.\n */\n remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;\n\n /**\n * Return the `topK` nearest fragments to `vector`, in descending score order,\n * each hit carrying its record `target` plus whichever of `locator` /\n * `fragmentId` the stored fragment was added with. When\n * `maxPerRecord` is supplied, no more than that many fragments of any single\n * record appear in the result — the cap is applied during selection (before the\n * `topK` cut) so one long document cannot crowd out others.\n */\n query(\n vector: Float32Array,\n topK: number,\n maxPerRecord?: number\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;\n}\n\n/**\n * How a vector-index rebuild treats a record it cannot index — whether the\n * **embedding** failed or the subsequent **add** did. Both are governed by this\n * one mode; neither is unconditionally fatal.\n *\n * @remarks\n * Deliberately mirrors the store's own open-time `onRecordError` mode, including\n * its default: `'fail'` preserves the historical all-or-nothing contract exactly,\n * and `'skip'` is opt-in. Defined here rather than imported from the store packlet\n * — the `vector` packlet does not depend on `store`, and the two modes describe\n * different domains that merely happen to share a shape.\n *\n * A **decline** (a {@link MemoryEmbedder} resolving `undefined`) is not an error\n * and is unaffected by this mode: it is always **excluded** from the index and\n * counted on {@link IVectorRebuildReport.declined}, **never** appearing in\n * {@link IVectorRebuildReport.skipped}. The word is worth being careful with here:\n * `skipped` is now a formal field meaning *a fault*, and a decline is the opposite.\n * @public\n */\nexport type VectorRebuildErrorMode = 'skip' | 'fail';\n\n/**\n * A record a rebuild could not index — because the embed failed or because the\n * subsequent add did — retained so a partial rebuild reports what it lost rather\n * than merely how much it kept.\n * @public\n */\nexport interface ISkippedVectorRecord {\n /** The scope-qualified address of the record that could not be indexed. */\n readonly target: IEdgeTarget;\n /** The failure message, from either the embed or the subsequent add. */\n readonly error: string;\n}\n\n/**\n * What a rebuild actually did — the structural answer to \"is this index complete?\".\n *\n * @remarks\n * A bare count cannot distinguish the three ways a record can be absent from the\n * index, and that distinction is the entire point: **`declined` was intentional,\n * `skipped` was a fault, and neither is the same as \"never attempted\"**. A caller\n * deriving coverage from a count alone cannot tell an embedder outage from a\n * deliberate policy, which is precisely the confusion this type exists to end.\n * @public\n */\nexport interface IVectorRebuildReport {\n /** Records embedded and added to the index. */\n readonly indexed: number;\n /** Records the embedder deliberately declined (resolved `undefined`). */\n readonly declined: number;\n /**\n * Records whose embedding or add FAILED and were skipped. Non-empty only under\n * {@link VectorRebuildErrorMode | `onRecordError: 'skip'`} — under `'fail'` the\n * first failure aborts the rebuild and no report is returned at all.\n */\n readonly skipped: ReadonlyArray<ISkippedVectorRecord>;\n}\n\n/**\n * Options for a vector-index rebuild.\n * @public\n */\nexport interface IVectorRebuildOptions {\n /**\n * How to treat a record the rebuild cannot index — an embed failure OR an add\n * failure. Defaults to `'fail'` — the historical behavior, unchanged for every\n * existing caller.\n */\n readonly onRecordError?: VectorRebuildErrorMode;\n}\n\n/**\n * Embeds a complete record into a vector for the store's embed-on-write hook.\n * Async and `Result`-returning, since a real embedder does a network call (cloud\n * provider) or in-process model inference. The consumer wires this — the core\n * package never calls an embedding provider directly, staying embedder-agnostic.\n *\n * @remarks\n * Resolving to `undefined` means **\"intentionally not embedded\"** — a deliberate\n * decline, not an error. The record is stored without an embedding reference, no\n * failure is reported, and **the decline itself logs nothing**. This is distinct\n * from a `Failure`, which means the embedder *tried and could not*.\n *\n * \"Logs nothing\" is a statement about the decline, not a promise of silence: a\n * decline on a record that was already embedded also prunes the vector that\n * reference named, and if that prune fails it is a genuine fault and warns like\n * any other. What a decline never does is warn merely for having happened.\n *\n * The distinction is load-bearing wherever the two are treated differently. On the\n * rebuild path a declined record is **excluded** from the index and counted on\n * {@link IVectorRebuildReport.declined}; a failed one is a genuine error and, under\n * `onRecordError: 'skip'`, is reported on {@link IVectorRebuildReport.skipped}. Collapsing \"I chose not to\" into `fail` would\n * make a deliberate policy indistinguishable from an embedder outage in the logs,\n * and would put a routine decision on whatever error path the caller has wired.\n *\n * The embedder receives the whole record, so the usual reason to decline is the\n * record's `kind` — a control or bookkeeping row that no query should ever return.\n *\n * @public\n */\nexport type MemoryEmbedder = (record: IMemoryRecord<unknown>) => Promise<Result<Float32Array | undefined>>;\n\n/**\n * The fragment-granular sibling of {@link MemoryEmbedder}: chunks a record's body\n * and embeds each chunk, returning one {@link IEmbeddedFragment} per chunk. The\n * chunking policy (window size, overlap) lives entirely in the consumer's embedder\n * — the core stays chunking-agnostic, exactly as it stays embedder-agnostic for\n * the record-granular path. Used by the store's fragment-embed-on-write hook.\n * @public\n */\nexport type FragmentEmbedder = (\n record: IMemoryRecord<unknown>\n) => Promise<Result<ReadonlyArray<IEmbeddedFragment>>>;\n\n/**\n * A record paired with its scope-qualified {@link IEdgeTarget | address}, as\n * yielded by {@link IMemoryRecordSource.list}. The address is required because\n * {@link InMemoryCosineIndex.rebuild} keys each re-embedded entry on the\n * scope-qualified target, not a bare {@link MemoryId} — two records that share a\n * filename stem across scopes must not collide when the whole vault is re-indexed.\n * @public\n */\nexport interface IScopedMemoryRecord {\n /** The record's scope-qualified `(scope, id)` address. */\n readonly target: IEdgeTarget;\n /** The record itself, passed to the embedder. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * The minimal record-source surface {@link InMemoryCosineIndex.rebuild} reads to\n * re-embed an entire vault. Each entry carries the record's scope-qualified\n * address (see {@link IScopedMemoryRecord}) so the rebuild keys the vector index\n * exactly as the incremental embed-on-write path does. A consumer backs this with\n * the store's scoped index — the vector packlet does not import the store packlet\n * (which depends on the vector packlet for {@link IVectorIndex}, so the reverse\n * import would be a cycle).\n * @public\n */\nexport interface IMemoryRecordSource {\n /** List every record in the vault, each paired with its scoped address. */\n list(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;\n}\n"]}
1
+ {"version":3,"file":"vectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/vector/vectorIndex.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { DetailedResult, Result } from '@fgv/ts-utils';\nimport { IEdgeTarget, IMemoryRecord, Kind } from '../types';\n\n/**\n * A half-open `[start, end)` span into a record's body — the in-record locator a\n * {@link IFragmentVectorIndex} may carry on a fragment. `start` is inclusive,\n * `end` exclusive. The unit (character / byte / token offsets) is the consumer's\n * choice: the index stores the two integers opaquely and never interprets them,\n * so they line up with whatever locator the consumer's own read side uses.\n *\n * @remarks\n * **The span is advisory.** It names the region of the body a fragment was\n * *derived from*; it is NOT a slice guaranteed to reproduce the fragment's text.\n * `body.slice(start, end)` round-trips only under a segmenter that merely chooses\n * boundaries. Under a **rewriting** segmenter — one that turns a span into a\n * curated block, an increasingly common ingestion shape when a model both selects\n * and rewrites — the fragment text is not a substring of the body at all, and the\n * fragmentation is not re-derivable from the body. Treat the span as a pointer for\n * locating context, never as an extraction recipe.\n *\n * A fragment whose provenance cannot honestly be expressed as a body span should\n * omit the locator entirely and carry an {@link IEmbeddedFragment.fragmentId}\n * instead.\n * @public\n */\nexport interface IFragmentLocator {\n /** Inclusive start offset into the record body. */\n readonly start: number;\n /** Exclusive end offset into the record body. */\n readonly end: number;\n}\n\n/**\n * A single hit returned by {@link IVectorIndex.query} (or\n * {@link IFragmentVectorIndex.query}): the matched record's scope-qualified\n * {@link IEdgeTarget | address} and the backend's similarity score (higher = more\n * similar; the exact scale is backend-defined). Hits are returned in descending\n * score order.\n *\n * @remarks\n * The address is a `(scope, id)` pair, NOT a bare {@link MemoryId} — per-scope\n * codecs (e.g. the medium-term codec's `turn-<n>` stems) legally mint the same\n * stem under different scopes, so a bare id could not disambiguate two records\n * that share a stem. The caller re-resolves the hit against the record index by\n * the same scoped address.\n *\n * **No single field discriminates a fragment hit from a record-granular hit.** A\n * record hit carries neither `locator` nor `fragmentId`; a fragment hit carries at\n * least one of the two, but not necessarily any particular one — a fragment with a\n * body span but no consumer-minted id, and a fragment with an id but no honest span,\n * are both legal. Testing one field for presence therefore cannot tell you which\n * kind of hit you hold.\n *\n * That \"at least one\" requirement is enforced on the upsert side by\n * {@link embeddedFragmentConverter} — a different boundary from this type — and is\n * deliberately NOT offered here as a discriminator either. A caller keyed off it\n * would be coupled to an invariant this type does not own, and would fail silently\n * if the invariant were ever relaxed.\n *\n * **The robust rule is that fragment-ness is determined by which index produced the\n * hit**: {@link IFragmentVectorIndex.query} returns fragment hits and\n * {@link IVectorIndex.query} returns record hits. The caller chose the index it\n * queried, so it already knows which kind it is holding.\n *\n * Note in particular that an absent `locator` now carries **two** distinct meanings\n * — a record-granular hit, or a fragment with no honest body span (see\n * {@link IFragmentLocator}) — which is precisely why presence-branching is unsafe.\n * @public\n */\nexport interface IVectorQueryHit {\n /** The scope-qualified address of the matched record. */\n readonly target: IEdgeTarget;\n /** Backend similarity score; higher is more similar. */\n readonly score: number;\n /**\n * The advisory in-record span the matched fragment was derived from, when the\n * producing fragment carried one. Absent on record-granular hits AND on fragment\n * hits with no honest span — see the remarks above; do not branch on its presence.\n */\n readonly locator?: IFragmentLocator;\n /**\n * The opaque identity the producing fragment was stored with, carried back\n * verbatim. Absent on record-granular hits AND on fragment hits stored without\n * one — see the remarks above; do not branch on its presence.\n */\n readonly fragmentId?: string;\n}\n\n/**\n * The vector-index seam an embedding backend implements to make\n * {@link SemanticRetriever | semantic recall} operational.\n *\n * @remarks\n * Vectors cross this seam as `Float32Array` (the in-memory representation an\n * embedding model produces); `number[]` is reserved for the JSON-wire edges\n * (e.g. a provider's embedding response). The in-package brute-force cosine\n * implementation is {@link InMemoryCosineIndex}; a consumer can swap an external\n * ANN backend behind the same seam once N grows beyond the in-memory regime.\n *\n * Every operation returns a `Result` (async, since a real backend does I/O) so\n * failure is explicit and never throws across the seam.\n * @public\n */\nexport interface IVectorIndex {\n /**\n * Add (or replace) the embedding for the scope-qualified `target`. Returns the\n * opaque {@link IMemoryEnvelope.embeddingRef | embeddingRef} the store stamps\n * onto the envelope so a later read knows the record is embedded. Keying on the\n * `(scope, id)` address (not a bare id) is load-bearing: two records that share\n * a filename stem across scopes must not clobber each other's embedding.\n */\n add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>>;\n\n /**\n * Remove the embedding for the scope-qualified `target`. Returns the removed\n * target. Idempotent — removing a target with no embedding still succeeds\n * (returns the target).\n */\n remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;\n\n /**\n * Return the `topK` nearest records to `vector`, in descending score order.\n */\n query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;\n\n /**\n * Whether this index holds a vector for the scope-qualified `target`.\n *\n * @remarks\n * On the contract because it is what makes a **targeted** repair possible, and\n * the reason is sharper than convenience: the only other way to ask *\"is this\n * record indexed?\"* is {@link IMemoryEnvelope.embeddingRef} — and that field is\n * the store's **belief**, which is wrong in precisely the situation a repair\n * runs in. A reopened vault backed by a fresh in-memory index carries an\n * `embeddingRef` on every record while holding no vectors at all. **A repair\n * that trusts the field it is repairing is not a repair.**\n *\n * It also makes a case *detectable* that an `embeddingRef`-only check cannot\n * see at all: the index holds the vector but the envelope lost its reference\n * (a failure swallowed after the vector was committed). That record needs its\n * reference restamped and **no embedder call**, which is only knowable by\n * asking the index.\n *\n * `Promise<Result<boolean>>`, unlike the synchronous {@link IVectorIndex.size},\n * because the two are not the same kind of accessor and should not be made to\n * look alike: `size` is a count both shipped implementations hold or can read\n * without a failure mode, while `has` on a durable backend is a keyed query\n * that can fail. Idempotent and side-effect-free; an absent target is\n * `succeed(false)`, never a failure.\n */\n has(target: IEdgeTarget): Promise<Result<boolean>>;\n\n /**\n * The number of vectors currently held.\n *\n * @remarks\n * On the contract because without it a caller cannot distinguish *\"the index is\n * empty\"* from *\"nothing matched\"*: {@link IVectorIndex.query} answers an empty\n * index with `succeed([])`, which is indistinguishable from a genuine miss. The\n * only other check available to a caller — \"is a vector index wired?\" — tests the\n * **wiring**, and that stays true while the index holds nothing.\n *\n * Note the narrow scope: this answers *how many vectors are held*, **not** how\n * many there ought to be. Full coverage — \"is every record that should be indexed\n * actually indexed?\" — still requires comparing against the record source and\n * {@link IMemoryStore.embedsKind}.\n *\n * Synchronous and non-`Result` because both shipped implementations can answer it\n * without I/O that can fail — the in-memory index reads a `Map`'s size, and the\n * SQLite-backed one a prepared `COUNT` against an open connection it already owns.\n */\n readonly size: number;\n\n /**\n * Re-embed every record from `source` and rebuild the index from scratch — the\n * **backfill / reconcile** operation.\n *\n * @remarks\n * On the contract because a persisted index is unusable without it. Records\n * written while the index was unwired, a re-embed after a dimension change (where\n * the backend supports one — a `vec0`-backed table's dimension is fixed at\n * creation, so there it needs a drop-and-re-index instead), and reconciliation\n * after a swallowed embed-on-write failure are all unreachable otherwise — and the store's own docstring already promises *\"the derived index\n * is reconciled by a later `rebuild`\"*, a promise the contract could not keep for\n * any index but the bundled one. A caller moving from the bundled implementation\n * to a persistent one found the swap type-checked everywhere **except** the one\n * place it backfills, which is the place that mattered.\n *\n * See {@link IVectorRebuildReport} for what it reports and\n * {@link IVectorRebuildOptions} for the failure mode.\n *\n * **A failure carries the report too, on the `detail`** — coverage is most\n * wanted exactly when a rebuild did not complete, and withholding it there made\n * the answer depend on the error-handling mode rather than on the question. The\n * `'fail'` contract itself is unchanged: it still resets, still aborts, still\n * returns a failure. It simply also says what it had established before it\n * stopped. See {@link IVectorRebuildReport} for how to read a report that\n * arrived on a failure — it describes the attempt, not the surviving index.\n *\n * On success the report is the **value** — that is where it belongs, and the\n * `detail` is not also populated. A failure carries no report when nothing was\n * attempted: always a `source.list()` failure, and additionally whatever\n * pre-loop step an implementation needs before it can start (the durable\n * `SqliteVecVectorIndex` must clear its table, and a failure to do so is such a\n * case). Those leave the existing index untouched, so an all-zero report would\n * describe an index the call never disturbed.\n *\n * **A rebuild does NOT re-establish the vector dimension on a persistent\n * index, and this is the one place the two shipped implementations genuinely\n * differ.** The in-memory indexes forget their dimension when they reset, so a\n * rebuild with a different-dimension embedder simply re-establishes it. A\n * `vec0`-backed index cannot: its reset is a `DELETE FROM`, the table's\n * declared dimension is part of its schema, and `vec0` has no `ALTER TABLE`.\n * So a rebuild that changes dimension **succeeds in memory and fails on\n * SQLite**, where it needs the same drop-and-re-index the package README\n * prescribes for any schema change (drop the table, or point the index at a\n * fresh `tableName`). Only embedding time is at risk — vectors are derived and\n * the vault records remain authoritative.\n */\n rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>>;\n}\n\n/**\n * One embedded fragment of a record: the fragment's vector, plus at least one of the\n * two ways to identify it — its advisory in-record {@link IFragmentLocator | span}\n * and/or an opaque consumer-minted {@link IEmbeddedFragment.fragmentId | fragmentId}.\n * Produced by a {@link FragmentEmbedder} and stored via\n * {@link IFragmentVectorIndex.addFragments}.\n *\n * @remarks\n * Both identity fields are optional **in the type**, but the \"at least one\"\n * requirement is real — a fragment carrying neither is unidentifiable at the read\n * side. It is enforced by {@link embeddedFragmentConverter} (and re-checked by the\n * in-package index implementations) rather than by a conditional-required union\n * (`{ locator; fragmentId? } | { locator?; fragmentId }`), which was considered and\n * declined: the union costs at every construction site and buys nothing at the read\n * site, where each field reads as `… | undefined` either way.\n * @public\n */\nexport interface IEmbeddedFragment {\n /**\n * The region of the record body this fragment was derived from, when one can be\n * stated honestly. Advisory — see {@link IFragmentLocator}; it is NOT a slice that\n * reproduces the fragment text. Omit it for a fragment with no honest body span (a\n * rewriting segmenter), in which case `fragmentId` must be supplied.\n */\n readonly locator?: IFragmentLocator;\n /**\n * An opaque, consumer-minted identity for this fragment, carried verbatim through\n * the index and returned on the corresponding {@link IVectorQueryHit}. The index\n * **never parses it, never filters on it, and never assigns meaning to it** — it is\n * a bytestring, not part of the query path. It exists so a fragment stays\n * identifiable when its text is not re-derivable from the record body.\n *\n * The guarantee is \"we never parse it\", NOT \"we keep it stable\". Because\n * `addFragments` is whole-record-replace, an updated record re-emits its entire\n * fragment set, so **any stability of a fragment id across re-embeds is the\n * consumer's responsibility**, not the index's.\n */\n readonly fragmentId?: string;\n /** The embedding vector for this fragment. */\n readonly vector: Float32Array;\n}\n\n/**\n * The fragment-granular sibling of {@link IVectorIndex}: instead of one vector per\n * record it holds many vectors per record, each tagged with the identity its\n * {@link IEmbeddedFragment} carried, and its `query` returns per-fragment hits\n * carrying that identity back. This is the seam behind sub-document semantic search\n * — the \"discovery\" half of a search-then-read contract, where a hit tells the\n * consumer which record AND which fragment of it to read.\n *\n * @remarks\n * Deliberately NOT `extends IVectorIndex`: an index holding many vectors per record\n * has no well-defined single-vector `add(target, vector)`. It is a parallel contract\n * with three operations — `addFragments`, `remove`, `query` — reusing\n * {@link IVectorQueryHit}, on which both `locator` and `fragmentId` are optional. A\n * fragment hit populates whichever of the two its stored fragment carried; see\n * {@link IVectorQueryHit} for why that is not a discriminator and why fragment-ness\n * is determined by the index queried, not by field presence. Kept distinct from the\n * record-granular index per the consumer contract: memory recall stays\n * record-granular; sub-document knowledge uses a separate fragment index.\n * @public\n */\nexport interface IFragmentVectorIndex {\n /**\n * Add (or replace) all fragments for the scope-qualified `target`. Whole-record\n * semantics: every fragment previously held for `target` is dropped and replaced\n * by `fragments`, so a re-authored document never leaves stale fragments behind.\n * Returns the number of fragments now held for the record.\n */\n addFragments(target: IEdgeTarget, fragments: ReadonlyArray<IEmbeddedFragment>): Promise<Result<number>>;\n\n /**\n * Remove every fragment for the scope-qualified `target`. Returns the removed\n * target. Idempotent — removing a target with no fragments still succeeds.\n */\n remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;\n\n /**\n * Return the `topK` nearest fragments to `vector`, in descending score order,\n * each hit carrying its record `target` plus whichever of `locator` /\n * `fragmentId` the stored fragment was added with. When\n * `maxPerRecord` is supplied, no more than that many fragments of any single\n * record appear in the result — the cap is applied during selection (before the\n * `topK` cut) so one long document cannot crowd out others.\n */\n query(\n vector: Float32Array,\n topK: number,\n maxPerRecord?: number\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;\n\n /**\n * Whether this index holds **any** fragment for the scope-qualified `target`.\n *\n * @remarks\n * The record-granular rationale on {@link IVectorIndex.has} applies verbatim —\n * a repair that trusts `embeddingRef` is not a repair. Note the granularity\n * this deliberately does **not** offer: it answers *\"is this record\n * represented?\"*, not *\"is this particular fragment present?\"*. Fragment\n * writes are whole-record-replace, so a record is either represented by the\n * current fragment set or not represented at all; a per-fragment membership\n * check would imply an incremental write path that does not exist.\n */\n has(target: IEdgeTarget): Promise<Result<boolean>>;\n\n /**\n * The number of **records** with at least one fragment held.\n *\n * @remarks\n * Deliberately **not** named `size`, unlike {@link IVectorIndex.size}. This\n * index is one-to-many, so `size` has two defensible readings and a reader\n * arriving from the record-granular sibling — where `size` counts vectors —\n * would take the wrong one silently. Two explicitly-named counts cost one extra\n * member and cannot be misread.\n */\n readonly recordCount: number;\n\n /**\n * The total number of **fragments** held across all records.\n *\n * @remarks\n * The fan-out, and the number a caller actually watches: fragments-per-record\n * is what makes a fragment reconcile expensive, and neither `recordCount` nor a\n * record-granular count answers it.\n */\n readonly fragmentCount: number;\n\n /**\n * Re-embed every record from `source` and rebuild the fragment index from\n * scratch — the **backfill / reconcile** operation, sibling to\n * {@link IVectorIndex.rebuild}.\n *\n * @remarks\n * On the contract for exactly the reasons its record-granular sibling is, and\n * the fragment lane was worse off: `rebuild` existed only on the bundled\n * in-memory class, and the durable `SqliteVecFragmentIndex` had **no backfill\n * at all**, so a persistent fragment index could not be reconciled by any\n * route — contractual or concrete. Records written while it was unwired, a\n * re-embed after a segmenter change, and reconciliation after a swallowed\n * fragment-embed failure were all unreachable.\n *\n * Semantics are kept observably identical to the record-granular sibling so a\n * caller who has learned one has learned both: a `source.list()` failure is\n * fatal and carries no detail (nothing was attempted, and the existing index is\n * untouched); a genuine rebuild then resets first; `onRecordError` defaults to\n * `'fail'`; and a failure carries whatever the attempt had established on the\n * `detail`.\n *\n * **A rebuild does NOT re-establish the vector dimension on a persistent\n * index, and this is the one place the two shipped implementations genuinely\n * differ.** The in-memory indexes forget their dimension when they reset, so a\n * rebuild with a different-dimension embedder simply re-establishes it. A\n * `vec0`-backed index cannot: its reset is a `DELETE FROM`, the table's\n * declared dimension is part of its schema, and `vec0` has no `ALTER TABLE`.\n * So a rebuild that changes dimension **succeeds in memory and fails on\n * SQLite**, where it needs the same drop-and-re-index the package README\n * prescribes for any schema change (drop the table, or point the index at a\n * fresh `tableName`). Only embedding time is at risk — vectors are derived and\n * the vault records remain authoritative.\n */\n rebuild(\n source: IMemoryRecordSource,\n embed: FragmentEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IFragmentVectorRebuildReport, IFragmentVectorRebuildReport>>;\n}\n\n/**\n * What an {@link IFragmentVectorIndex.rebuild} established, resolved by\n * {@link Kind} — the fragment-granular sibling of {@link IVectorRebuildReport}.\n *\n * @remarks\n * The rule stated on {@link IVectorRebuildReport} applies here verbatim and is\n * not re-opened: **every count is resolved by kind unless there is a stated\n * reason it cannot be**, `excluded` is optional because only the source can know\n * it, and the report is carried on a failure as well as a success.\n *\n * The one member with no record-granular analogue is\n * {@link IFragmentVectorRebuildReport.fragments | fragments} — the fan-out. It is\n * the number that distinguishes this lane: `indexed: 40` says forty records are\n * represented and says nothing about whether that cost forty embedding round\n * trips or four thousand, which is the difference between a reconcile that\n * finishes in a second and one that blocks a request past thirty.\n *\n * A **declined** record is one whose {@link FragmentEmbedder} returned an empty\n * array — the fragment lane's way of saying *intentionally not embedded*. Note\n * this is a different mechanism from a {@link MemoryEmbedder} decline: an empty\n * array still performs a real whole-record-replace (which is what clears any\n * stale fragments), where a record-granular decline skips the index entirely.\n * @public\n */\nexport interface IFragmentVectorRebuildReport {\n /** Records that ended with at least one fragment held, per kind. */\n readonly indexed: ReadonlyMap<Kind, number>;\n /** Fragments held, per kind — the fan-out `indexed` cannot express. */\n readonly fragments: ReadonlyMap<Kind, number>;\n /** Records whose embedder intentionally produced no fragments, per kind. */\n readonly declined: ReadonlyMap<Kind, number>;\n /**\n * Records the source filtered out before the rebuild saw them, per kind.\n * `undefined` means *this source does not report exclusions*; an empty map\n * means *it does, and excluded nothing*.\n */\n readonly excluded?: ReadonlyMap<Kind, number>;\n /** Records that failed, per record, with the error — a fault, never a decline. */\n readonly skipped: ReadonlyArray<ISkippedVectorRecord>;\n}\n\n/**\n * How a vector-index rebuild treats a record it cannot index — whether the\n * **embedding** failed or the subsequent **add** did. Both are governed by this\n * one mode; neither is unconditionally fatal.\n *\n * @remarks\n * Deliberately mirrors the store's own open-time `onRecordError` mode, including\n * its default: `'fail'` preserves the historical all-or-nothing contract exactly,\n * and `'skip'` is opt-in. Defined here rather than imported from the store packlet\n * — the `vector` packlet does not depend on `store`, and the two modes describe\n * different domains that merely happen to share a shape.\n *\n * A **decline** (a {@link MemoryEmbedder} resolving `undefined`) is not an error\n * and is unaffected by this mode: it is always **excluded** from the index and\n * counted on {@link IVectorRebuildReport.declined}, **never** appearing in\n * {@link IVectorRebuildReport.skipped}. The word is worth being careful with here:\n * `skipped` is now a formal field meaning *a fault*, and a decline is the opposite.\n * @public\n */\nexport type VectorRebuildErrorMode = 'skip' | 'fail';\n\n/**\n * A record a rebuild could not index — because the embed failed or because the\n * subsequent add did — retained so a partial rebuild reports what it lost rather\n * than merely how much it kept.\n * @public\n */\nexport interface ISkippedVectorRecord {\n /** The scope-qualified address of the record that could not be indexed. */\n readonly target: IEdgeTarget;\n /** The failure message, from either the embed or the subsequent add. */\n readonly error: string;\n}\n\n/**\n * What a rebuild actually did — the structural answer to \"is this index complete?\".\n *\n * @remarks\n * A bare count cannot distinguish the ways a record can be absent from the index,\n * and that distinction is the entire point: **`declined` was intentional,\n * `excluded` was never offered, `skipped` was a fault, and none of them is the\n * same as \"never attempted\"**. A caller deriving coverage from a count alone\n * cannot tell an embedder outage from a deliberate policy, which is precisely the\n * confusion this type exists to end.\n *\n * **Every count in this report is resolved by kind.** A coverage report exists to\n * answer *\"is my coverage what I intended?\"*, and a bare total cannot:\n * `indexed: 500` reads identically whether the right kinds were indexed or a\n * policy drift silently redirected coverage, and the same is true of every other\n * count here. Totals are derivable by summing; the per-kind breakdown is not\n * derivable from anything else — {@link IVectorQueryHit} carries no `kind`,\n * {@link IVectorIndex.query} answers \"what is near this\" rather than \"what is in\n * here\", and {@link IVectorIndex.size} is a scalar, so the index cannot be\n * interrogated after the fact for any of them. **A new count added to this report\n * is resolved by kind unless there is a stated reason it cannot be.**\n *\n * `indexed` is the count most tempting to leave bare and the most dangerous to,\n * because it is the number a coverage surface actually renders: 500 bookkeeping\n * rows and zero knowledge rows is a healthy-looking number for a catastrophically\n * broken index.\n *\n * **Reading a report that arrived on a failure.** Under\n * {@link VectorRebuildErrorMode | `onRecordError: 'fail'`} the report is handed\n * back on the failure's `detail` — *after* the rollback has already run. It\n * describes the attempt, not the surviving index: `indexed` names what had been\n * established when the rebuild stopped, and the index itself now holds nothing. It\n * is a diagnostic (\"we were 340 knowledge rows in when the embedder died\"), not a\n * coverage statement. Only a report from a **successful** rebuild describes what\n * the index holds.\n * @public\n */\nexport interface IVectorRebuildReport {\n /**\n * Records embedded and added to the index, counted by {@link Kind}.\n *\n * @remarks\n * A count of successful `add` calls, so it lines up with its per-record\n * siblings and the buckets sum back to the listing. It is deliberately **not**\n * read back off {@link IVectorIndex.size} at the end, which no implementation\n * could resolve by kind anyway. The trade that makes: a `source` that lists the\n * same `(scope, id)` twice contributes twice here while the index holds one\n * vector, where a size read would have self-corrected. A source that does that\n * is malformed, and a total that silently disagreed with the per-kind\n * breakdown would be the worse failure.\n */\n readonly indexed: ReadonlyMap<Kind, number>;\n /**\n * Records the embedder deliberately declined (resolved `undefined`), counted by\n * {@link Kind}. The embedder was called and answered — contrast `excluded`,\n * where it never was.\n */\n readonly declined: ReadonlyMap<Kind, number>;\n /**\n * Records the `source` filtered out before the rebuild ever saw them, counted by\n * {@link Kind} — for a store-backed source, the kinds outside\n * {@link IMemoryStore.embedsKind | embedsKind}.\n *\n * **Optional, and the optionality is semantic rather than cosmetic**: it is the\n * one count a rebuild genuinely cannot know for itself, because the decision is\n * made upstream in the source. `undefined` means *this source does not report\n * exclusions* — distinct from an empty map, which means *this source reports\n * them and excluded nothing*. `indexed` and `declined` are knowable by\n * construction (the rebuild either added the vector or the embedder answered)\n * and so are never optional.\n */\n readonly excluded?: ReadonlyMap<Kind, number>;\n /**\n * Records whose embedding or add FAILED and were skipped. Non-empty only under\n * {@link VectorRebuildErrorMode | `onRecordError: 'skip'`} — under `'fail'` the\n * first failure aborts the rebuild, so a `'fail'` report names the casualty in\n * its failure message rather than here.\n *\n * Per-record and carrying the error, so it already implies the per-kind\n * breakdown the counts above spell out; that is the stated reason this one field\n * is not a `ReadonlyMap<Kind, number>`.\n */\n readonly skipped: ReadonlyArray<ISkippedVectorRecord>;\n}\n\n/**\n * Options for a vector-index rebuild.\n * @public\n */\nexport interface IVectorRebuildOptions {\n /**\n * How to treat a record the rebuild cannot index — an embed failure OR an add\n * failure. Defaults to `'fail'` — the historical behavior, unchanged for every\n * existing caller.\n */\n readonly onRecordError?: VectorRebuildErrorMode;\n}\n\n/**\n * Embeds a complete record into a vector for the store's embed-on-write hook.\n * Async and `Result`-returning, since a real embedder does a network call (cloud\n * provider) or in-process model inference. The consumer wires this — the core\n * package never calls an embedding provider directly, staying embedder-agnostic.\n *\n * @remarks\n * Resolving to `undefined` means **\"intentionally not embedded\"** — a deliberate\n * decline, not an error. The record is stored without an embedding reference, no\n * failure is reported, and **the decline itself logs nothing**. This is distinct\n * from a `Failure`, which means the embedder *tried and could not*.\n *\n * \"Logs nothing\" is a statement about the decline, not a promise of silence: a\n * decline on a record that was already embedded also prunes the vector that\n * reference named, and if that prune fails it is a genuine fault and warns like\n * any other. What a decline never does is warn merely for having happened.\n *\n * The distinction is load-bearing wherever the two are treated differently. On the\n * rebuild path a declined record is **excluded** from the index and counted on\n * {@link IVectorRebuildReport.declined}; a failed one is a genuine error and, under\n * `onRecordError: 'skip'`, is reported on {@link IVectorRebuildReport.skipped}. Collapsing \"I chose not to\" into `fail` would\n * make a deliberate policy indistinguishable from an embedder outage in the logs,\n * and would put a routine decision on whatever error path the caller has wired.\n *\n * The embedder receives the whole record, so the usual reason to decline is the\n * record's `kind` — a control or bookkeeping row that no query should ever return.\n *\n * @public\n */\nexport type MemoryEmbedder = (record: IMemoryRecord<unknown>) => Promise<Result<Float32Array | undefined>>;\n\n/**\n * The fragment-granular sibling of {@link MemoryEmbedder}: chunks a record's body\n * and embeds each chunk, returning one {@link IEmbeddedFragment} per chunk. The\n * chunking policy (window size, overlap) lives entirely in the consumer's embedder\n * — the core stays chunking-agnostic, exactly as it stays embedder-agnostic for\n * the record-granular path. Used by the store's fragment-embed-on-write hook.\n * @public\n */\nexport type FragmentEmbedder = (\n record: IMemoryRecord<unknown>\n) => Promise<Result<ReadonlyArray<IEmbeddedFragment>>>;\n\n/**\n * A record paired with its scope-qualified {@link IEdgeTarget | address}, as\n * yielded by {@link IMemoryRecordSource.list}. The address is required because\n * {@link InMemoryCosineIndex.rebuild} keys each re-embedded entry on the\n * scope-qualified target, not a bare {@link MemoryId} — two records that share a\n * filename stem across scopes must not collide when the whole vault is re-indexed.\n * @public\n */\nexport interface IScopedMemoryRecord {\n /** The record's scope-qualified `(scope, id)` address. */\n readonly target: IEdgeTarget;\n /** The record itself, passed to the embedder. */\n readonly record: IMemoryRecord<unknown>;\n}\n\n/**\n * What a {@link IMemoryRecordSource.list} call yields: the records the rebuild\n * should embed, plus — when the source can say — what it filtered out on the way.\n *\n * @remarks\n * The exclusion count originates here because **this is the layer where the\n * decision is made**. A rebuild never sees an excluded record, so it cannot count\n * one; a report assembled without this would silently undercount coverage, and\n * undercount in the direction of looking healthier.\n *\n * A store accessor answering \"how many are excluded right now\" was considered and\n * declined: it answers a *different question* than the report does — \"excluded\n * right now\" versus \"excluded in this reconcile\" — and the two legitimately differ\n * whenever records are written between reconciles. Two correct-and-unequal numbers\n * are worse than one absent number: they invite treating a real difference as a\n * bug, or picking whichever supports the conclusion already held.\n * @public\n */\nexport interface IMemoryRecordListing {\n /** Every record the rebuild should embed, each paired with its scoped address. */\n readonly records: ReadonlyArray<IScopedMemoryRecord>;\n /**\n * Records this source filtered out, counted by {@link Kind}. Omit it entirely if\n * the source does not track exclusions — that reads as *\"cannot say\"* on\n * {@link IVectorRebuildReport.excluded}, which is distinct from an empty map\n * (*\"nothing was excluded\"*).\n */\n readonly excluded?: ReadonlyMap<Kind, number>;\n}\n\n/**\n * The minimal record-source surface {@link InMemoryCosineIndex.rebuild} reads to\n * re-embed an entire vault. Each entry carries the record's scope-qualified\n * address (see {@link IScopedMemoryRecord}) so the rebuild keys the vector index\n * exactly as the incremental embed-on-write path does. A consumer backs this with\n * the store's scoped index — the vector packlet does not import the store packlet\n * (which depends on the vector packlet for {@link IVectorIndex}, so the reverse\n * import would be a cycle).\n * @public\n */\nexport interface IMemoryRecordSource {\n /**\n * List every record the rebuild should embed, each paired with its scoped\n * address, plus the exclusions this source applied if it tracks them. See\n * {@link IMemoryRecordListing}.\n */\n list(): Promise<Result<IMemoryRecordListing>>;\n}\n"]}