@equationalapplications/core-llm-wiki 5.2.1 → 5.3.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.
@@ -79,6 +79,31 @@ var WikiDuplicateHashError = class extends Error {
79
79
  this.entityId = params.entityId;
80
80
  }
81
81
  };
82
+ var WikiStrictOntologyViolation = class extends Error {
83
+ constructor(entityId, kind, type) {
84
+ super(
85
+ `Out-of-manifest ${kind} type "${type}" for entity "${entityId}" under strict mode.`
86
+ );
87
+ this.code = "WIKI_STRICT_ONTOLOGY_VIOLATION";
88
+ this.entityId = entityId;
89
+ this.kind = kind;
90
+ this.type = type;
91
+ this.name = "WikiStrictOntologyViolation";
92
+ }
93
+ };
94
+ var WikiSourceRefHashCollision = class extends Error {
95
+ constructor(params) {
96
+ super(
97
+ `Source hash "${params.sourceHash}" for entity "${params.entityId}" is already mapped to sourceRef "${params.existingSourceRef}"; cannot remap to "${params.attemptedSourceRef}".`
98
+ );
99
+ this.code = "WIKI_SOURCE_REF_HASH_COLLISION";
100
+ this.entityId = params.entityId;
101
+ this.sourceHash = params.sourceHash;
102
+ this.existingSourceRef = params.existingSourceRef;
103
+ this.attemptedSourceRef = params.attemptedSourceRef;
104
+ this.name = "WikiSourceRefHashCollision";
105
+ }
106
+ };
82
107
  var WikiTransactionError = class extends Error {
83
108
  constructor(message, options) {
84
109
  super(message, options);
@@ -211,14 +236,25 @@ function mergeOntologyUpdates(current, updates) {
211
236
  }
212
237
  return { node_types, edge_types };
213
238
  }
214
- function validateInlineEdges(sourceType, _targetType, edges, manifest) {
215
- if (!Array.isArray(edges)) return [];
239
+ function validateInlineEdges(sourceType, _targetType, edges, manifest, opts) {
240
+ const strict = opts?.strict === true;
241
+ const entityId = opts?.entityId ?? "";
242
+ if (!Array.isArray(edges)) {
243
+ if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", "");
244
+ return [];
245
+ }
216
246
  const valid = [];
217
247
  for (const edge of edges) {
218
- if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") continue;
248
+ if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") {
249
+ if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", String(edge?.edge_type ?? ""));
250
+ continue;
251
+ }
219
252
  const defs = resolveEdgeDefinitions(edge.edge_type, manifest);
220
253
  const match = defs.find((d) => d.source_type.toLowerCase() === sourceType.toLowerCase());
221
- if (!match) continue;
254
+ if (!match) {
255
+ if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", edge.edge_type);
256
+ continue;
257
+ }
222
258
  valid.push({ edge_type: match.type, target_title: edge.target_title });
223
259
  }
224
260
  return valid;
@@ -1266,12 +1302,14 @@ var DEFAULT_CHUNK_OVERLAP = 400;
1266
1302
 
1267
1303
  // src/services/IngestionService.ts
1268
1304
  var IngestionService = class {
1269
- constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
1305
+ constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, metadataRepo, edgeRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
1270
1306
  this.db = db;
1271
1307
  this.prefix = prefix;
1272
1308
  this.options = options;
1273
1309
  this.entryRepo = entryRepo;
1274
1310
  this.sourceRefIndexRepo = sourceRefIndexRepo;
1311
+ this.metadataRepo = metadataRepo;
1312
+ this.edgeRepo = edgeRepo;
1275
1313
  this.searchService = searchService;
1276
1314
  this.jobManager = jobManager;
1277
1315
  this.embeddingService = embeddingService;
@@ -1344,12 +1382,8 @@ var IngestionService = class {
1344
1382
  try {
1345
1383
  await this.db.withTransactionAsync(async (tx) => {
1346
1384
  deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
1347
- await this.entryRepo.softDeleteBySource(entityId, tx, sourceRef, null);
1348
- await this.sourceRefIndexRepo.softDeleteByEntityAndSourceRef(entityId, sourceRef, tx);
1349
- await this.sourceRefIndexRepo.upsert(entityId, sourceHash, sourceRef, tx);
1350
1385
  const titleIndex = /* @__PURE__ */ new Map();
1351
- const pendingEdges = [];
1352
- const existingFacts = await this.entryRepo.findRecentByEntityId(entityId, 500, tx);
1386
+ const existingFacts = await this.entryRepo.findRecentByEntityId(entityId, 500, tx, sourceRef);
1353
1387
  for (const existing of existingFacts) {
1354
1388
  titleIndex.set(normalizeTitleKey(existing.title), {
1355
1389
  id: existing.id,
@@ -1358,6 +1392,8 @@ var IngestionService = class {
1358
1392
  }
1359
1393
  let ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
1360
1394
  let { mode, manifest } = ontologyState;
1395
+ const hostNodes = [];
1396
+ const rawEdgeRequests = [];
1361
1397
  for (const { facts, ontology_updates } of orderedChunkFacts) {
1362
1398
  if (mode === "emergent" && ontology_updates && this.ontologyService) {
1363
1399
  manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
@@ -1366,45 +1402,51 @@ var IngestionService = class {
1366
1402
  }
1367
1403
  for (const fact of facts) {
1368
1404
  const ontologyFact = fact;
1369
- const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest) ?? { okf_type: null, edges: [] };
1405
+ const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
1370
1406
  const id = generateId("fact_");
1371
- const wikiFact = {
1407
+ hostNodes.push({
1372
1408
  id,
1373
- entity_id: entityId,
1409
+ type: ontologyFact.okf_type ?? "",
1374
1410
  title: fact.title,
1375
1411
  body: fact.body,
1412
+ // Forward the LLM-extracted tags/confidence through the
1413
+ // extracted-shape upsertGraphCore path so the entries row
1414
+ // stores them (search filterability, heal-candidate
1415
+ // selection, runReembed embedding-text signal). The public
1416
+ // WikiMemory.upsertGraph API doesn't accept tags/confidence
1417
+ // — host-supplied deterministic nodes default to [] and
1418
+ // 'certain' as documented.
1376
1419
  tags: fact.tags,
1377
- confidence: fact.confidence,
1378
- source_type: "immutable_document",
1379
- source_hash: sourceHash,
1380
- source_ref: sourceRef,
1381
- created_at: now,
1382
- updated_at: now,
1383
- last_accessed_at: null,
1384
- access_count: 0,
1385
- deleted_at: null,
1386
- okf_type: normalized.okf_type
1387
- };
1388
- await this.entryRepo.upsert(wikiFact, tx);
1420
+ confidence: fact.confidence
1421
+ });
1389
1422
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
1390
1423
  titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
1391
1424
  if (normalized.edges.length > 0) {
1392
- pendingEdges.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
1425
+ rawEdgeRequests.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
1393
1426
  }
1394
1427
  }
1395
1428
  }
1396
- for (const item of pendingEdges) {
1397
- await this.ontologyService?.resolveAndPersistEdges(
1429
+ const hostEdges = [];
1430
+ for (const req of rawEdgeRequests) {
1431
+ const resolved = this.ontologyService?.resolveEdges(
1398
1432
  entityId,
1399
- item.sourceId,
1400
- item.sourceType,
1401
- item.edges ?? [],
1433
+ req.sourceId,
1434
+ req.sourceType,
1435
+ req.edges,
1402
1436
  manifest,
1403
1437
  titleIndex,
1404
- tx,
1405
1438
  now
1406
- );
1439
+ ) ?? [];
1440
+ for (const e of resolved) {
1441
+ hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
1442
+ }
1407
1443
  }
1444
+ await this.upsertGraphCore(
1445
+ entityId,
1446
+ { sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
1447
+ tx,
1448
+ { strict: false }
1449
+ );
1408
1450
  });
1409
1451
  } catch (err) {
1410
1452
  const sqliteCode = err instanceof WikiTransactionError ? err.sqliteErrorCode : extractSqliteCode(err);
@@ -1434,6 +1476,126 @@ var IngestionService = class {
1434
1476
  releaseIngestLocks();
1435
1477
  }
1436
1478
  }
1479
+ /**
1480
+ * Implements spec §2 data flow steps a–j with the host-facing
1481
+ * `{sourceRef, sourceHash, nodes, edges}` parameter shape. Shared between
1482
+ * `ingestDocument` (with `{ strict: false }` to preserve current behavior)
1483
+ * and `WikiMemory.upsertGraph` (which lets the persisted ontology mode
1484
+ * decide strictness).
1485
+ *
1486
+ * Runs ENTIRELY inside the supplied `tx`. Does not acquire locks, does not
1487
+ * open a nested transaction, does not perform post-commit work (search
1488
+ * sync, embedding, cache eviction). Those concerns remain in `ingestDocument`.
1489
+ *
1490
+ * The pre-flight validation (steps a–c) lives INSIDE this method per the
1491
+ * spec, so the public `WikiMemory.upsertGraph` can be a thin wrapper that
1492
+ * performs only the C2 probe before delegating.
1493
+ *
1494
+ * @returns Counts: nodesWritten (validated nodes persisted), edgesWritten
1495
+ * (manifest-valid edges persisted), superseded (prior facts soft-deleted
1496
+ * plus prior source-ref edges hard-deleted).
1497
+ */
1498
+ async upsertGraphCore(entityId, params, tx, opts) {
1499
+ const now = Date.now();
1500
+ const ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
1501
+ let { mode, manifest } = ontologyState;
1502
+ const strictEffective = opts?.strict === false ? false : opts?.strict === true || mode === "strict";
1503
+ const wikiFacts = [];
1504
+ for (const node of params.nodes) {
1505
+ const factLike = { okf_type: node.type, edges: [] };
1506
+ const normalized = this.ontologyService?.validateAndNormalizeFact(factLike, manifest, {
1507
+ strict: strictEffective,
1508
+ entityId
1509
+ }) ?? { okf_type: null};
1510
+ const wikiFact = {
1511
+ id: node.id,
1512
+ entity_id: entityId,
1513
+ title: node.title,
1514
+ body: node.body ?? "",
1515
+ // Public upsertGraph callers don't supply tags/confidence, so they
1516
+ // default to [] / 'certain' for deterministic AST-grade facts.
1517
+ // ingestDocument routes through this method for the LLM path and
1518
+ // forwards the LLM-supplied tags + confidence, preserving the prior
1519
+ // behavior (search filterability, heal-candidate selection, runReembed
1520
+ // embedding-text signal).
1521
+ tags: node.tags ? Array.from(node.tags) : [],
1522
+ confidence: node.confidence ?? "certain",
1523
+ source_type: "immutable_document",
1524
+ source_hash: params.sourceHash,
1525
+ source_ref: params.sourceRef,
1526
+ created_at: now,
1527
+ updated_at: now,
1528
+ last_accessed_at: null,
1529
+ access_count: 0,
1530
+ deleted_at: null,
1531
+ okf_type: normalized.okf_type
1532
+ };
1533
+ wikiFacts.push(wikiFact);
1534
+ }
1535
+ const sourceIdToType = /* @__PURE__ */ new Map();
1536
+ for (const fact of wikiFacts) sourceIdToType.set(fact.id, fact.okf_type ?? null);
1537
+ const hasConstraints = (manifest.node_types?.length ?? 0) > 0 || (manifest.edge_types?.length ?? 0) > 0;
1538
+ const validEdges = [];
1539
+ for (const edge of params.edges) {
1540
+ if (!hasConstraints) {
1541
+ validEdges.push({
1542
+ id: edge.id ?? generateId(),
1543
+ entity_id: entityId,
1544
+ source_id: edge.sourceId,
1545
+ target_id: edge.targetId,
1546
+ edge_type: edge.type,
1547
+ created_at: now
1548
+ });
1549
+ continue;
1550
+ }
1551
+ if (!sourceIdToType.has(edge.sourceId)) {
1552
+ validEdges.push({
1553
+ id: edge.id ?? generateId(),
1554
+ entity_id: entityId,
1555
+ source_id: edge.sourceId,
1556
+ target_id: edge.targetId,
1557
+ edge_type: edge.type,
1558
+ created_at: now
1559
+ });
1560
+ continue;
1561
+ }
1562
+ const sourceType = sourceIdToType.get(edge.sourceId);
1563
+ const candidates = (manifest.edge_types ?? []).filter(
1564
+ (d) => d.type.toLowerCase() === edge.type.toLowerCase() && d.source_type.toLowerCase() === (sourceType ?? "").toLowerCase()
1565
+ );
1566
+ const match = candidates[0];
1567
+ if (!match) {
1568
+ if (strictEffective) throw new WikiStrictOntologyViolation(entityId, "edge", edge.type);
1569
+ continue;
1570
+ }
1571
+ validEdges.push({
1572
+ id: edge.id ?? generateId(),
1573
+ entity_id: entityId,
1574
+ source_id: edge.sourceId,
1575
+ target_id: edge.targetId,
1576
+ edge_type: match.type,
1577
+ created_at: now
1578
+ });
1579
+ }
1580
+ const deletedFactIds = await this.entryRepo.findIdsBySource(entityId, params.sourceRef, null, tx, false);
1581
+ await this.entryRepo.softDeleteBySource(entityId, tx, params.sourceRef, null);
1582
+ const deletedEdgeCount = await this.edgeRepo.softDeleteBySourceFactIds(entityId, deletedFactIds, tx);
1583
+ await this.sourceRefIndexRepo.softDeleteByEntityAndSourceRef(entityId, params.sourceRef, tx);
1584
+ await this.sourceRefIndexRepo.upsert(entityId, params.sourceHash, params.sourceRef, tx);
1585
+ for (const wikiFact of wikiFacts) {
1586
+ await this.entryRepo.upsert(wikiFact, tx);
1587
+ }
1588
+ let edgesInsertedCount = 0;
1589
+ for (const edge of validEdges) {
1590
+ const inserted = await this.edgeRepo.addIgnoreDuplicate(edge, tx);
1591
+ if (inserted) edgesInsertedCount++;
1592
+ }
1593
+ return {
1594
+ nodesWritten: wikiFacts.length,
1595
+ edgesWritten: edgesInsertedCount,
1596
+ superseded: deletedFactIds.length + deletedEdgeCount
1597
+ };
1598
+ }
1437
1599
  };
1438
1600
 
1439
1601
  // src/repositories/BaseRepository.ts
@@ -1546,16 +1708,25 @@ var MetadataRepository = class extends BaseRepository {
1546
1708
  await this.db.execAsync(`PRAGMA wal_checkpoint(TRUNCATE)`);
1547
1709
  await this.db.execAsync(`VACUUM`);
1548
1710
  }
1711
+ /**
1712
+ * Returns the set of distinct `entity_id` values across entries/tasks/events.
1713
+ *
1714
+ * Deliberately widened from a prior `WHERE deleted_at IS NULL` form: the
1715
+ * returned set intentionally includes entities whose only rows are
1716
+ * soft-deleted, so the host's maintenance sweep can visit such
1717
+ * "decommissioned-scope" namespaces and reap the soft-deleted rows. Sorting
1718
+ * is `entity_id COLLATE BINARY` for stable, locale-independent ordering.
1719
+ */
1549
1720
  async getDistinctEntityIds(tx) {
1550
1721
  const executor = this.getExecutor(tx);
1551
1722
  const rows = await executor.getAllAsync(
1552
1723
  `SELECT DISTINCT entity_id FROM (
1553
- SELECT entity_id FROM ${this.prefix}entries WHERE deleted_at IS NULL
1724
+ SELECT entity_id FROM ${this.prefix}entries
1554
1725
  UNION
1555
- SELECT entity_id FROM ${this.prefix}tasks WHERE deleted_at IS NULL
1726
+ SELECT entity_id FROM ${this.prefix}tasks
1556
1727
  UNION
1557
1728
  SELECT entity_id FROM ${this.prefix}events
1558
- ) ORDER BY entity_id`
1729
+ ) ORDER BY entity_id COLLATE BINARY`
1559
1730
  );
1560
1731
  return rows.map((r) => r.entity_id);
1561
1732
  }
@@ -2045,7 +2216,7 @@ var MaintenanceService = class {
2045
2216
  }
2046
2217
  if (skip) continue;
2047
2218
  const ontologyFact = fact;
2048
- const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest) ?? { okf_type: null, edges: [] };
2219
+ const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
2049
2220
  const id = generateId("fact_");
2050
2221
  const factObj = {
2051
2222
  id,
@@ -2430,7 +2601,8 @@ var MaintenanceService = class {
2430
2601
  okf_type: classification.okf_type,
2431
2602
  edges: classification.edges
2432
2603
  },
2433
- manifest
2604
+ manifest,
2605
+ { strict: false }
2434
2606
  );
2435
2607
  if (!normalized.okf_type) {
2436
2608
  failedValidation++;
@@ -3741,6 +3913,6 @@ var WriteService = class {
3741
3913
  }
3742
3914
  };
3743
3915
 
3744
- export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, EmbeddingService, HEAL_BATCH_SIZE, 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, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, validateInlineEdges, validateManifest };
3745
- //# sourceMappingURL=chunk-6HNIOKT4.mjs.map
3746
- //# sourceMappingURL=chunk-6HNIOKT4.mjs.map
3916
+ export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, EmbeddingService, HEAL_BATCH_SIZE, 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, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, validateInlineEdges, validateManifest };
3917
+ //# sourceMappingURL=chunk-KIJINNYU.mjs.map
3918
+ //# sourceMappingURL=chunk-KIJINNYU.mjs.map