@equationalapplications/core-llm-wiki 4.20.0 → 4.21.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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, generateId, BaseRepository, emptyManifest, resolveNodeType, validateInlineEdges, resolveEdgeDefinition, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-HKB5FEJG.mjs';
2
- export { HOOK_TIMEOUT_MARKER, PromptService, PrunePartialFailureError, WikiBusyError, WikiTransactionError, configureRandomSource, parseEmbedding } from './chunk-HKB5FEJG.mjs';
1
+ import { __privateAdd, MetadataRepository, EmbeddingService, SearchService, JobManager, PromptService, IngestionService, MaintenanceService, ImportExportService, RetrievalService, WriteService, __privateGet, __privateSet, normalizeSourceRef, normalizeSourceHash, entitySummaryMetaKey, generateId, BaseRepository, emptyManifest, resolveNodeType, validateInlineEdges, resolveEdgeDefinition, normalizeTitleKey, WikiTransactionError, extractSqliteCode } from './chunk-UYLYN5N4.mjs';
2
+ export { HOOK_TIMEOUT_MARKER, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, WikiBusyError, WikiTransactionError, configureRandomSource, parseEmbedding } from './chunk-UYLYN5N4.mjs';
3
3
  import { appendRelatedSection, buildConceptDocument, buildLogMd, buildEntityIndexMd, buildRootIndexMd, isAllowedOkfPath, parseRootIndexMd, parseEntityIndexMd, parseConcept, splitRelatedSection, extractMarkdownLinks, parseLogMd, parseEventIdComment, appendEventIdComment } from '@equationalapplications/core-okf';
4
4
 
5
5
  // src/db/schema.ts
@@ -22,7 +22,8 @@ async function setupDatabase(db, prefix) {
22
22
  deleted_at INTEGER,
23
23
  embedding TEXT,
24
24
  embedding_blob BLOB,
25
- okf_type TEXT
25
+ okf_type TEXT,
26
+ ontology_checked_at INTEGER
26
27
  );
27
28
 
28
29
  CREATE INDEX IF NOT EXISTS ${prefix}entries_entity_idx ON ${prefix}entries(entity_id);
@@ -248,6 +249,20 @@ var MIGRATIONS = [
248
249
  );
249
250
  `);
250
251
  }
252
+ },
253
+ {
254
+ version: 7,
255
+ description: "Add ontology_checked_at to entries for ontology backfill recheck cooldown",
256
+ run: async (db, prefix) => {
257
+ const cols = await db.getAllAsync(
258
+ `PRAGMA table_info(${prefix}entries)`
259
+ );
260
+ if (!cols.some((c) => c.name === "ontology_checked_at")) {
261
+ await db.execAsync(
262
+ `ALTER TABLE ${prefix}entries ADD COLUMN ontology_checked_at INTEGER`
263
+ );
264
+ }
265
+ }
251
266
  }
252
267
  ];
253
268
  for (let i = 1; i < MIGRATIONS.length; i++) {
@@ -996,6 +1011,79 @@ var EntryRepository = class extends BaseRepository {
996
1011
  );
997
1012
  return rows.map(mapRowToFact);
998
1013
  }
1014
+ /**
1015
+ * Live untyped facts eligible for ontology backfill, oldest first.
1016
+ * Skips facts checked within the recheck cooldown (ontology_checked_at > recheckCutoff).
1017
+ */
1018
+ async findUntypedByEntityId(entityId, limit, recheckCutoff, tx) {
1019
+ const executor = this.getExecutor(tx);
1020
+ const rows = await executor.getAllAsync(
1021
+ `SELECT * FROM ${this.prefix}entries
1022
+ WHERE entity_id = ? AND okf_type IS NULL AND deleted_at IS NULL
1023
+ AND (ontology_checked_at IS NULL OR ontology_checked_at <= ?)
1024
+ ORDER BY updated_at ASC LIMIT ?`,
1025
+ [entityId, recheckCutoff, limit]
1026
+ );
1027
+ return rows.map(mapRowToFact);
1028
+ }
1029
+ /** Counts live untyped facts: eligible (past cooldown) vs deferred (in cooldown). */
1030
+ async countUntypedByEntityId(entityId, recheckCutoff, tx) {
1031
+ const executor = this.getExecutor(tx);
1032
+ const row = await executor.getFirstAsync(
1033
+ `SELECT
1034
+ SUM(CASE WHEN ontology_checked_at IS NULL OR ontology_checked_at <= ? THEN 1 ELSE 0 END) AS eligible,
1035
+ SUM(CASE WHEN ontology_checked_at IS NOT NULL AND ontology_checked_at > ? THEN 1 ELSE 0 END) AS deferred
1036
+ FROM ${this.prefix}entries
1037
+ WHERE entity_id = ? AND okf_type IS NULL AND deleted_at IS NULL`,
1038
+ [recheckCutoff, recheckCutoff, entityId]
1039
+ );
1040
+ return { eligible: Number(row?.eligible ?? 0), deferred: Number(row?.deferred ?? 0) };
1041
+ }
1042
+ /**
1043
+ * Sets okf_type only when currently NULL — additive by construction: a
1044
+ * concurrently-typed fact is never overwritten. Returns changes for the caller
1045
+ * to distinguish applied vs skipped.
1046
+ */
1047
+ async updateOkfType(id, entityId, okfType, tx) {
1048
+ const result = await tx.runAsync(
1049
+ `UPDATE ${this.prefix}entries SET okf_type = ?
1050
+ WHERE id = ? AND entity_id = ? AND okf_type IS NULL AND deleted_at IS NULL`,
1051
+ [okfType, id, entityId]
1052
+ );
1053
+ return { changes: result.changes };
1054
+ }
1055
+ /**
1056
+ * Stamps the backfill recheck cooldown. NEVER touches updated_at — import
1057
+ * merge resolution is last-write-wins on updated_at and a bump here would
1058
+ * make an unchanged local fact beat a genuinely newer remote edit.
1059
+ */
1060
+ async markOntologyChecked(ids, entityId, now, tx) {
1061
+ if (ids.length === 0) return;
1062
+ for (let i = 0; i < ids.length; i += this.chunkSize) {
1063
+ const chunk = ids.slice(i, i + this.chunkSize);
1064
+ const placeholders = chunk.map(() => "?").join(",");
1065
+ await tx.runAsync(
1066
+ `UPDATE ${this.prefix}entries SET ontology_checked_at = ?
1067
+ WHERE id IN (${placeholders}) AND entity_id = ? AND deleted_at IS NULL`,
1068
+ [now, ...chunk, entityId]
1069
+ );
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Lightweight full-breadth title index (id, title, okf_type) over all live
1074
+ * typed facts. Used by ontology backfill edge resolution — a recent-N window
1075
+ * would miss an old fact's contemporaries. Untyped rows are excluded: they
1076
+ * can never pass the edge target-type check, and batch facts typed during
1077
+ * the run are re-added to the in-memory index by the caller.
1078
+ */
1079
+ async findTitleIndexByEntityId(entityId, tx) {
1080
+ const executor = this.getExecutor(tx);
1081
+ const rows = await executor.getAllAsync(
1082
+ `SELECT id, title, okf_type FROM ${this.prefix}entries WHERE entity_id = ? AND deleted_at IS NULL AND okf_type IS NOT NULL`,
1083
+ [entityId]
1084
+ );
1085
+ return rows.map((r) => ({ id: r.id, title: r.title, okf_type: r.okf_type ?? null }));
1086
+ }
999
1087
  };
1000
1088
 
1001
1089
  // src/repositories/OutboxRepository.ts
@@ -1416,6 +1504,7 @@ var CONFIDENCE_RANK = {
1416
1504
  var EdgeRepository = class extends BaseRepository {
1417
1505
  /**
1418
1506
  * Insert an edge, silently skipping on primary-key or uniqueness conflicts.
1507
+ * Returns true when a row was inserted, false when skipped as a duplicate.
1419
1508
  * Throws when the insert was skipped due to an id collision with a different edge tuple.
1420
1509
  */
1421
1510
  async addIgnoreDuplicate(edge, tx) {
@@ -1425,17 +1514,18 @@ var EdgeRepository = class extends BaseRepository {
1425
1514
  VALUES (?, ?, ?, ?, ?, ?)`,
1426
1515
  [edge.id, edge.entity_id, edge.source_id, edge.target_id, edge.edge_type, edge.created_at]
1427
1516
  );
1428
- if (result.changes > 0) return;
1517
+ if (result.changes > 0) return true;
1429
1518
  const existing = await executor.getFirstAsync(
1430
1519
  `SELECT entity_id, source_id, target_id, edge_type FROM ${this.prefix}edges WHERE id = ?`,
1431
1520
  [edge.id]
1432
1521
  );
1433
- if (!existing) return;
1522
+ if (!existing) return false;
1434
1523
  if (String(existing.entity_id) !== edge.entity_id || String(existing.source_id) !== edge.source_id || String(existing.target_id) !== edge.target_id || String(existing.edge_type) !== edge.edge_type) {
1435
1524
  throw new Error(
1436
1525
  `Edge id collision: ${JSON.stringify(edge.id)} already exists with a different (entity_id, source_id, target_id, edge_type) tuple`
1437
1526
  );
1438
1527
  }
1528
+ return false;
1439
1529
  }
1440
1530
  async getByEntityId(entityId, tx) {
1441
1531
  const executor = this.getExecutor(tx);
@@ -1635,7 +1725,8 @@ var OntologyService = class {
1635
1725
  return { okf_type: canonical, edges };
1636
1726
  }
1637
1727
  async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now) {
1638
- if (!sourceType || edges.length === 0) return;
1728
+ if (!sourceType || edges.length === 0) return 0;
1729
+ let persisted = 0;
1639
1730
  for (const edge of edges) {
1640
1731
  const def = resolveEdgeDefinition(edge.edge_type, manifest);
1641
1732
  if (!def || def.source_type.toLowerCase() !== sourceType.toLowerCase()) continue;
@@ -1651,8 +1742,10 @@ var OntologyService = class {
1651
1742
  edge_type: def.type,
1652
1743
  created_at: now
1653
1744
  };
1654
- await this.edgeRepo.addIgnoreDuplicate(wikiEdge, tx);
1745
+ const inserted = await this.edgeRepo.addIgnoreDuplicate(wikiEdge, tx);
1746
+ if (inserted) persisted++;
1655
1747
  }
1748
+ return persisted;
1656
1749
  }
1657
1750
  };
1658
1751
 
@@ -1892,6 +1985,24 @@ var WikiMemory = class {
1892
1985
  async runHeal(entityId, options) {
1893
1986
  return this.maintenanceService.runHeal(entityId, options);
1894
1987
  }
1988
+ /**
1989
+ * Types already-persisted untyped facts (okf_type IS NULL) in place via one
1990
+ * librarian-style LLM call. Strictly additive: never creates, deletes, or
1991
+ * rewrites facts; never overwrites an existing okf_type. Free when there is
1992
+ * nothing to do (ontology off, or zero eligible untyped facts).
1993
+ *
1994
+ * Hosts own the trigger cadence (e.g. after each sync); loop `while
1995
+ * result.remaining > 0` for convergence. Throws WikiBusyError when another
1996
+ * backfill (or conflicting maintenance op) is running for the entity.
1997
+ *
1998
+ * @param options.promptOverride - Applies only to this call. For persistent
1999
+ * customization set `options.config.prompts.ontologyBackfillSystemPrompt`.
2000
+ * @param options.batchSize - Facts per run (default 25) for providers with
2001
+ * tighter context limits.
2002
+ */
2003
+ async runOntologyBackfill(entityId, options) {
2004
+ return this.maintenanceService.runOntologyBackfill(entityId, options);
2005
+ }
1895
2006
  async runReembed(entityId, opts) {
1896
2007
  return this.maintenanceService.runReembed(entityId, opts);
1897
2008
  }