@equationalapplications/core-llm-wiki 7.8.0 → 7.9.1

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.
@@ -1,5 +1,4 @@
1
1
  import { OkfSource, OkfVerifiedEntry, OkfSourceUsageWindow } from '@equationalapplications/core-okf';
2
- import { SearchResult } from 'minisearch';
3
2
 
4
3
  /**
5
4
  * Platform-agnostic SQLite driver interface.
@@ -1923,6 +1922,108 @@ declare class MetadataRepository extends BaseRepository {
1923
1922
  mergeManifestUpdates(entityId: string, updates: OntologyUpdates, tx: SQLiteAdapter): Promise<OntologyManifest>;
1924
1923
  }
1925
1924
 
1925
+ /**
1926
+ * One document that both keyword-index strategies must understand. Both
1927
+ * implementations expect `tags` to be a single string with the JSON array's
1928
+ * tokens space-joined (see `toIndexDoc` in utils/indexDoc.ts). The exact token
1929
+ * shape is strategy-private — callers do not see it.
1930
+ */
1931
+ interface IndexDocument {
1932
+ id: string;
1933
+ entity_id: string;
1934
+ title: string;
1935
+ body: string;
1936
+ tags: string;
1937
+ }
1938
+ interface IndexSearchOptions {
1939
+ /** Empty array means "no entities match"; strategy returns []. */
1940
+ entityIds: string[];
1941
+ /** Upper bound on results returned. Strategy MUST return ≤ `limit`. */
1942
+ limit: number;
1943
+ /** Optional pre-filter cap used by hybrid retrieval. */
1944
+ preFilterLimit?: number;
1945
+ }
1946
+ interface IndexSearchResult {
1947
+ id: string;
1948
+ /**
1949
+ * Strategy-native relevance: finite, non-negative, higher is better,
1950
+ * comparable only within one result set. Not normalized —
1951
+ * `SearchService.getKeywordScores` divides by `max(1, top)` for the hybrid
1952
+ * blend, and `searchKeyword` passes scores through unscaled. SearchService
1953
+ * drops any result whose score is not finite.
1954
+ */
1955
+ score: number;
1956
+ /**
1957
+ * The entity id this search result belongs to. Populated from
1958
+ * `IndexDocument.entity_id` by the strategy on every result.
1959
+ */
1960
+ entity_id: string;
1961
+ }
1962
+ /**
1963
+ * Keyword-index abstraction. The strategy owns its own backing store
1964
+ * (in-RAM MiniSearch vs on-disk FTS5) and is responsible for keeping it
1965
+ * consistent with the rows it has been told to write. The strategy also owns
1966
+ * the per-entity id bookkeeping — `SearchService` never sees the ids, only
1967
+ * an indexed-or-not probe.
1968
+ *
1969
+ * Concurrency: writes are called from serialized turns on
1970
+ * `SearchService.syncChain`, but searches are not, so each write method MUST
1971
+ * apply as one step — a concurrent `search()` sees the index wholly before or
1972
+ * wholly after it, never between its removal and its insertion. MiniSearch
1973
+ * gets this by never awaiting inside a write; FTS5 by one transaction per
1974
+ * write. Strategies MUST NOT spawn background work.
1975
+ */
1976
+ interface IndexStrategy {
1977
+ /**
1978
+ * Replace documents under `entityId`: remove the given `ids` only if they
1979
+ * are currently tracked under `entityId` (ids under other entities and ids
1980
+ * the strategy never indexed are silently ignored), then upsert
1981
+ * `documents` by id. The strategy owns its id-tracking structure; callers
1982
+ * do not see it. Used for incremental sync.
1983
+ *
1984
+ * Argument order is `(entityId, ids, documents)` so the FTS5 SQL
1985
+ * `DELETE … WHERE entity_id = ? AND id IN (…)` matches the call shape.
1986
+ */
1987
+ replace(entityId: string, ids: readonly string[], documents: readonly IndexDocument[]): Promise<void>;
1988
+ /**
1989
+ * Drop every document currently tracked under `entityId`, then add
1990
+ * `documents` (all of which belong to `entityId`). The entity stays
1991
+ * registered as indexed, even when `documents` is empty — `hasIndexedEntity`
1992
+ * continues to return `true` so the next incremental sync can take the
1993
+ * fast path. Used for per-entity rebuilds.
1994
+ */
1995
+ replaceEntity(entityId: string, documents: readonly IndexDocument[]): Promise<void>;
1996
+ /**
1997
+ * Empty the entire index, then add `documents`. After this call, no entity
1998
+ * is registered as indexed (`hasIndexedEntity` returns `false` for every
1999
+ * entity). Used for global rebuilds and clears.
2000
+ */
2001
+ replaceAll(documents: readonly IndexDocument[]): Promise<void>;
2002
+ /**
2003
+ * Search the index, best match first. If `preFilterLimit` is set, the
2004
+ * strategy MAY return up to `preFilterLimit` candidates before the `limit`
2005
+ * cap is applied (hybrid retrieval uses this to over-fetch candidates for
2006
+ * downstream vector blending). Score semantics are in `IndexSearchResult`.
2007
+ */
2008
+ search(query: string, options: IndexSearchOptions): Promise<IndexSearchResult[]>;
2009
+ /**
2010
+ * Strategy-internal cleanup. For MiniSearch, this is the explicit vacuum
2011
+ * that `syncEntries` invokes after a discard-bearing turn (#64). FTS5 has
2012
+ * no equivalent; the default implementation is a no-op.
2013
+ */
2014
+ vacuum?(): Promise<void>;
2015
+ /**
2016
+ * `true` when `entityId` has been indexed at least once under the current
2017
+ * strategy state; `false` otherwise. `SearchService.syncEntries` uses this
2018
+ * to choose between the empty-ids no-op fast path and the full-rebuild
2019
+ * fallback. Implementations MUST return `false` after `replaceAll([])` and
2020
+ * MUST return `true` after a `replace`/`replaceEntity` that wrote at least
2021
+ * one document (including `replace(entityId, [], [])` for tracking alone).
2022
+ * Returning `undefined` means "unknown — take the rebuild path".
2023
+ */
2024
+ hasIndexedEntity?(entityId: string): boolean | undefined;
2025
+ }
2026
+
1926
2027
  interface ScoredRow {
1927
2028
  id: string;
1928
2029
  entity_id: string;
@@ -1961,8 +2062,7 @@ declare class SearchService {
1961
2062
  * entities while still maintaining a bounded memory footprint.
1962
2063
  */
1963
2064
  private static readonly MAX_VECTOR_CACHE_FACTS_PER_ENTITY;
1964
- private miniSearch;
1965
- private miniSearchEntryIdsByEntity;
2065
+ private indexStrategy;
1966
2066
  private vectorCache;
1967
2067
  /**
1968
2068
  * Serializes rebuilds. `rebuildIndex` awaits a repository read between
@@ -1988,19 +2088,12 @@ declare class SearchService {
1988
2088
  * its flag must outlive the turn (#233 review).
1989
2089
  */
1990
2090
  private staleEpochs;
1991
- constructor(entryRepo: EntryRepository);
1992
- /**
1993
- * A fresh index with the production options. clearAll() swaps one in because
1994
- * MiniSearch.removeAll() empties the index but leaves dirtCount (and its
1995
- * vacuum bookkeeping) at its old value, which would trip syncEntries'
1996
- * conditional vacuum early after a clear.
1997
- */
1998
- private createMiniSearch;
2091
+ constructor(entryRepo: EntryRepository, indexStrategy: IndexStrategy);
1999
2092
  /**
2000
2093
  * Rebuilds the search index and clears the vector cache for a given entity.
2001
2094
  * A direct replacement for manually syncing state after a DB transaction.
2002
2095
  *
2003
- * Rebuilds are serialized per instance and never reject: the MiniSearch index
2096
+ * Rebuilds are serialized per instance and never reject: the keyword index
2004
2097
  * is a rebuildable cache over SQLite, so degraded keyword search is the
2005
2098
  * correct failure mode and killing the host process is not.
2006
2099
  */
@@ -2023,6 +2116,13 @@ declare class SearchService {
2023
2116
  * cannot index itself, such as upsertGraph inside a host transaction.
2024
2117
  */
2025
2118
  markStale(entityId: string): void;
2119
+ /**
2120
+ * Runs syncEntries(id, []) for each entity marked stale when this is called,
2121
+ * so only those entities are rebuilt. Each rebuild still takes its own turn
2122
+ * on the sync chain, and like syncEntries this never rejects. With nothing
2123
+ * stale it resolves at once, without waiting on work already on the chain.
2124
+ */
2125
+ syncStale(): Promise<void>;
2026
2126
  /**
2027
2127
  * Clears the parsed vector cache. Useful for mid-loop flush guarantees
2028
2128
  * or memory pressure evictions.
@@ -2031,30 +2131,36 @@ declare class SearchService {
2031
2131
  /**
2032
2132
  * Fully resets the search service.
2033
2133
  */
2034
- clearAll(): void;
2134
+ clearAll(): Promise<void>;
2035
2135
  /**
2036
- * Executes a keyword search against the active MiniSearch index.
2136
+ * Executes a keyword search against the active index strategy.
2037
2137
  */
2038
- searchKeyword(query: string, entityIds: string[], limit: number): SearchResult[];
2138
+ searchKeyword(query: string, entityIds: string[], limit: number): Promise<IndexSearchResult[]>;
2039
2139
  /**
2040
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
2140
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
2041
2141
  */
2042
- getMiniSearchScores(query: string, entityIds: string[], preFilterLimit?: number): Map<string, number>;
2142
+ getKeywordScores(query: string, entityIds: string[], preFilterLimit?: number): Promise<Map<string, number>>;
2043
2143
  /**
2044
2144
  * Score candidate rows using in-process JS cosine similarity.
2045
2145
  * Applies hybrid blending (if weight set) and tie-break sorting before returning.
2046
2146
  */
2047
2147
  rankSemantic(args: RankSemanticArgs): Promise<ScoredRow[]>;
2148
+ /**
2149
+ * Rebuild a single entity's index from scratch. Reads every row for
2150
+ * `entityId` from the repository (already filtered by `deleted_at IS NULL`)
2151
+ * and hands the lot to `indexStrategy.replaceEntity`, which owns the
2152
+ * drop-then-add bookkeeping so callers don't need to know the strategy's
2153
+ * id-tracking shape.
2154
+ */
2048
2155
  private rebuildIndex;
2049
- private normalizeMiniSearchRow;
2050
2156
  private _tieBreakSort;
2051
2157
  private _compareScoredRows;
2052
2158
  /**
2053
- * MiniSearch breaks equal-score ties by internal insertion order, which
2054
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
2055
- * caller truncates these results to a limit, so which ids survive a tie at
2056
- * the boundary must not depend on write history. Re-sort exact score ties
2057
- * by id — the same final tie-break _compareScoredRows applies.
2159
+ * The keyword index breaks equal-score ties by internal insertion order,
2160
+ * which syncEntries changes: a discarded-and-re-added id moves to the end.
2161
+ * Every caller truncates these results to a limit, so which ids survive a
2162
+ * tie at the boundary must not depend on write history. Re-sort exact
2163
+ * score ties by id — the same final tie-break _compareScoredRows applies.
2058
2164
  */
2059
2165
  private _compareSearchResults;
2060
2166
  }
@@ -3486,6 +3592,28 @@ declare class WikiMemory {
3486
3592
  getEntityStatus(entityId: string): EntityStatus;
3487
3593
  subscribeEntityStatus(entityId: string, callback: (status: EntityStatus) => void): () => void;
3488
3594
  clearVectorCache(): void;
3595
+ /**
3596
+ * Brings the keyword search index up to date with SQLite after a host
3597
+ * transaction that used `upsertGraph` has committed. Core never sees that
3598
+ * commit, so without this the nodes stay out of keyword, hybrid and
3599
+ * fallback retrieval until the entity's next core write.
3600
+ *
3601
+ * With `entityId`, rebuilds that entity if it is stale or has never been
3602
+ * indexed; otherwise it is a no-op that still waits for rebuilds already
3603
+ * queued. Without an argument, rebuilds every entity currently marked
3604
+ * stale; when none is, it resolves immediately and does not wait for rebuilds
3605
+ * already queued.
3606
+ *
3607
+ * Call only after the host's transaction commits: called inside it, the
3608
+ * rebuild may index rows that later roll back, or miss them and clear the
3609
+ * stale flag anyway. Serialized with core's internal index syncs and takes
3610
+ * no maintenance lock. Never rejects for a valid `entityId` — on failure the
3611
+ * entity stays stale and the next call retries it. Does not compute
3612
+ * embeddings; that remains the maintenance sweep's job.
3613
+ *
3614
+ * @throws {TypeError} when `entityId` is given but is not a non-empty string.
3615
+ */
3616
+ syncSearchIndex(entityId?: string): Promise<void>;
3489
3617
  exportDump(entityIds?: string[]): Promise<MemoryDump>;
3490
3618
  /** Entity summary prose persisted from an OKF profile ≥ 1 import; null when none stored. */
3491
3619
  getEntitySummary(entityId: string): Promise<string | null>;
@@ -3548,9 +3676,11 @@ declare class WikiMemory {
3548
3676
  * MUST be called from inside an open `db.withTransactionAsync` callback —
3549
3677
  * the third argument is the caller's `tx`. The method does not open a
3550
3678
  * nested transaction, does not acquire any lock, and does not perform
3551
- * post-commit work (search sync, embedding, cache eviction). Hosts that
3552
- * want embeddings synced should drive the existing maintenance sweep
3553
- * (`runLibrarian` / `runHeal` / `runOntologyBackfill` / `runPrune`,
3679
+ * post-commit work (search sync, embedding, cache eviction). After the
3680
+ * transaction commits, call `syncSearchIndex(entityId)` — or
3681
+ * `syncSearchIndex()` after writing several entities in one transaction —
3682
+ * to make the new nodes keyword-searchable. Embeddings still come from the maintenance
3683
+ * sweep (`runLibrarian` / `runHeal` / `runOntologyBackfill` / `runPrune`,
3554
3684
  * scoped via `listEntityIds`).
3555
3685
  *
3556
3686
  * Contract (full pre-flight validation lives inside
@@ -1,5 +1,4 @@
1
1
  import { OkfSource, OkfVerifiedEntry, OkfSourceUsageWindow } from '@equationalapplications/core-okf';
2
- import { SearchResult } from 'minisearch';
3
2
 
4
3
  /**
5
4
  * Platform-agnostic SQLite driver interface.
@@ -1923,6 +1922,108 @@ declare class MetadataRepository extends BaseRepository {
1923
1922
  mergeManifestUpdates(entityId: string, updates: OntologyUpdates, tx: SQLiteAdapter): Promise<OntologyManifest>;
1924
1923
  }
1925
1924
 
1925
+ /**
1926
+ * One document that both keyword-index strategies must understand. Both
1927
+ * implementations expect `tags` to be a single string with the JSON array's
1928
+ * tokens space-joined (see `toIndexDoc` in utils/indexDoc.ts). The exact token
1929
+ * shape is strategy-private — callers do not see it.
1930
+ */
1931
+ interface IndexDocument {
1932
+ id: string;
1933
+ entity_id: string;
1934
+ title: string;
1935
+ body: string;
1936
+ tags: string;
1937
+ }
1938
+ interface IndexSearchOptions {
1939
+ /** Empty array means "no entities match"; strategy returns []. */
1940
+ entityIds: string[];
1941
+ /** Upper bound on results returned. Strategy MUST return ≤ `limit`. */
1942
+ limit: number;
1943
+ /** Optional pre-filter cap used by hybrid retrieval. */
1944
+ preFilterLimit?: number;
1945
+ }
1946
+ interface IndexSearchResult {
1947
+ id: string;
1948
+ /**
1949
+ * Strategy-native relevance: finite, non-negative, higher is better,
1950
+ * comparable only within one result set. Not normalized —
1951
+ * `SearchService.getKeywordScores` divides by `max(1, top)` for the hybrid
1952
+ * blend, and `searchKeyword` passes scores through unscaled. SearchService
1953
+ * drops any result whose score is not finite.
1954
+ */
1955
+ score: number;
1956
+ /**
1957
+ * The entity id this search result belongs to. Populated from
1958
+ * `IndexDocument.entity_id` by the strategy on every result.
1959
+ */
1960
+ entity_id: string;
1961
+ }
1962
+ /**
1963
+ * Keyword-index abstraction. The strategy owns its own backing store
1964
+ * (in-RAM MiniSearch vs on-disk FTS5) and is responsible for keeping it
1965
+ * consistent with the rows it has been told to write. The strategy also owns
1966
+ * the per-entity id bookkeeping — `SearchService` never sees the ids, only
1967
+ * an indexed-or-not probe.
1968
+ *
1969
+ * Concurrency: writes are called from serialized turns on
1970
+ * `SearchService.syncChain`, but searches are not, so each write method MUST
1971
+ * apply as one step — a concurrent `search()` sees the index wholly before or
1972
+ * wholly after it, never between its removal and its insertion. MiniSearch
1973
+ * gets this by never awaiting inside a write; FTS5 by one transaction per
1974
+ * write. Strategies MUST NOT spawn background work.
1975
+ */
1976
+ interface IndexStrategy {
1977
+ /**
1978
+ * Replace documents under `entityId`: remove the given `ids` only if they
1979
+ * are currently tracked under `entityId` (ids under other entities and ids
1980
+ * the strategy never indexed are silently ignored), then upsert
1981
+ * `documents` by id. The strategy owns its id-tracking structure; callers
1982
+ * do not see it. Used for incremental sync.
1983
+ *
1984
+ * Argument order is `(entityId, ids, documents)` so the FTS5 SQL
1985
+ * `DELETE … WHERE entity_id = ? AND id IN (…)` matches the call shape.
1986
+ */
1987
+ replace(entityId: string, ids: readonly string[], documents: readonly IndexDocument[]): Promise<void>;
1988
+ /**
1989
+ * Drop every document currently tracked under `entityId`, then add
1990
+ * `documents` (all of which belong to `entityId`). The entity stays
1991
+ * registered as indexed, even when `documents` is empty — `hasIndexedEntity`
1992
+ * continues to return `true` so the next incremental sync can take the
1993
+ * fast path. Used for per-entity rebuilds.
1994
+ */
1995
+ replaceEntity(entityId: string, documents: readonly IndexDocument[]): Promise<void>;
1996
+ /**
1997
+ * Empty the entire index, then add `documents`. After this call, no entity
1998
+ * is registered as indexed (`hasIndexedEntity` returns `false` for every
1999
+ * entity). Used for global rebuilds and clears.
2000
+ */
2001
+ replaceAll(documents: readonly IndexDocument[]): Promise<void>;
2002
+ /**
2003
+ * Search the index, best match first. If `preFilterLimit` is set, the
2004
+ * strategy MAY return up to `preFilterLimit` candidates before the `limit`
2005
+ * cap is applied (hybrid retrieval uses this to over-fetch candidates for
2006
+ * downstream vector blending). Score semantics are in `IndexSearchResult`.
2007
+ */
2008
+ search(query: string, options: IndexSearchOptions): Promise<IndexSearchResult[]>;
2009
+ /**
2010
+ * Strategy-internal cleanup. For MiniSearch, this is the explicit vacuum
2011
+ * that `syncEntries` invokes after a discard-bearing turn (#64). FTS5 has
2012
+ * no equivalent; the default implementation is a no-op.
2013
+ */
2014
+ vacuum?(): Promise<void>;
2015
+ /**
2016
+ * `true` when `entityId` has been indexed at least once under the current
2017
+ * strategy state; `false` otherwise. `SearchService.syncEntries` uses this
2018
+ * to choose between the empty-ids no-op fast path and the full-rebuild
2019
+ * fallback. Implementations MUST return `false` after `replaceAll([])` and
2020
+ * MUST return `true` after a `replace`/`replaceEntity` that wrote at least
2021
+ * one document (including `replace(entityId, [], [])` for tracking alone).
2022
+ * Returning `undefined` means "unknown — take the rebuild path".
2023
+ */
2024
+ hasIndexedEntity?(entityId: string): boolean | undefined;
2025
+ }
2026
+
1926
2027
  interface ScoredRow {
1927
2028
  id: string;
1928
2029
  entity_id: string;
@@ -1961,8 +2062,7 @@ declare class SearchService {
1961
2062
  * entities while still maintaining a bounded memory footprint.
1962
2063
  */
1963
2064
  private static readonly MAX_VECTOR_CACHE_FACTS_PER_ENTITY;
1964
- private miniSearch;
1965
- private miniSearchEntryIdsByEntity;
2065
+ private indexStrategy;
1966
2066
  private vectorCache;
1967
2067
  /**
1968
2068
  * Serializes rebuilds. `rebuildIndex` awaits a repository read between
@@ -1988,19 +2088,12 @@ declare class SearchService {
1988
2088
  * its flag must outlive the turn (#233 review).
1989
2089
  */
1990
2090
  private staleEpochs;
1991
- constructor(entryRepo: EntryRepository);
1992
- /**
1993
- * A fresh index with the production options. clearAll() swaps one in because
1994
- * MiniSearch.removeAll() empties the index but leaves dirtCount (and its
1995
- * vacuum bookkeeping) at its old value, which would trip syncEntries'
1996
- * conditional vacuum early after a clear.
1997
- */
1998
- private createMiniSearch;
2091
+ constructor(entryRepo: EntryRepository, indexStrategy: IndexStrategy);
1999
2092
  /**
2000
2093
  * Rebuilds the search index and clears the vector cache for a given entity.
2001
2094
  * A direct replacement for manually syncing state after a DB transaction.
2002
2095
  *
2003
- * Rebuilds are serialized per instance and never reject: the MiniSearch index
2096
+ * Rebuilds are serialized per instance and never reject: the keyword index
2004
2097
  * is a rebuildable cache over SQLite, so degraded keyword search is the
2005
2098
  * correct failure mode and killing the host process is not.
2006
2099
  */
@@ -2023,6 +2116,13 @@ declare class SearchService {
2023
2116
  * cannot index itself, such as upsertGraph inside a host transaction.
2024
2117
  */
2025
2118
  markStale(entityId: string): void;
2119
+ /**
2120
+ * Runs syncEntries(id, []) for each entity marked stale when this is called,
2121
+ * so only those entities are rebuilt. Each rebuild still takes its own turn
2122
+ * on the sync chain, and like syncEntries this never rejects. With nothing
2123
+ * stale it resolves at once, without waiting on work already on the chain.
2124
+ */
2125
+ syncStale(): Promise<void>;
2026
2126
  /**
2027
2127
  * Clears the parsed vector cache. Useful for mid-loop flush guarantees
2028
2128
  * or memory pressure evictions.
@@ -2031,30 +2131,36 @@ declare class SearchService {
2031
2131
  /**
2032
2132
  * Fully resets the search service.
2033
2133
  */
2034
- clearAll(): void;
2134
+ clearAll(): Promise<void>;
2035
2135
  /**
2036
- * Executes a keyword search against the active MiniSearch index.
2136
+ * Executes a keyword search against the active index strategy.
2037
2137
  */
2038
- searchKeyword(query: string, entityIds: string[], limit: number): SearchResult[];
2138
+ searchKeyword(query: string, entityIds: string[], limit: number): Promise<IndexSearchResult[]>;
2039
2139
  /**
2040
- * Pre-fetches MiniSearch scores for candidate hydration, used during hybrid weighting.
2140
+ * Pre-fetches keyword scores for candidate hydration, used during hybrid weighting.
2041
2141
  */
2042
- getMiniSearchScores(query: string, entityIds: string[], preFilterLimit?: number): Map<string, number>;
2142
+ getKeywordScores(query: string, entityIds: string[], preFilterLimit?: number): Promise<Map<string, number>>;
2043
2143
  /**
2044
2144
  * Score candidate rows using in-process JS cosine similarity.
2045
2145
  * Applies hybrid blending (if weight set) and tie-break sorting before returning.
2046
2146
  */
2047
2147
  rankSemantic(args: RankSemanticArgs): Promise<ScoredRow[]>;
2148
+ /**
2149
+ * Rebuild a single entity's index from scratch. Reads every row for
2150
+ * `entityId` from the repository (already filtered by `deleted_at IS NULL`)
2151
+ * and hands the lot to `indexStrategy.replaceEntity`, which owns the
2152
+ * drop-then-add bookkeeping so callers don't need to know the strategy's
2153
+ * id-tracking shape.
2154
+ */
2048
2155
  private rebuildIndex;
2049
- private normalizeMiniSearchRow;
2050
2156
  private _tieBreakSort;
2051
2157
  private _compareScoredRows;
2052
2158
  /**
2053
- * MiniSearch breaks equal-score ties by internal insertion order, which
2054
- * syncEntries changes: a discarded-and-re-added id moves to the end. Every
2055
- * caller truncates these results to a limit, so which ids survive a tie at
2056
- * the boundary must not depend on write history. Re-sort exact score ties
2057
- * by id — the same final tie-break _compareScoredRows applies.
2159
+ * The keyword index breaks equal-score ties by internal insertion order,
2160
+ * which syncEntries changes: a discarded-and-re-added id moves to the end.
2161
+ * Every caller truncates these results to a limit, so which ids survive a
2162
+ * tie at the boundary must not depend on write history. Re-sort exact
2163
+ * score ties by id — the same final tie-break _compareScoredRows applies.
2058
2164
  */
2059
2165
  private _compareSearchResults;
2060
2166
  }
@@ -3486,6 +3592,28 @@ declare class WikiMemory {
3486
3592
  getEntityStatus(entityId: string): EntityStatus;
3487
3593
  subscribeEntityStatus(entityId: string, callback: (status: EntityStatus) => void): () => void;
3488
3594
  clearVectorCache(): void;
3595
+ /**
3596
+ * Brings the keyword search index up to date with SQLite after a host
3597
+ * transaction that used `upsertGraph` has committed. Core never sees that
3598
+ * commit, so without this the nodes stay out of keyword, hybrid and
3599
+ * fallback retrieval until the entity's next core write.
3600
+ *
3601
+ * With `entityId`, rebuilds that entity if it is stale or has never been
3602
+ * indexed; otherwise it is a no-op that still waits for rebuilds already
3603
+ * queued. Without an argument, rebuilds every entity currently marked
3604
+ * stale; when none is, it resolves immediately and does not wait for rebuilds
3605
+ * already queued.
3606
+ *
3607
+ * Call only after the host's transaction commits: called inside it, the
3608
+ * rebuild may index rows that later roll back, or miss them and clear the
3609
+ * stale flag anyway. Serialized with core's internal index syncs and takes
3610
+ * no maintenance lock. Never rejects for a valid `entityId` — on failure the
3611
+ * entity stays stale and the next call retries it. Does not compute
3612
+ * embeddings; that remains the maintenance sweep's job.
3613
+ *
3614
+ * @throws {TypeError} when `entityId` is given but is not a non-empty string.
3615
+ */
3616
+ syncSearchIndex(entityId?: string): Promise<void>;
3489
3617
  exportDump(entityIds?: string[]): Promise<MemoryDump>;
3490
3618
  /** Entity summary prose persisted from an OKF profile ≥ 1 import; null when none stored. */
3491
3619
  getEntitySummary(entityId: string): Promise<string | null>;
@@ -3548,9 +3676,11 @@ declare class WikiMemory {
3548
3676
  * MUST be called from inside an open `db.withTransactionAsync` callback —
3549
3677
  * the third argument is the caller's `tx`. The method does not open a
3550
3678
  * nested transaction, does not acquire any lock, and does not perform
3551
- * post-commit work (search sync, embedding, cache eviction). Hosts that
3552
- * want embeddings synced should drive the existing maintenance sweep
3553
- * (`runLibrarian` / `runHeal` / `runOntologyBackfill` / `runPrune`,
3679
+ * post-commit work (search sync, embedding, cache eviction). After the
3680
+ * transaction commits, call `syncSearchIndex(entityId)` — or
3681
+ * `syncSearchIndex()` after writing several entities in one transaction —
3682
+ * to make the new nodes keyword-searchable. Embeddings still come from the maintenance
3683
+ * sweep (`runLibrarian` / `runHeal` / `runOntologyBackfill` / `runPrune`,
3554
3684
  * scoped via `listEntityIds`).
3555
3685
  *
3556
3686
  * Contract (full pre-flight validation lives inside
@@ -1,3 +1,2 @@
1
- export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-CWZaXKLj.mjs';
1
+ export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Bf6etdUy.mjs';
2
2
  import '@equationalapplications/core-okf';
3
- import 'minisearch';
package/dist/testing.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-CWZaXKLj.js';
1
+ export { aB as EmbeddingService, aC as ImportExportService, aD as IngestionService, aE as JobManager, aE as JobManagerType, aF as MaintenanceService, aG as RetrievalService, aH as SearchService, aH as SearchServiceType, ar as WikiMemoryTestAccess, aI as WriteService } from './testing-Bf6etdUy.js';
2
2
  import '@equationalapplications/core-okf';
3
- import 'minisearch';