@equationalapplications/core-llm-wiki 7.3.0 → 7.4.0

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/README.md CHANGED
@@ -285,6 +285,31 @@ True cosine-range pure semantic ranking (including negative cosine values) is us
285
285
  **Pre-filtering optimization:**
286
286
  When `preFilterLimit: 50` is set with 1000 facts, cosine similarity is computed only for the top 50 MiniSearch keyword matches, reducing O(N) scoring to O(50).
287
287
 
288
+ ## Draft Review
289
+
290
+ Facts can carry `lifecycle_status: 'draft'`, for example when a host marks model output as unreviewed. Drafts stay visible by default. To keep them out of results:
291
+
292
+ ```ts
293
+ await wiki.read('user-1', 'deploy process', { excludeDrafts: true });
294
+ await wiki.traverseGraph('user-1', { sourceId, excludeDrafts: true });
295
+ // or engine-wide:
296
+ createWiki(db, { llmProvider, config: { excludeDrafts: true } });
297
+ ```
298
+
299
+ - On every `read()` path, drafts are removed **before** `maxResults`, `tierFloors`, and pre-filter cuts, so they never take slots from reviewed facts.
300
+ - In traversal, drafts are dead ends. The starting fact is always returned.
301
+ - Status is read from SQLite on every call. A promotion is visible immediately, with no re-indexing.
302
+
303
+ Review API:
304
+
305
+ ```ts
306
+ const { facts, nextCursor } = await wiki.listDrafts('user-1', { limit: 50 });
307
+ await wiki.promoteDraft(facts[0].id, 'user-1', { by: 'human:alice' }); // → stable, trustTier 'human-reviewed'
308
+ // Reject with setLifecycleStatus(id, entityId, 'deprecated') or forget().
309
+ ```
310
+
311
+ `promoteDraft` throws `WikiDraftNotFound` when no live draft with that id exists for the entity. The error is contextless by design. Promotion does not change `updated_at`, so a promoted fact keeps its recency position.
312
+
288
313
  ## Pluggable Vector Retrieval
289
314
 
290
315
  When your entity corpus grows, in-process cosine similarity scoring becomes a bottleneck. The optional **`VectorRanker`** interface lets you delegate semantic ranking to [**sqlite-vec**](https://github.com/asg017/sqlite-vec), [**sqlite-vss**](https://github.com/asg017/sqlite-vss), or an external vector database while `WikiMemory` handles embedding validation, hybrid scoring, and tier-2 row hydration.
@@ -168,6 +168,14 @@ var WikiInvalidReadOptions = class _WikiInvalidReadOptions extends Error {
168
168
  Object.setPrototypeOf(this, _WikiInvalidReadOptions.prototype);
169
169
  }
170
170
  };
171
+ var WikiDraftNotFound = class _WikiDraftNotFound extends Error {
172
+ constructor() {
173
+ super("No draft fact with that id exists for this entity.");
174
+ this.code = "WIKI_DRAFT_NOT_FOUND";
175
+ this.name = "WikiDraftNotFound";
176
+ Object.setPrototypeOf(this, _WikiDraftNotFound.prototype);
177
+ }
178
+ };
171
179
 
172
180
  // src/utils/ids.ts
173
181
  var _injectedGetRandomValues = null;
@@ -4552,6 +4560,7 @@ function selectWithFloors(sortedRows, floors, maxResults) {
4552
4560
  }
4553
4561
 
4554
4562
  // src/services/RetrievalService.ts
4563
+ var EMPTY_ID_SET = /* @__PURE__ */ new Set();
4555
4564
  var RetrievalService = class {
4556
4565
  constructor(options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService) {
4557
4566
  this.options = options;
@@ -4584,6 +4593,7 @@ var RetrievalService = class {
4584
4593
  }
4585
4594
  const rawMaxResults = options?.maxResults ?? config?.maxResults ?? config?.maxFtsResults ?? 10;
4586
4595
  const maxResults = Number.isFinite(rawMaxResults) ? Math.max(0, Math.trunc(rawMaxResults)) : 10;
4596
+ const excludeDrafts = (options?.excludeDrafts ?? config?.excludeDrafts ?? false) === true;
4587
4597
  const trimmedQuery = query.trim();
4588
4598
  const sanitizedTierFloors = trimmedQuery && exposeMetadata ? validateTierFloors(
4589
4599
  entityIds,
@@ -4603,6 +4613,9 @@ var RetrievalService = class {
4603
4613
  if (maxResults === 0) ; else if (trimmedQuery) {
4604
4614
  let usedEmbed = false;
4605
4615
  const scoredEntityIds = this._filterScoredEntities(entityIds, sanitizedTierWeights, options?.includeZeroWeightEntities);
4616
+ const draftIds = excludeDrafts && scoredEntityIds.length > 0 ? await this.entryRepo.findDraftIdsByEntityIds(scoredEntityIds) : EMPTY_ID_SET;
4617
+ const draftPad = draftIds.size;
4618
+ const padLimit = (n) => n >= Number.MAX_SAFE_INTEGER ? n : n + draftPad;
4606
4619
  if (scoredEntityIds.length === 0) {
4607
4620
  usedEmbed = true;
4608
4621
  } else if (!skipEmbed && embedFn) {
@@ -4636,7 +4649,7 @@ var RetrievalService = class {
4636
4649
  let miniSearchScores;
4637
4650
  if (effectivePreFilterLimit !== void 0) {
4638
4651
  populateCache = false;
4639
- const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER);
4652
+ const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER).filter((r) => !draftIds.has(r.id));
4640
4653
  if (preResults.length === 0) {
4641
4654
  candidateRows = null;
4642
4655
  } else {
@@ -4658,9 +4671,9 @@ var RetrievalService = class {
4658
4671
  }
4659
4672
  } else {
4660
4673
  if (useRanker) {
4661
- candidateRows = await this.entryRepo.findMetadataByEntityIds(scoredEntityIds);
4674
+ candidateRows = this._withoutDrafts(await this.entryRepo.findMetadataByEntityIds(scoredEntityIds), draftIds);
4662
4675
  } else {
4663
- candidateRows = await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds);
4676
+ candidateRows = this._withoutDrafts(await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds), draftIds);
4664
4677
  }
4665
4678
  if (weight !== void 0 && weight < 1) {
4666
4679
  miniSearchScores = this.searchService.getMiniSearchScores(trimmedQuery, scoredEntityIds);
@@ -4690,7 +4703,7 @@ var RetrievalService = class {
4690
4703
  candidateRows: rowsForEntity,
4691
4704
  weight,
4692
4705
  miniSearchScores,
4693
- limit: Math.max(maxResults * 2, maxResults + 50)
4706
+ limit: padLimit(Math.max(maxResults * 2, maxResults + 50))
4694
4707
  });
4695
4708
  return ranked.map((row) => ({ ...row, entity_id: scopedEntityId }));
4696
4709
  })
@@ -4904,7 +4917,7 @@ var RetrievalService = class {
4904
4917
  });
4905
4918
  } else if (policy === "keyword") {
4906
4919
  const hasActiveFloorsKeywordFallback = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
4907
- const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
4920
+ const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
4908
4921
  const preFilteredIds = effectivePreFilterLimit !== void 0 ? new Set(candidateRows.map((r) => r.id)) : void 0;
4909
4922
  const keywordResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, keywordOversampledLimit);
4910
4923
  const topResults = preFilteredIds === void 0 ? keywordResults : keywordResults.filter((r) => preFilteredIds.has(r.id));
@@ -4945,6 +4958,7 @@ var RetrievalService = class {
4945
4958
  // read() re-sorts after applying tier weights
4946
4959
  });
4947
4960
  }
4961
+ if (draftPad > 0) scored = scored.filter((s) => !draftIds.has(s.id));
4948
4962
  if (scored.length > 0) {
4949
4963
  scored = scored.map((row) => ({
4950
4964
  ...row,
@@ -5007,8 +5021,8 @@ var RetrievalService = class {
5007
5021
  }
5008
5022
  if (!usedEmbed && scoredEntityIds.length > 0) {
5009
5023
  const hasActiveFloors = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
5010
- const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
5011
- const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit);
5024
+ const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
5025
+ const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit).filter((r) => !draftIds.has(r.id));
5012
5026
  const candidates = results.map((r) => ({
5013
5027
  id: r.id,
5014
5028
  entity_id: r.entity_id,
@@ -5032,7 +5046,7 @@ var RetrievalService = class {
5032
5046
  await this.entryRepo.trackAccess(ids, now);
5033
5047
  }
5034
5048
  } else {
5035
- facts = await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
5049
+ facts = excludeDrafts ? await this.entryRepo.findRecentByEntityIds(entityIds, maxResults, void 0, { excludeDrafts: true }) : await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
5036
5050
  }
5037
5051
  const eventsLimit = Math.min(10 * entityIds.length, 100);
5038
5052
  const [tasks, events] = await Promise.all([
@@ -5068,6 +5082,9 @@ var RetrievalService = class {
5068
5082
  _tieBreakSort(items) {
5069
5083
  items.sort((a, b) => this._compareScoredRows(a, b));
5070
5084
  }
5085
+ _withoutDrafts(rows, draftIds) {
5086
+ return draftIds.size === 0 ? rows : rows.filter((row) => !draftIds.has(row.id));
5087
+ }
5071
5088
  /**
5072
5089
  * Comparator for score + deterministic tie-break fields.
5073
5090
  * Negative return means "a ranks ahead of b" for descending score order.
@@ -5274,6 +5291,6 @@ var WriteService = class {
5274
5291
  }
5275
5292
  };
5276
5293
 
5277
- export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, DiagnosticBuffer, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
5278
- //# sourceMappingURL=chunk-IAYO63DQ.mjs.map
5279
- //# sourceMappingURL=chunk-IAYO63DQ.mjs.map
5294
+ export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, DiagnosticBuffer, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDraftNotFound, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
5295
+ //# sourceMappingURL=chunk-G5OR2VWD.mjs.map
5296
+ //# sourceMappingURL=chunk-G5OR2VWD.mjs.map