@equationalapplications/core-llm-wiki 7.12.0 → 7.13.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.
package/dist/testing.js CHANGED
@@ -38,6 +38,8 @@ var SQLITE_RESULT_CODE_NAMES = {
38
38
  // prefix and we normalize it back to the symbolic name so callers can branch on
39
39
  // the same string the rest of the ecosystem uses.
40
40
  2067: "SQLITE_CONSTRAINT_UNIQUE",
41
+ // 267 = SQLITE_CORRUPT_VTAB — a corrupt FTS5 shadow table (ftsCorruptionClass).
42
+ 267: "SQLITE_CORRUPT_VTAB",
41
43
  100: "SQLITE_ROW",
42
44
  101: "SQLITE_DONE"
43
45
  };
@@ -1407,7 +1409,7 @@ var ImportExportService = class {
1407
1409
  if (merge) {
1408
1410
  await this.searchService.syncEntries(entityId, [...upsertedFactIds]);
1409
1411
  } else {
1410
- await this.searchService.sync(entityId);
1412
+ await this.searchService.sync(entityId, { ids: [.../* @__PURE__ */ new Set([...upsertedFactIds, ...softDeletedFactIds])] });
1411
1413
  }
1412
1414
  for (const fact of bundle.facts) {
1413
1415
  if (!fact.deleted_at && upsertedFactIds.has(fact.id) && !factsWithPreservedBlob.has(fact.id)) {
@@ -2340,7 +2342,8 @@ var IngestionService = class {
2340
2342
  *
2341
2343
  * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
2342
2344
  * (manifest-valid edges persisted), superseded (prior facts soft-deleted
2343
- * plus prior source-ref edges hard-deleted).
2345
+ * plus prior source-ref edges hard-deleted), and supersededFactIds (the
2346
+ * ids of those soft-deleted facts, for the caller's post-commit index sync).
2344
2347
  *
2345
2348
  * Node-ownership pre-flight (spec §3, REQ-GRAPH-01): the first statement
2346
2349
  * is `assertGraphNodeOwnership(entityId, params.nodes, tx)`, which runs
@@ -2468,7 +2471,8 @@ var IngestionService = class {
2468
2471
  return {
2469
2472
  nodesWritten: wikiFacts.length,
2470
2473
  edgesWritten: edgesInsertedCount,
2471
- superseded: deletedFactIds.length + deletedEdgeCount
2474
+ superseded: deletedFactIds.length + deletedEdgeCount,
2475
+ supersededFactIds: deletedFactIds
2472
2476
  };
2473
2477
  }
2474
2478
  /**
@@ -2882,6 +2886,17 @@ async function createTemporalIndexesIfColumnsExist(db, prefix) {
2882
2886
  }
2883
2887
  }
2884
2888
 
2889
+ // src/services/search/fts5Sql.ts
2890
+ var FTS5_STATE_KEY = "fts5_index_state";
2891
+ var FTS5_REBUILD_WM_KEY = "fts5_rebuild_wm";
2892
+ var FTS5_REBUILD_SNAPSHOT_KEY = "fts5_rebuild_snapshot";
2893
+ var FTS5_SWEEP_CURSOR_KEY = "fts5_orphan_sweep_cursor";
2894
+ function fts5PendingIndexesDdl(p) {
2895
+ return `
2896
+ CREATE INDEX IF NOT EXISTS ${p}fts_pending_id ON ${p}fts_pending(id);
2897
+ CREATE INDEX IF NOT EXISTS ${p}fts_pending_entity ON ${p}fts_pending(entity_id);`;
2898
+ }
2899
+
2885
2900
  // src/db/migrations.ts
2886
2901
  var MIGRATIONS = [
2887
2902
  {
@@ -3162,6 +3177,17 @@ var MIGRATIONS = [
3162
3177
  run: async (db, prefix) => {
3163
3178
  await createLegacySourceTypeIndex(db, prefix);
3164
3179
  }
3180
+ },
3181
+ {
3182
+ version: 15,
3183
+ description: "fts_pending(id) and fts_pending(entity_id) indexes for the ids-first and entity-targeted drains",
3184
+ run: async (db, prefix) => {
3185
+ const row = await db.getFirstAsync(
3186
+ `SELECT count(*) AS n FROM sqlite_master WHERE type = 'table' AND name = ?`,
3187
+ [`${prefix}fts_pending`]
3188
+ );
3189
+ if (row?.n) await db.execAsync(fts5PendingIndexesDdl(prefix));
3190
+ }
3165
3191
  }
3166
3192
  ];
3167
3193
  async function createLegacySourceTypeIndex(db, prefix) {
@@ -3186,6 +3212,21 @@ async function resetNormalizeWatermarks(meta) {
3186
3212
  await meta.deleteMeta(SRI_WM_KEY);
3187
3213
  }
3188
3214
 
3215
+ // src/services/search/fts5Progress.ts
3216
+ async function writeRebuildStart(tx, prefix, meta) {
3217
+ const e = await tx.getFirstAsync(`SELECT coalesce(max(rowid), 0) AS hi FROM ${prefix}entries`);
3218
+ const m = await tx.getFirstAsync(`SELECT coalesce(max(fts_rowid), 0) AS hi FROM ${prefix}fts_map`);
3219
+ await meta.setMeta(FTS5_REBUILD_WM_KEY, JSON.stringify({ rowid: 0, id: null }), tx);
3220
+ await meta.setMeta(FTS5_REBUILD_SNAPSHOT_KEY, JSON.stringify({ entries: e?.hi ?? 0, map: m?.hi ?? 0 }), tx);
3221
+ await meta.setMeta(FTS5_SWEEP_CURSOR_KEY, "0", tx);
3222
+ }
3223
+ async function restartRebuildAfterVacuum(db, prefix, meta) {
3224
+ await db.withTransactionAsync(async (tx) => {
3225
+ if (await meta.getMeta(FTS5_STATE_KEY, tx) !== "rebuilding") return;
3226
+ await writeRebuildStart(tx, prefix, meta);
3227
+ });
3228
+ }
3229
+
3189
3230
  // src/services/librarian/constants.ts
3190
3231
  var FUZZY_THRESHOLD = 0.5;
3191
3232
  var MIN_TOKENS_TO_QUALIFY = 3;
@@ -4121,6 +4162,7 @@ var MaintenanceService = class {
4121
4162
  if (vacuum) {
4122
4163
  await resetNormalizeWatermarks(this.metadataRepo);
4123
4164
  await this.metadataRepo.vacuum();
4165
+ await restartRebuildAfterVacuum(this.db, this.prefix, this.metadataRepo);
4124
4166
  }
4125
4167
  await this.searchService.syncEntries(entityId, syncedIds);
4126
4168
  return { entries: deletedEntries, tasks: deletedTasks, events: deletedEvents };
@@ -5814,7 +5856,7 @@ var UnavailableIndexStrategy = class {
5814
5856
  hasIndexedEntity(_entityId) {
5815
5857
  return false;
5816
5858
  }
5817
- async drain() {
5859
+ async drain(_opts) {
5818
5860
  }
5819
5861
  };
5820
5862
 
@@ -5860,24 +5902,31 @@ var _SearchService = class _SearchService {
5860
5902
  isKeywordAvailable() {
5861
5903
  return !(this.indexStrategy instanceof UnavailableIndexStrategy);
5862
5904
  }
5905
+ /** True when the installed strategy drains a durable ledger (FTS5, or the degraded stand-in). */
5906
+ hasLedger() {
5907
+ return this.indexStrategy.drain !== void 0;
5908
+ }
5863
5909
  /**
5864
5910
  * One serialized drain turn for strategies with a durable ledger. Like
5865
5911
  * sync(), it never rejects: the keyword index is rebuildable, and a failed
5866
- * drain leaves the ledger rows in place for the next turn.
5912
+ * drain leaves the ledger rows in place for the next turn. Resolves false
5913
+ * when the drain failed.
5867
5914
  */
5868
- drainTurn(entityId) {
5915
+ drainTurn(entityId, opts) {
5869
5916
  const work = this.syncChain.then(async () => {
5870
5917
  try {
5871
5918
  try {
5872
- await this.indexStrategy.drain();
5919
+ await this.indexStrategy.drain(opts);
5873
5920
  } finally {
5874
5921
  this.evictCache(entityId);
5875
5922
  }
5923
+ return true;
5876
5924
  } catch (err) {
5877
5925
  console.warn(`[WikiMemory] search index drain failed for ${entityId ?? "*"}:`, err);
5926
+ return false;
5878
5927
  }
5879
5928
  });
5880
- this.syncChain = work;
5929
+ this.syncChain = work.then(() => void 0);
5881
5930
  return work;
5882
5931
  }
5883
5932
  /**
@@ -5887,9 +5936,18 @@ var _SearchService = class _SearchService {
5887
5936
  * Rebuilds are serialized per instance and never reject: the keyword index
5888
5937
  * is a rebuildable cache over SQLite, so degraded keyword search is the
5889
5938
  * correct failure mode and killing the host process is not.
5939
+ *
5940
+ * With a ledger strategy, `sync()` runs one full quantum bounded by
5941
+ * `opts.deadline`; `sync(entityId)` is a write turn (ledger only).
5942
+ * `opts.ids` are the caller's touched ids: a ledger strategy drains them
5943
+ * first, to completion; MiniSearch ignores them and rebuilds the entity in
5944
+ * one scan.
5890
5945
  */
5891
- async sync(entityId) {
5892
- if (this.indexStrategy.drain) return this.drainTurn(entityId);
5946
+ async sync(entityId, opts) {
5947
+ if (this.indexStrategy.drain) {
5948
+ await this.drainTurn(entityId, entityId === void 0 ? { mode: "full", deadline: opts?.deadline, ids: opts?.ids } : { mode: "ledger", entityId, ids: opts?.ids });
5949
+ return;
5950
+ }
5893
5951
  const work = this.syncChain.then(async () => {
5894
5952
  try {
5895
5953
  try {
@@ -5930,13 +5988,17 @@ var _SearchService = class _SearchService {
5930
5988
  * — including on an empty `ids` list, which is a no-op only for an entity
5931
5989
  * the index already tracks, and even then one that waits for rebuilds
5932
5990
  * already queued on the chain.
5991
+ *
5992
+ * Resolves false only when a ledger drain failed: the caller's ids were not
5993
+ * applied (their ledger rows remain), so a caller holding them retries.
5933
5994
  */
5934
5995
  async syncEntries(entityId, ids) {
5935
- if (this.indexStrategy.drain) return this.drainTurn(entityId);
5996
+ if (this.indexStrategy.drain) return this.drainTurn(entityId, { mode: "ledger", entityId, ids: [...new Set(ids)] });
5936
5997
  const uniqueIds = [...new Set(ids)];
5937
5998
  const indexed = this.indexStrategy.hasIndexedEntity?.(entityId);
5938
5999
  if (uniqueIds.length === 0 && !this.staleEntities.has(entityId) && indexed === true) {
5939
- return this.syncChain;
6000
+ await this.syncChain;
6001
+ return true;
5940
6002
  }
5941
6003
  const work = this.syncChain.then(async () => {
5942
6004
  try {
@@ -5965,7 +6027,8 @@ var _SearchService = class _SearchService {
5965
6027
  }
5966
6028
  });
5967
6029
  this.syncChain = work;
5968
- return work;
6030
+ await work;
6031
+ return true;
5969
6032
  }
5970
6033
  /**
5971
6034
  * Forces the entity's next syncEntries to rebuild it in full. For writes core
@@ -5981,11 +6044,13 @@ var _SearchService = class _SearchService {
5981
6044
  * so only those entities are rebuilt. Each rebuild still takes its own turn
5982
6045
  * on the sync chain, and like syncEntries this never rejects. With nothing
5983
6046
  * stale it resolves at once, without waiting on work already on the chain.
6047
+ * Resolves false only when a ledger drain failed (see syncEntries).
5984
6048
  */
5985
- async syncStale() {
5986
- if (this.indexStrategy.drain) return this.drainTurn();
6049
+ async syncStale(opts) {
6050
+ if (this.indexStrategy.drain) return this.drainTurn(void 0, { mode: "full", ids: opts?.ids });
5987
6051
  const ids = [...this.staleEntities];
5988
6052
  await Promise.all(ids.map((id) => this.syncEntries(id, [])));
6053
+ return true;
5989
6054
  }
5990
6055
  /**
5991
6056
  * Clears the parsed vector cache. Useful for mid-loop flush guarantees