@equationalapplications/core-llm-wiki 7.7.2 → 7.7.4

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
@@ -361,7 +361,7 @@ new WikiMemory(db, {
361
361
  - Instructions, the ontology manifest, existing facts and identifiers never count, so a model cannot ground a claim by quoting them.
362
362
  - **The check.** Both sides are normalized with NFKC, whitespace runs collapse to one space, and matching is case-sensitive. A fact with more than 10 quotes, or any quote not found, fails.
363
363
  - **Diagnostics.** `grounding_missing` (reasons `no_evidence`, `evidence_too_short`) and `grounding_failed` (reasons `quote_not_found`, `too_many_quotes`), one per fact, with the new fact's `factId`. Quotes are never included.
364
- - **Duplicate titles in one ingest.** When chunks yield facts with the same title, ingest keeps one: a grounded fact beats one with missing or failed evidence, and on a tie the first one wins. The others are reported as `fact_deduplicated`. A fact already stored for the same `sourceRef` is not replaced by a later partial ingest.
364
+ - **Duplicate titles in one ingest.** When chunks yield facts with the same title, ingest keeps one: a grounded fact beats one with missing or failed evidence, and on a tie the first one wins. The others are reported as `fact_deduplicated`. A fact already stored for the same `sourceRef` is not replaced by a later partial ingest, and that skip is reported the same way. Either way the diagnostic carries `{ sourceRef, chunkIndex, itemIndex }` for the skipped fact, whether or not grounding is enabled.
365
365
  - **`upsertGraph`** nodes are host-supplied and never grounded.
366
366
  - **Librarian and heal** synthesize across events, so their pass rates are unknown. Measure them on your own event log before opting them in.
367
367
  - Evidence quotes are not stored.
@@ -1217,6 +1217,8 @@ await wikiMemory.ingestDocument(
1217
1217
  - `'throw'`: Pre-check before any LLM call; throw `WikiDuplicateHashError` (carries the canonical `sourceRef`).
1218
1218
  - The guard only considers **live** references — soft-deleted refs do not trigger it in any mode.
1219
1219
 
1220
+ When the pre-check passes but a concurrent writer claims the hash before the write commits, the transaction rolls back after the LLM extraction has already run. You still receive the diagnostics that describe that extraction — `ingest_chunk_failed`, `fact_rejected` and `fact_deduplicated` — in every mode, and also in the rarer case where the winning writer has itself rolled back by the time core looks, so the call surfaces the raw database error instead of `WikiDuplicateHashError`. Diagnostics tied to the rolled-back write (`grounding_*`, `edge_dropped`) are dropped, because the `factId`s they carry name rows that were never committed.
1221
+
1220
1222
  ## Batch Change Detection
1221
1223
 
1222
1224
  Check multiple documents for changes in one call:
@@ -2018,6 +2018,23 @@ var DiagnosticBuffer = class {
2018
2018
  this.items = [];
2019
2019
  for (const input of pending) emitDiagnostic(options, input);
2020
2020
  }
2021
+ /**
2022
+ * Emit only the buffered diagnostics whose code is in `codes` and discard
2023
+ * the rest. The buffer is empty afterwards either way.
2024
+ *
2025
+ * For an operation that did real work before its write was rolled back:
2026
+ * some buffered diagnostics describe that work and are still true, while
2027
+ * others describe rows that no longer exist (anything carrying a `factId`
2028
+ * minted inside the aborted transaction). `flush` would emit both and
2029
+ * `discard` would drop both; this emits the first set only.
2030
+ */
2031
+ flushOnly(options, codes) {
2032
+ const pending = this.items;
2033
+ this.items = [];
2034
+ for (const input of pending) {
2035
+ if (codes.includes(input.code)) emitDiagnostic(options, input);
2036
+ }
2037
+ }
2021
2038
  discard() {
2022
2039
  this.items = [];
2023
2040
  }
@@ -2034,6 +2051,16 @@ function edgeDropDiagnostic(drop, ctx) {
2034
2051
  }
2035
2052
 
2036
2053
  // src/services/IngestionService.ts
2054
+ var LLM_PASS_DIAGNOSTIC_CODES = ["ingest_chunk_failed", "fact_rejected", "fact_deduplicated"];
2055
+ function dedupDiagnostic(entityId, sourceRef, locator) {
2056
+ return {
2057
+ entityId,
2058
+ operation: "ingest",
2059
+ trigger: "call",
2060
+ code: "fact_deduplicated",
2061
+ detail: locator ? { sourceRef, chunkIndex: locator.chunkIndex, itemIndex: locator.itemIndex, reason: "exact_title" } : { sourceRef, reason: "exact_title" }
2062
+ };
2063
+ }
2037
2064
  function zeroChunkResult(duplicateOf) {
2038
2065
  const result = {
2039
2066
  truncated: false,
@@ -2170,11 +2197,7 @@ var IngestionService = class {
2170
2197
  const diagBuffer = new DiagnosticBuffer();
2171
2198
  const diagBase = { entityId, operation: "ingest", trigger: "call" };
2172
2199
  const pushDeduplicated = (chunkIndex, itemIndex) => {
2173
- diagBuffer.push({
2174
- ...diagBase,
2175
- code: "fact_deduplicated",
2176
- detail: { sourceRef, chunkIndex, itemIndex, reason: "exact_title" }
2177
- });
2200
+ diagBuffer.push(dedupDiagnostic(entityId, sourceRef, { chunkIndex, itemIndex }));
2178
2201
  };
2179
2202
  for (const [chunkIndex, slot] of chunkResults.entries()) {
2180
2203
  if (slot.status === "failed") {
@@ -2201,7 +2224,7 @@ var IngestionService = class {
2201
2224
  });
2202
2225
  }
2203
2226
  const orderedChunkFacts = [];
2204
- const groundingLedger = /* @__PURE__ */ new Map();
2227
+ const factLedger = /* @__PURE__ */ new Map();
2205
2228
  for (const [chunkIndex, slot] of chunkResults.entries()) {
2206
2229
  if (slot.status === "failed") continue;
2207
2230
  const keptFacts = [];
@@ -2209,7 +2232,11 @@ var IngestionService = class {
2209
2232
  const winner = winners.get(normalizeTitleKey(fact.title));
2210
2233
  if (winner?.chunkIndex !== chunkIndex || winner.k !== k) return;
2211
2234
  keptFacts.push(fact);
2212
- if (ingestGrounding) groundingLedger.set(fact, { verdict: slot.verdicts[k], chunkIndex, itemIndex: winner.itemIndex });
2235
+ factLedger.set(fact, {
2236
+ verdict: ingestGrounding ? slot.verdicts[k] : null,
2237
+ chunkIndex,
2238
+ itemIndex: winner.itemIndex
2239
+ });
2213
2240
  });
2214
2241
  orderedChunkFacts.push({ facts: keptFacts, ontology_updates: slot.ontology_updates });
2215
2242
  }
@@ -2239,7 +2266,7 @@ var IngestionService = class {
2239
2266
  try {
2240
2267
  if (failedChunks === 0) {
2241
2268
  const fullResult = await this.db.withTransactionAsync(async (tx) => {
2242
- return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer, groundingLedger);
2269
+ return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer, factLedger);
2243
2270
  });
2244
2271
  deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
2245
2272
  insertedFacts.push(...fullResult.insertedFacts);
@@ -2247,13 +2274,14 @@ var IngestionService = class {
2247
2274
  const partialResult = await this.db.withTransactionAsync(async (tx) => {
2248
2275
  const flat = [];
2249
2276
  for (const slot of orderedChunkFacts) flat.push(...slot.facts);
2250
- return await this.appendPartialFacts(entityId, sourceRef, flat, tx, diagBuffer, groundingLedger);
2277
+ return await this.appendPartialFacts(entityId, sourceRef, flat, tx, diagBuffer, factLedger);
2251
2278
  });
2252
2279
  insertedFacts.push(...partialResult.insertedDescriptors);
2253
2280
  }
2254
2281
  } catch (err) {
2255
2282
  const sqliteCode = err instanceof WikiTransactionError ? err.sqliteErrorCode : extractSqliteCode(err);
2256
2283
  if (sqliteCode !== "SQLITE_CONSTRAINT_UNIQUE") throw err;
2284
+ diagBuffer.flushOnly(this.options, LLM_PASS_DIAGNOSTIC_CODES);
2257
2285
  const canonical = await this.sourceRefIndexRepo.findActiveByEntityAndHash(entityId, sourceHash);
2258
2286
  if (canonical === null) throw err;
2259
2287
  if (onDuplicateHash === "throw" || onDuplicateHash === "ingest") {
@@ -2481,7 +2509,7 @@ var IngestionService = class {
2481
2509
  * On the partial path the caller never invokes this method; the empty
2482
2510
  * arrays stay empty.
2483
2511
  */
2484
- async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer, groundingLedger = /* @__PURE__ */ new Map()) {
2512
+ async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer, factLedger = /* @__PURE__ */ new Map()) {
2485
2513
  const deletedSourceFactIds = [];
2486
2514
  const insertedFacts = [];
2487
2515
  const nodeTrust = /* @__PURE__ */ new Map();
@@ -2528,9 +2556,9 @@ var IngestionService = class {
2528
2556
  tags: fact.tags,
2529
2557
  confidence: fact.confidence
2530
2558
  });
2531
- const grounding = groundingLedger.get(fact);
2532
- if (grounding) {
2533
- const outcome = groundingOutcome(grounding.verdict, now);
2559
+ const locator = factLedger.get(fact);
2560
+ if (locator?.verdict) {
2561
+ const outcome = groundingOutcome(locator.verdict, now);
2534
2562
  nodeTrust.set(id, outcome.trust);
2535
2563
  if (outcome.diagnostic) {
2536
2564
  diagBuffer.push({
@@ -2538,7 +2566,7 @@ var IngestionService = class {
2538
2566
  operation: "ingest",
2539
2567
  trigger: "call",
2540
2568
  code: outcome.diagnostic.code,
2541
- detail: { factId: id, sourceRef, chunkIndex: grounding.chunkIndex, itemIndex: grounding.itemIndex, reason: outcome.diagnostic.reason }
2569
+ detail: { factId: id, sourceRef, chunkIndex: locator.chunkIndex, itemIndex: locator.itemIndex, reason: outcome.diagnostic.reason }
2542
2570
  });
2543
2571
  }
2544
2572
  }
@@ -2597,7 +2625,7 @@ var IngestionService = class {
2597
2625
  * Runs INSIDE the caller's `tx`. Does not open a nested transaction.
2598
2626
  * Returns `{ inserted, skippedDuplicate }` for observability.
2599
2627
  */
2600
- async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx, diagBuffer, groundingLedger = /* @__PURE__ */ new Map()) {
2628
+ async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx, diagBuffer, factLedger = /* @__PURE__ */ new Map()) {
2601
2629
  const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
2602
2630
  const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
2603
2631
  const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
@@ -2607,28 +2635,22 @@ var IngestionService = class {
2607
2635
  const insertedDescriptors = [];
2608
2636
  for (const fact of dedupedFacts) {
2609
2637
  const normalizedTitle = normalizeTitleKey(fact.title);
2638
+ const locator = factLedger.get(fact);
2610
2639
  if (liveTitles.has(normalizedTitle)) {
2611
- diagBuffer.push({
2612
- entityId,
2613
- operation: "ingest",
2614
- trigger: "call",
2615
- code: "fact_deduplicated",
2616
- detail: { sourceRef, reason: "exact_title" }
2617
- });
2640
+ diagBuffer.push(dedupDiagnostic(entityId, sourceRef, locator));
2618
2641
  skippedDuplicate++;
2619
2642
  continue;
2620
2643
  }
2621
2644
  liveTitles.add(normalizedTitle);
2622
2645
  const id = generateId("fact_");
2623
- const grounding = groundingLedger.get(fact);
2624
- const outcome = grounding ? groundingOutcome(grounding.verdict, now) : null;
2625
- if (grounding && outcome?.diagnostic) {
2646
+ const outcome = locator?.verdict ? groundingOutcome(locator.verdict, now) : null;
2647
+ if (locator?.verdict && outcome?.diagnostic) {
2626
2648
  diagBuffer.push({
2627
2649
  entityId,
2628
2650
  operation: "ingest",
2629
2651
  trigger: "call",
2630
2652
  code: outcome.diagnostic.code,
2631
- detail: { factId: id, sourceRef, chunkIndex: grounding.chunkIndex, itemIndex: grounding.itemIndex, reason: outcome.diagnostic.reason }
2653
+ detail: { factId: id, sourceRef, chunkIndex: locator.chunkIndex, itemIndex: locator.itemIndex, reason: outcome.diagnostic.reason }
2632
2654
  });
2633
2655
  }
2634
2656
  const wikiFact = {
@@ -5641,5 +5663,5 @@ var WriteService = class {
5641
5663
  };
5642
5664
 
5643
5665
  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, edgeTripleAllowed, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveGrounding, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
5644
- //# sourceMappingURL=chunk-NWODQRSM.mjs.map
5645
- //# sourceMappingURL=chunk-NWODQRSM.mjs.map
5666
+ //# sourceMappingURL=chunk-JV2OQ5CM.mjs.map
5667
+ //# sourceMappingURL=chunk-JV2OQ5CM.mjs.map