@equationalapplications/core-llm-wiki 7.2.1 → 7.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.
- package/README.md +23 -0
- package/dist/{chunk-TTECJ3S3.mjs → chunk-IAYO63DQ.mjs} +346 -41
- package/dist/chunk-IAYO63DQ.mjs.map +1 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.d.ts +3 -5
- package/dist/index.js +388 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +47 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-DYeMd9d1.d.mts → testing-ht1qOvcD.d.mts} +98 -6
- package/dist/{testing-DYeMd9d1.d.ts → testing-ht1qOvcD.d.ts} +98 -6
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +336 -38
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-TTECJ3S3.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2768,20 +2768,27 @@ function mergeOntologyUpdates(current, updates) {
|
|
|
2768
2768
|
function validateInlineEdges(sourceType, _targetType, edges, manifest, opts) {
|
|
2769
2769
|
const strict = opts?.strict === true;
|
|
2770
2770
|
const entityId = opts?.entityId ?? "";
|
|
2771
|
+
const drops = opts?.drops;
|
|
2772
|
+
const drop = (reason, edgeType) => {
|
|
2773
|
+
drops?.push({ reason, sourceId: null, edgeType, sourceNodeType: sourceType, targetNodeType: null });
|
|
2774
|
+
};
|
|
2771
2775
|
if (!Array.isArray(edges)) {
|
|
2772
2776
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", "");
|
|
2777
|
+
drop("invalid_shape", null);
|
|
2773
2778
|
return [];
|
|
2774
2779
|
}
|
|
2775
2780
|
const valid = [];
|
|
2776
2781
|
for (const edge of edges) {
|
|
2777
2782
|
if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") {
|
|
2778
2783
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", String(edge?.edge_type ?? ""));
|
|
2784
|
+
drop("invalid_shape", typeof edge?.edge_type === "string" ? edge.edge_type : null);
|
|
2779
2785
|
continue;
|
|
2780
2786
|
}
|
|
2781
2787
|
const defs = resolveEdgeDefinitions(edge.edge_type, manifest);
|
|
2782
2788
|
const match = defs.find((d) => typeSatisfies(d.source_type, sourceType, manifest));
|
|
2783
2789
|
if (!match) {
|
|
2784
2790
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", edge.edge_type);
|
|
2791
|
+
drop("type_not_in_manifest", edge.edge_type);
|
|
2785
2792
|
continue;
|
|
2786
2793
|
}
|
|
2787
2794
|
valid.push({ edge_type: match.type, target_title: edge.target_title });
|
|
@@ -4129,6 +4136,17 @@ function validateTask(task) {
|
|
|
4129
4136
|
priority
|
|
4130
4137
|
};
|
|
4131
4138
|
}
|
|
4139
|
+
function factRejectionReason(raw) {
|
|
4140
|
+
const fact = raw;
|
|
4141
|
+
if (typeof fact?.title !== "string" || typeof fact?.body !== "string") return "invalid_shape";
|
|
4142
|
+
if (!clip(fact.title, 80)) return "missing_title";
|
|
4143
|
+
return "missing_body";
|
|
4144
|
+
}
|
|
4145
|
+
function taskRejectionReason(raw) {
|
|
4146
|
+
const task = raw;
|
|
4147
|
+
if (typeof task?.description !== "string") return "invalid_shape";
|
|
4148
|
+
return "missing_description";
|
|
4149
|
+
}
|
|
4132
4150
|
function normalizeSourceRef(value) {
|
|
4133
4151
|
if (typeof value !== "string") return null;
|
|
4134
4152
|
const cleaned = value.replace(/[^A-Za-z0-9._\- ]/g, "").trim().slice(0, 255);
|
|
@@ -4148,6 +4166,104 @@ function jaccardScore(a, b) {
|
|
|
4148
4166
|
return intersection.size / union.size;
|
|
4149
4167
|
}
|
|
4150
4168
|
|
|
4169
|
+
// src/utils/diagnostics.ts
|
|
4170
|
+
var SEVERITY = {
|
|
4171
|
+
ingest_chunk_failed: "warn",
|
|
4172
|
+
fact_rejected: "warn",
|
|
4173
|
+
task_rejected: "warn",
|
|
4174
|
+
fact_deduplicated: "info",
|
|
4175
|
+
edge_dropped: "warn",
|
|
4176
|
+
embedding_failed: "warn",
|
|
4177
|
+
hook_failed: "warn",
|
|
4178
|
+
background_job_failed: "error",
|
|
4179
|
+
heal_skipped: "warn",
|
|
4180
|
+
grounding_missing: "warn",
|
|
4181
|
+
grounding_failed: "warn",
|
|
4182
|
+
classification_low_confidence: "info",
|
|
4183
|
+
classification_invalid: "warn"
|
|
4184
|
+
};
|
|
4185
|
+
var MESSAGE = {
|
|
4186
|
+
ingest_chunk_failed: "One or more document chunks failed to parse or generate.",
|
|
4187
|
+
fact_rejected: "An extracted fact failed validation and was not written.",
|
|
4188
|
+
task_rejected: "An extracted task failed validation and was not written.",
|
|
4189
|
+
fact_deduplicated: "An extracted fact duplicated an existing fact and was not written.",
|
|
4190
|
+
edge_dropped: "An extracted edge could not be resolved against the ontology and was not written.",
|
|
4191
|
+
embedding_failed: "A fact could not be embedded.",
|
|
4192
|
+
hook_failed: "A host hook threw or rejected.",
|
|
4193
|
+
background_job_failed: "A background maintenance job failed.",
|
|
4194
|
+
heal_skipped: "Heal skipped a candidate fact.",
|
|
4195
|
+
grounding_missing: "A fact carried no qualifying evidence and was stored as a draft.",
|
|
4196
|
+
grounding_failed: "A fact carried evidence not found in its source and was stored as a draft.",
|
|
4197
|
+
classification_low_confidence: "A classifier answer was below the confidence threshold and was not applied.",
|
|
4198
|
+
classification_invalid: "A classifier answer was invalid and was not applied."
|
|
4199
|
+
};
|
|
4200
|
+
var warnedNonFunction = /* @__PURE__ */ new WeakSet();
|
|
4201
|
+
function copyDetail(detail) {
|
|
4202
|
+
const copy = { ...detail };
|
|
4203
|
+
if (detail.chunkIndexes) copy.chunkIndexes = detail.chunkIndexes.slice();
|
|
4204
|
+
return copy;
|
|
4205
|
+
}
|
|
4206
|
+
function emitDiagnostic(options, input) {
|
|
4207
|
+
const hook = options.onDiagnostic;
|
|
4208
|
+
if (hook === void 0 || hook === null) return;
|
|
4209
|
+
if (typeof hook !== "function") {
|
|
4210
|
+
if (!warnedNonFunction.has(options)) {
|
|
4211
|
+
warnedNonFunction.add(options);
|
|
4212
|
+
console.warn("[WikiMemory] onDiagnostic is not a function; diagnostics are disabled.");
|
|
4213
|
+
}
|
|
4214
|
+
return;
|
|
4215
|
+
}
|
|
4216
|
+
const diagnostic = {
|
|
4217
|
+
code: input.code,
|
|
4218
|
+
severity: SEVERITY[input.code],
|
|
4219
|
+
operation: input.operation,
|
|
4220
|
+
trigger: input.trigger,
|
|
4221
|
+
entityId: input.entityId,
|
|
4222
|
+
at: Date.now(),
|
|
4223
|
+
message: MESSAGE[input.code],
|
|
4224
|
+
...input.detail ? { detail: copyDetail(input.detail) } : {}
|
|
4225
|
+
};
|
|
4226
|
+
try {
|
|
4227
|
+
const returned = hook(diagnostic);
|
|
4228
|
+
if (returned !== void 0) {
|
|
4229
|
+
Promise.resolve(returned).catch((err) => {
|
|
4230
|
+
console.warn("[WikiMemory] onDiagnostic hook rejected:", err);
|
|
4231
|
+
});
|
|
4232
|
+
}
|
|
4233
|
+
} catch (err) {
|
|
4234
|
+
console.warn("[WikiMemory] onDiagnostic hook threw:", err);
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
var DiagnosticBuffer = class {
|
|
4238
|
+
constructor() {
|
|
4239
|
+
this.items = [];
|
|
4240
|
+
}
|
|
4241
|
+
push(input) {
|
|
4242
|
+
this.items.push(input);
|
|
4243
|
+
}
|
|
4244
|
+
get size() {
|
|
4245
|
+
return this.items.length;
|
|
4246
|
+
}
|
|
4247
|
+
flush(options) {
|
|
4248
|
+
const pending = this.items;
|
|
4249
|
+
this.items = [];
|
|
4250
|
+
for (const input of pending) emitDiagnostic(options, input);
|
|
4251
|
+
}
|
|
4252
|
+
discard() {
|
|
4253
|
+
this.items = [];
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
function edgeDropDiagnostic(drop, ctx) {
|
|
4257
|
+
const detail = { reason: drop.reason };
|
|
4258
|
+
const factId = ctx.factId ?? drop.sourceId;
|
|
4259
|
+
if (factId) detail.factId = factId;
|
|
4260
|
+
if (drop.edgeType) detail.edgeType = drop.edgeType;
|
|
4261
|
+
if (drop.sourceNodeType) detail.sourceNodeType = drop.sourceNodeType;
|
|
4262
|
+
if (drop.targetNodeType) detail.targetNodeType = drop.targetNodeType;
|
|
4263
|
+
if (ctx.sourceRef) detail.sourceRef = ctx.sourceRef;
|
|
4264
|
+
return { code: "edge_dropped", operation: ctx.operation, trigger: ctx.trigger, entityId: ctx.entityId, detail };
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4151
4267
|
// src/prompts.ts
|
|
4152
4268
|
var LIBRARIAN_SYSTEM_PROMPT = `You are a knowledge extraction agent. Your job is to analyze recent episodic events and extract stable facts and actionable tasks about the user or entity.
|
|
4153
4269
|
Return ONLY a valid JSON object matching this schema:
|
|
@@ -4435,9 +4551,24 @@ var IngestionService = class {
|
|
|
4435
4551
|
try {
|
|
4436
4552
|
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
4437
4553
|
const result2 = parseJsonResponse(responseText);
|
|
4554
|
+
const rawFacts = Array.isArray(result2.facts) ? result2.facts : [];
|
|
4555
|
+
const facts = [];
|
|
4556
|
+
const itemIndexes = [];
|
|
4557
|
+
const rejected = [];
|
|
4558
|
+
rawFacts.forEach((raw, itemIndex) => {
|
|
4559
|
+
const valid = validateFact(raw);
|
|
4560
|
+
if (valid) {
|
|
4561
|
+
facts.push(valid);
|
|
4562
|
+
itemIndexes.push(itemIndex);
|
|
4563
|
+
} else {
|
|
4564
|
+
rejected.push({ itemIndex, reason: factRejectionReason(raw) });
|
|
4565
|
+
}
|
|
4566
|
+
});
|
|
4438
4567
|
return {
|
|
4439
4568
|
status: "ok",
|
|
4440
|
-
facts
|
|
4569
|
+
facts,
|
|
4570
|
+
itemIndexes,
|
|
4571
|
+
rejected,
|
|
4441
4572
|
ontology_updates: result2.ontology_updates
|
|
4442
4573
|
};
|
|
4443
4574
|
} catch (e) {
|
|
@@ -4482,23 +4613,48 @@ var IngestionService = class {
|
|
|
4482
4613
|
const failures = [];
|
|
4483
4614
|
const seen = /* @__PURE__ */ new Set();
|
|
4484
4615
|
const orderedChunkFacts = [];
|
|
4485
|
-
|
|
4616
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
4617
|
+
const diagBase = { entityId, operation: "ingest", trigger: "call" };
|
|
4618
|
+
for (const [chunkIndex, slot] of chunkResults.entries()) {
|
|
4486
4619
|
if (slot.status === "failed") {
|
|
4487
4620
|
failedChunks++;
|
|
4488
4621
|
failures.push(slot.error);
|
|
4489
4622
|
continue;
|
|
4490
4623
|
}
|
|
4491
4624
|
ingestedChunks++;
|
|
4625
|
+
for (const r of slot.rejected) {
|
|
4626
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { sourceRef, chunkIndex, itemIndex: r.itemIndex, reason: r.reason } });
|
|
4627
|
+
}
|
|
4492
4628
|
const dedupedFacts = [];
|
|
4493
|
-
|
|
4629
|
+
slot.facts.forEach((fact, k) => {
|
|
4494
4630
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
4495
4631
|
if (!seen.has(normalizedTitle)) {
|
|
4496
4632
|
seen.add(normalizedTitle);
|
|
4497
4633
|
dedupedFacts.push(fact);
|
|
4634
|
+
} else {
|
|
4635
|
+
diagBuffer.push({
|
|
4636
|
+
...diagBase,
|
|
4637
|
+
code: "fact_deduplicated",
|
|
4638
|
+
detail: { sourceRef, chunkIndex, itemIndex: slot.itemIndexes[k], reason: "exact_title" }
|
|
4639
|
+
});
|
|
4498
4640
|
}
|
|
4499
|
-
}
|
|
4641
|
+
});
|
|
4500
4642
|
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: slot.ontology_updates });
|
|
4501
4643
|
}
|
|
4644
|
+
for (const reason of ["parse", "llm"]) {
|
|
4645
|
+
const ofReason = failures.filter((f) => f.source === reason);
|
|
4646
|
+
if (ofReason.length === 0) continue;
|
|
4647
|
+
diagBuffer.push({
|
|
4648
|
+
...diagBase,
|
|
4649
|
+
code: "ingest_chunk_failed",
|
|
4650
|
+
detail: {
|
|
4651
|
+
sourceRef,
|
|
4652
|
+
reason,
|
|
4653
|
+
count: ofReason.length,
|
|
4654
|
+
chunkIndexes: ofReason.map((f) => f.chunkIndex).sort((a, b) => a - b).slice(0, 20)
|
|
4655
|
+
}
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4502
4658
|
if (failedChunks === chunks.length) {
|
|
4503
4659
|
throw new WikiIngestEmptyError({
|
|
4504
4660
|
parseFailures: failures,
|
|
@@ -4511,7 +4667,7 @@ var IngestionService = class {
|
|
|
4511
4667
|
try {
|
|
4512
4668
|
if (failedChunks === 0) {
|
|
4513
4669
|
const fullResult = await this.db.withTransactionAsync(async (tx) => {
|
|
4514
|
-
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx);
|
|
4670
|
+
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer);
|
|
4515
4671
|
});
|
|
4516
4672
|
deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
|
|
4517
4673
|
insertedFacts.push(...fullResult.insertedFacts);
|
|
@@ -4519,7 +4675,7 @@ var IngestionService = class {
|
|
|
4519
4675
|
const partialResult = await this.db.withTransactionAsync(async (tx) => {
|
|
4520
4676
|
const flat = [];
|
|
4521
4677
|
for (const slot of orderedChunkFacts) flat.push(...slot.facts);
|
|
4522
|
-
return await this.appendPartialFacts(entityId, sourceRef, flat, tx);
|
|
4678
|
+
return await this.appendPartialFacts(entityId, sourceRef, flat, tx, diagBuffer);
|
|
4523
4679
|
});
|
|
4524
4680
|
insertedFacts.push(...partialResult.insertedDescriptors);
|
|
4525
4681
|
}
|
|
@@ -4533,6 +4689,7 @@ var IngestionService = class {
|
|
|
4533
4689
|
}
|
|
4534
4690
|
return zeroChunkResult(canonical);
|
|
4535
4691
|
}
|
|
4692
|
+
diagBuffer.flush(this.options);
|
|
4536
4693
|
await this.searchService.sync(entityId);
|
|
4537
4694
|
if (failedChunks === 0) {
|
|
4538
4695
|
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
@@ -4541,11 +4698,18 @@ var IngestionService = class {
|
|
|
4541
4698
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
4542
4699
|
} catch (hookErr) {
|
|
4543
4700
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during ingest for ${factId}:`, hookErr);
|
|
4701
|
+
emitDiagnostic(this.options, {
|
|
4702
|
+
entityId,
|
|
4703
|
+
operation: "ingest",
|
|
4704
|
+
trigger: "call",
|
|
4705
|
+
code: "hook_failed",
|
|
4706
|
+
detail: { factId, reason: "on_embedding_persisted" }
|
|
4707
|
+
});
|
|
4544
4708
|
}
|
|
4545
4709
|
}
|
|
4546
4710
|
}
|
|
4547
4711
|
for (const fact of insertedFacts) {
|
|
4548
|
-
await this.embeddingService.embedFact(fact);
|
|
4712
|
+
await this.embeddingService.embedFact(fact, { operation: "ingest", trigger: "call" });
|
|
4549
4713
|
}
|
|
4550
4714
|
this.searchService.evictCache(entityId);
|
|
4551
4715
|
const result = {
|
|
@@ -4691,6 +4855,19 @@ var IngestionService = class {
|
|
|
4691
4855
|
const match = candidates[0];
|
|
4692
4856
|
if (!match) {
|
|
4693
4857
|
if (strictEffective) throw new WikiStrictOntologyViolation(entityId, "edge", edge.type);
|
|
4858
|
+
opts?.diag?.buffer.push({
|
|
4859
|
+
entityId,
|
|
4860
|
+
operation: opts.diag.operation,
|
|
4861
|
+
trigger: "call",
|
|
4862
|
+
code: "edge_dropped",
|
|
4863
|
+
detail: {
|
|
4864
|
+
reason: "manifest_violation",
|
|
4865
|
+
factId: edge.sourceId,
|
|
4866
|
+
edgeType: edge.type,
|
|
4867
|
+
...sourceType ? { sourceNodeType: sourceType } : {},
|
|
4868
|
+
sourceRef: params.sourceRef
|
|
4869
|
+
}
|
|
4870
|
+
});
|
|
4694
4871
|
continue;
|
|
4695
4872
|
}
|
|
4696
4873
|
validEdges.push({
|
|
@@ -4731,7 +4908,7 @@ var IngestionService = class {
|
|
|
4731
4908
|
* On the partial path the caller never invokes this method; the empty
|
|
4732
4909
|
* arrays stay empty.
|
|
4733
4910
|
*/
|
|
4734
|
-
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx) {
|
|
4911
|
+
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer) {
|
|
4735
4912
|
const deletedSourceFactIds = [];
|
|
4736
4913
|
const insertedFacts = [];
|
|
4737
4914
|
deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
|
|
@@ -4756,8 +4933,12 @@ var IngestionService = class {
|
|
|
4756
4933
|
}
|
|
4757
4934
|
for (const fact of facts) {
|
|
4758
4935
|
const ontologyFact = fact;
|
|
4759
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
4760
4936
|
const id = generateId("fact_");
|
|
4937
|
+
const validationDrops = [];
|
|
4938
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
4939
|
+
for (const drop of validationDrops) {
|
|
4940
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef, factId: id }));
|
|
4941
|
+
}
|
|
4761
4942
|
hostNodes.push({
|
|
4762
4943
|
id,
|
|
4763
4944
|
type: ontologyFact.okf_type ?? "",
|
|
@@ -4782,6 +4963,7 @@ var IngestionService = class {
|
|
|
4782
4963
|
}
|
|
4783
4964
|
const hostEdges = [];
|
|
4784
4965
|
for (const req of rawEdgeRequests) {
|
|
4966
|
+
const resolveDrops = [];
|
|
4785
4967
|
const resolved = this.ontologyService?.resolveEdges(
|
|
4786
4968
|
entityId,
|
|
4787
4969
|
req.sourceId,
|
|
@@ -4789,8 +4971,12 @@ var IngestionService = class {
|
|
|
4789
4971
|
req.edges,
|
|
4790
4972
|
manifest,
|
|
4791
4973
|
titleIndex,
|
|
4792
|
-
now
|
|
4974
|
+
now,
|
|
4975
|
+
resolveDrops
|
|
4793
4976
|
) ?? [];
|
|
4977
|
+
for (const drop of resolveDrops) {
|
|
4978
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef }));
|
|
4979
|
+
}
|
|
4794
4980
|
for (const e of resolved) {
|
|
4795
4981
|
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
4796
4982
|
}
|
|
@@ -4799,7 +4985,7 @@ var IngestionService = class {
|
|
|
4799
4985
|
entityId,
|
|
4800
4986
|
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
4801
4987
|
tx,
|
|
4802
|
-
{ strict: false }
|
|
4988
|
+
{ strict: false, diag: { buffer: diagBuffer, operation: "ingest" } }
|
|
4803
4989
|
);
|
|
4804
4990
|
return { deletedSourceFactIds, insertedFacts };
|
|
4805
4991
|
}
|
|
@@ -4823,7 +5009,7 @@ var IngestionService = class {
|
|
|
4823
5009
|
* Runs INSIDE the caller's `tx`. Does not open a nested transaction.
|
|
4824
5010
|
* Returns `{ inserted, skippedDuplicate }` for observability.
|
|
4825
5011
|
*/
|
|
4826
|
-
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx) {
|
|
5012
|
+
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx, diagBuffer) {
|
|
4827
5013
|
const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
|
|
4828
5014
|
const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
|
|
4829
5015
|
const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
|
|
@@ -4834,6 +5020,13 @@ var IngestionService = class {
|
|
|
4834
5020
|
for (const fact of dedupedFacts) {
|
|
4835
5021
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
4836
5022
|
if (liveTitles.has(normalizedTitle)) {
|
|
5023
|
+
diagBuffer.push({
|
|
5024
|
+
entityId,
|
|
5025
|
+
operation: "ingest",
|
|
5026
|
+
trigger: "call",
|
|
5027
|
+
code: "fact_deduplicated",
|
|
5028
|
+
detail: { sourceRef, reason: "exact_title" }
|
|
5029
|
+
});
|
|
4837
5030
|
skippedDuplicate++;
|
|
4838
5031
|
continue;
|
|
4839
5032
|
}
|
|
@@ -5234,7 +5427,7 @@ var MaintenanceService = class {
|
|
|
5234
5427
|
permanentlyFailed++;
|
|
5235
5428
|
continue;
|
|
5236
5429
|
}
|
|
5237
|
-
const result = await this.embeddingService.tryEmbedFact(row);
|
|
5430
|
+
const result = await this.embeddingService.tryEmbedFact(row, { operation: "reembed", trigger: "call" });
|
|
5238
5431
|
if (result.ok) embedded++;
|
|
5239
5432
|
else failed++;
|
|
5240
5433
|
}
|
|
@@ -5355,7 +5548,7 @@ var MaintenanceService = class {
|
|
|
5355
5548
|
return { deleted: { entries, tasks: 0 } };
|
|
5356
5549
|
}
|
|
5357
5550
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
5358
|
-
async doRunLibrarian(entityId, promptOverride) {
|
|
5551
|
+
async doRunLibrarian(entityId, promptOverride, trigger = "call") {
|
|
5359
5552
|
const events = await this.eventRepo.getRecent(entityId, 50);
|
|
5360
5553
|
const currentFactsRows = await this.entryRepo.findRecentByEntityId(entityId, 100);
|
|
5361
5554
|
const currentFacts = currentFactsRows.map((f) => {
|
|
@@ -5377,8 +5570,25 @@ var MaintenanceService = class {
|
|
|
5377
5570
|
const facts = Array.isArray(result.facts) ? result.facts : [];
|
|
5378
5571
|
const tasks = Array.isArray(result.tasks) ? result.tasks : [];
|
|
5379
5572
|
const ontologyUpdates = result.ontology_updates;
|
|
5380
|
-
const
|
|
5381
|
-
const
|
|
5573
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
5574
|
+
const diagBase = { entityId, operation: "librarian", trigger };
|
|
5575
|
+
const validFacts = [];
|
|
5576
|
+
const validFactItemIndexes = [];
|
|
5577
|
+
facts.forEach((raw, itemIndex) => {
|
|
5578
|
+
const valid = validateFact(raw);
|
|
5579
|
+
if (valid) {
|
|
5580
|
+
validFacts.push(valid);
|
|
5581
|
+
validFactItemIndexes.push(itemIndex);
|
|
5582
|
+
} else {
|
|
5583
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
5584
|
+
}
|
|
5585
|
+
});
|
|
5586
|
+
const validTasks = [];
|
|
5587
|
+
tasks.forEach((raw, itemIndex) => {
|
|
5588
|
+
const valid = validateTask(raw);
|
|
5589
|
+
if (valid) validTasks.push(valid);
|
|
5590
|
+
else diagBuffer.push({ ...diagBase, code: "task_rejected", detail: { itemIndex, reason: taskRejectionReason(raw) } });
|
|
5591
|
+
});
|
|
5382
5592
|
const now = Date.now();
|
|
5383
5593
|
const insertedFacts = [];
|
|
5384
5594
|
await this.db.withTransactionAsync(async (tx) => {
|
|
@@ -5395,7 +5605,7 @@ var MaintenanceService = class {
|
|
|
5395
5605
|
}
|
|
5396
5606
|
const factsForDedupe = await this.entryRepo.findRecentByEntityId(entityId, 100, tx);
|
|
5397
5607
|
const pendingEdges = [];
|
|
5398
|
-
for (const fact of validFacts) {
|
|
5608
|
+
for (const [k, fact] of validFacts.entries()) {
|
|
5399
5609
|
const newTokens = titleTokens(fact.title);
|
|
5400
5610
|
let skip = false;
|
|
5401
5611
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -5410,10 +5620,15 @@ var MaintenanceService = class {
|
|
|
5410
5620
|
}
|
|
5411
5621
|
}
|
|
5412
5622
|
}
|
|
5413
|
-
if (skip)
|
|
5623
|
+
if (skip) {
|
|
5624
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
5625
|
+
continue;
|
|
5626
|
+
}
|
|
5414
5627
|
const ontologyFact = fact;
|
|
5415
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
5416
5628
|
const id = generateId("fact_");
|
|
5629
|
+
const validationDrops = [];
|
|
5630
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
5631
|
+
for (const drop of validationDrops) diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: id }));
|
|
5417
5632
|
const factObj = {
|
|
5418
5633
|
id,
|
|
5419
5634
|
entity_id: entityId,
|
|
@@ -5440,6 +5655,7 @@ var MaintenanceService = class {
|
|
|
5440
5655
|
}
|
|
5441
5656
|
}
|
|
5442
5657
|
for (const item of pendingEdges) {
|
|
5658
|
+
const resolveDrops = [];
|
|
5443
5659
|
await this.ontologyService?.resolveAndPersistEdges(
|
|
5444
5660
|
entityId,
|
|
5445
5661
|
item.sourceId,
|
|
@@ -5448,8 +5664,10 @@ var MaintenanceService = class {
|
|
|
5448
5664
|
manifest,
|
|
5449
5665
|
titleIndex,
|
|
5450
5666
|
tx,
|
|
5451
|
-
now
|
|
5667
|
+
now,
|
|
5668
|
+
resolveDrops
|
|
5452
5669
|
);
|
|
5670
|
+
for (const drop of resolveDrops) diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
5453
5671
|
}
|
|
5454
5672
|
for (const task of validTasks) {
|
|
5455
5673
|
const id = generateId("task_");
|
|
@@ -5467,9 +5685,10 @@ var MaintenanceService = class {
|
|
|
5467
5685
|
await this.taskRepo.upsert(taskObj, tx);
|
|
5468
5686
|
}
|
|
5469
5687
|
});
|
|
5688
|
+
diagBuffer.flush(this.options);
|
|
5470
5689
|
await this.searchService.sync(entityId);
|
|
5471
5690
|
for (const fact of insertedFacts) {
|
|
5472
|
-
await this.embeddingService.embedFact(fact);
|
|
5691
|
+
await this.embeddingService.embedFact(fact, { operation: "librarian", trigger });
|
|
5473
5692
|
}
|
|
5474
5693
|
this.searchService.evictCache(entityId);
|
|
5475
5694
|
}
|
|
@@ -5483,6 +5702,8 @@ var MaintenanceService = class {
|
|
|
5483
5702
|
async doRunHeal(entityId, options) {
|
|
5484
5703
|
const promptOverride = options?.promptOverride;
|
|
5485
5704
|
const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
|
|
5705
|
+
const trigger = options?.trigger ?? "call";
|
|
5706
|
+
const diagBase = { entityId, operation: "heal", trigger };
|
|
5486
5707
|
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
5487
5708
|
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
5488
5709
|
}
|
|
@@ -5518,6 +5739,7 @@ var MaintenanceService = class {
|
|
|
5518
5739
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
5519
5740
|
} catch (hookErr) {
|
|
5520
5741
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
|
|
5742
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
5521
5743
|
}
|
|
5522
5744
|
}
|
|
5523
5745
|
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
@@ -5590,22 +5812,38 @@ var MaintenanceService = class {
|
|
|
5590
5812
|
const safeDowngradedSet = /* @__PURE__ */ new Set();
|
|
5591
5813
|
const safeDeletedSet = /* @__PURE__ */ new Set();
|
|
5592
5814
|
const newFacts = [];
|
|
5815
|
+
const newFactItemIndexes = [];
|
|
5593
5816
|
for (const batchResult of outcome.results) {
|
|
5594
5817
|
const mutableIds = new Set(batchResult.batch.map((f) => f.id));
|
|
5595
5818
|
for (const id of batchResult.downgraded) if (mutableIds.has(id)) safeDowngradedSet.add(id);
|
|
5596
5819
|
for (const id of batchResult.deleted) if (mutableIds.has(id)) safeDeletedSet.add(id);
|
|
5597
|
-
newFacts.
|
|
5820
|
+
batchResult.newFacts.forEach((raw, itemIndex) => {
|
|
5821
|
+
newFacts.push(raw);
|
|
5822
|
+
newFactItemIndexes.push(itemIndex);
|
|
5823
|
+
});
|
|
5598
5824
|
}
|
|
5599
5825
|
const safeDowngraded = Array.from(safeDowngradedSet);
|
|
5600
5826
|
const safeDeleted = Array.from(safeDeletedSet);
|
|
5601
|
-
const
|
|
5827
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
5828
|
+
const validNewFacts = [];
|
|
5829
|
+
const validNewFactItemIndexes = [];
|
|
5830
|
+
newFacts.forEach((raw, k) => {
|
|
5831
|
+
const itemIndex = newFactItemIndexes[k];
|
|
5832
|
+
const valid = validateFact(raw);
|
|
5833
|
+
if (valid) {
|
|
5834
|
+
validNewFacts.push(valid);
|
|
5835
|
+
validNewFactItemIndexes.push(itemIndex);
|
|
5836
|
+
} else {
|
|
5837
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
5838
|
+
}
|
|
5839
|
+
});
|
|
5602
5840
|
const insertedFacts = [];
|
|
5603
5841
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
5604
5842
|
await this.db.withTransactionAsync(async (tx) => {
|
|
5605
5843
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
5606
5844
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
5607
5845
|
const healFactsForDedupe = await this.entryRepo.findInferredTitlesByEntityId(entityId, tx);
|
|
5608
|
-
for (const fact of validNewFacts) {
|
|
5846
|
+
for (const [k, fact] of validNewFacts.entries()) {
|
|
5609
5847
|
const newTokens = titleTokens(fact.title);
|
|
5610
5848
|
let skip = false;
|
|
5611
5849
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -5619,7 +5857,10 @@ var MaintenanceService = class {
|
|
|
5619
5857
|
}
|
|
5620
5858
|
}
|
|
5621
5859
|
}
|
|
5622
|
-
if (skip)
|
|
5860
|
+
if (skip) {
|
|
5861
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validNewFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
5862
|
+
continue;
|
|
5863
|
+
}
|
|
5623
5864
|
const id = generateId("fact_");
|
|
5624
5865
|
const factObj = {
|
|
5625
5866
|
id,
|
|
@@ -5654,16 +5895,21 @@ var MaintenanceService = class {
|
|
|
5654
5895
|
tx
|
|
5655
5896
|
);
|
|
5656
5897
|
});
|
|
5898
|
+
for (const { item, reason } of outcome.skipped) {
|
|
5899
|
+
diagBuffer.push({ ...diagBase, code: "heal_skipped", detail: { factId: item.id, reason } });
|
|
5900
|
+
}
|
|
5901
|
+
diagBuffer.flush(this.options);
|
|
5657
5902
|
await this.searchService.sync(entityId);
|
|
5658
5903
|
for (const factId of uniqueDeletedFactIds) {
|
|
5659
5904
|
try {
|
|
5660
5905
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
5661
5906
|
} catch (hookErr) {
|
|
5662
5907
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal for ${factId}:`, hookErr);
|
|
5908
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
5663
5909
|
}
|
|
5664
5910
|
}
|
|
5665
5911
|
for (const fact of insertedFacts) {
|
|
5666
|
-
await this.embeddingService.embedFact(fact);
|
|
5912
|
+
await this.embeddingService.embedFact(fact, { operation: "heal", trigger });
|
|
5667
5913
|
}
|
|
5668
5914
|
this.searchService.evictCache(entityId);
|
|
5669
5915
|
const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
|
|
@@ -5803,6 +6049,8 @@ var MaintenanceService = class {
|
|
|
5803
6049
|
let failedValidation = 0;
|
|
5804
6050
|
let edgesAdded = 0;
|
|
5805
6051
|
let abortedOntologyOff = false;
|
|
6052
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
6053
|
+
const diagBase = { entityId, operation: "ontologyBackfill", trigger: "call" };
|
|
5806
6054
|
await this.db.withTransactionAsync(async (tx) => {
|
|
5807
6055
|
let { mode: txMode, manifest } = await ontologyService.getEffectiveState(entityId, tx);
|
|
5808
6056
|
if (txMode === "off") {
|
|
@@ -5827,6 +6075,7 @@ var MaintenanceService = class {
|
|
|
5827
6075
|
continue;
|
|
5828
6076
|
}
|
|
5829
6077
|
applied.add(fact.id);
|
|
6078
|
+
const validationDrops = [];
|
|
5830
6079
|
const normalized = ontologyService.validateAndNormalizeFact(
|
|
5831
6080
|
{
|
|
5832
6081
|
title: fact.title,
|
|
@@ -5837,8 +6086,11 @@ var MaintenanceService = class {
|
|
|
5837
6086
|
edges: classification.edges
|
|
5838
6087
|
},
|
|
5839
6088
|
manifest,
|
|
5840
|
-
{ strict: false }
|
|
6089
|
+
{ strict: false, drops: validationDrops }
|
|
5841
6090
|
);
|
|
6091
|
+
for (const drop of validationDrops) {
|
|
6092
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: fact.id }));
|
|
6093
|
+
}
|
|
5842
6094
|
if (!normalized.okf_type) {
|
|
5843
6095
|
failedValidation++;
|
|
5844
6096
|
continue;
|
|
@@ -5852,7 +6104,8 @@ var MaintenanceService = class {
|
|
|
5852
6104
|
}
|
|
5853
6105
|
}
|
|
5854
6106
|
for (const item of pendingEdges) {
|
|
5855
|
-
|
|
6107
|
+
const resolveDrops = [];
|
|
6108
|
+
const added = await ontologyService.resolveAndPersistEdges(
|
|
5856
6109
|
entityId,
|
|
5857
6110
|
item.sourceId,
|
|
5858
6111
|
item.sourceType,
|
|
@@ -5860,11 +6113,17 @@ var MaintenanceService = class {
|
|
|
5860
6113
|
manifest,
|
|
5861
6114
|
titleIndex,
|
|
5862
6115
|
tx,
|
|
5863
|
-
now
|
|
6116
|
+
now,
|
|
6117
|
+
resolveDrops
|
|
5864
6118
|
);
|
|
6119
|
+
edgesAdded += added;
|
|
6120
|
+
for (const drop of resolveDrops) {
|
|
6121
|
+
diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
6122
|
+
}
|
|
5865
6123
|
}
|
|
5866
6124
|
await this.entryRepo.markOntologyChecked(batch.map((f) => f.id), entityId, now, tx);
|
|
5867
6125
|
});
|
|
6126
|
+
diagBuffer.flush(this.options);
|
|
5868
6127
|
return { typed, failedValidation, edgesAdded, abortedOntologyOff };
|
|
5869
6128
|
}
|
|
5870
6129
|
_validatePruneDuration(value, name) {
|
|
@@ -6266,7 +6525,7 @@ var ImportExportService = class {
|
|
|
6266
6525
|
title: clipped?.title ?? fact.title,
|
|
6267
6526
|
body: clipped?.body ?? fact.body,
|
|
6268
6527
|
tags: Array.isArray(fact.tags) || typeof fact.tags === "string" ? fact.tags : []
|
|
6269
|
-
});
|
|
6528
|
+
}, { operation: "importDump", trigger: "call" });
|
|
6270
6529
|
if (!embedded) {
|
|
6271
6530
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, fact.id, null);
|
|
6272
6531
|
}
|
|
@@ -6291,6 +6550,11 @@ var ImportExportService = class {
|
|
|
6291
6550
|
`[WikiMemory] onEmbeddingPersisted hook failed for preserved-blob fact ${fact.id}:`,
|
|
6292
6551
|
hookErr
|
|
6293
6552
|
);
|
|
6553
|
+
this.embeddingService.reportHookFailed(
|
|
6554
|
+
{ operation: "importDump", trigger: "call" },
|
|
6555
|
+
entityId,
|
|
6556
|
+
fact.id
|
|
6557
|
+
);
|
|
6294
6558
|
}
|
|
6295
6559
|
}
|
|
6296
6560
|
}
|
|
@@ -6307,6 +6571,11 @@ var ImportExportService = class {
|
|
|
6307
6571
|
`[WikiMemory] onEmbeddingPersisted(vector=null) hook failed for soft-deleted fact ${factId}:`,
|
|
6308
6572
|
hookErr
|
|
6309
6573
|
);
|
|
6574
|
+
this.embeddingService.reportHookFailed(
|
|
6575
|
+
{ operation: "importDump", trigger: "call" },
|
|
6576
|
+
entityId,
|
|
6577
|
+
factId
|
|
6578
|
+
);
|
|
6310
6579
|
}
|
|
6311
6580
|
}
|
|
6312
6581
|
}
|
|
@@ -6469,7 +6738,7 @@ var EmbeddingService = class {
|
|
|
6469
6738
|
});
|
|
6470
6739
|
}
|
|
6471
6740
|
}
|
|
6472
|
-
async tryEmbedFact(fact) {
|
|
6741
|
+
async tryEmbedFact(fact, ctx) {
|
|
6473
6742
|
const embedFn = this.options.llmProvider.embed;
|
|
6474
6743
|
if (typeof embedFn !== "function") return { ok: false, kind: "no_provider" };
|
|
6475
6744
|
let tagsStr;
|
|
@@ -6491,6 +6760,7 @@ var EmbeddingService = class {
|
|
|
6491
6760
|
const vector = await embedFn(text);
|
|
6492
6761
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
6493
6762
|
console.warn(`[WikiMemory] embedFact: embed() returned an invalid vector for ${fact.id}; skipping.`);
|
|
6763
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "invalid_vector");
|
|
6494
6764
|
await this.markFailure(fact.id, "invalid_vector");
|
|
6495
6765
|
return { ok: false, kind: "invalid_vector" };
|
|
6496
6766
|
}
|
|
@@ -6504,11 +6774,13 @@ var EmbeddingService = class {
|
|
|
6504
6774
|
}
|
|
6505
6775
|
if (hasNonFinite) {
|
|
6506
6776
|
console.warn(`[WikiMemory] embedFact: embed() returned values that overflow float32 for ${fact.id}; skipping.`);
|
|
6777
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "float32_overflow");
|
|
6507
6778
|
await this.markFailure(fact.id, "float32_overflow");
|
|
6508
6779
|
return { ok: false, kind: "float32_overflow" };
|
|
6509
6780
|
}
|
|
6510
6781
|
} catch (err) {
|
|
6511
6782
|
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
6783
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "embed_threw");
|
|
6512
6784
|
await this.markFailure(fact.id, "provider_error");
|
|
6513
6785
|
return { ok: false, kind: "provider_error" };
|
|
6514
6786
|
}
|
|
@@ -6518,15 +6790,31 @@ var EmbeddingService = class {
|
|
|
6518
6790
|
await this.entryRepo.updateEmbeddingBlob(fact.id, blob);
|
|
6519
6791
|
} catch (err) {
|
|
6520
6792
|
console.warn(`[WikiMemory] embedFact: persisting embedding failed for ${fact.id}:`, err);
|
|
6793
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "persist_failed");
|
|
6521
6794
|
return { ok: false, kind: "storage_error" };
|
|
6522
6795
|
}
|
|
6523
6796
|
try {
|
|
6524
6797
|
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
6525
6798
|
} catch (hookErr) {
|
|
6526
6799
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
6800
|
+
this.reportEmbed(ctx, fact, "hook_failed", "on_embedding_persisted");
|
|
6527
6801
|
}
|
|
6528
6802
|
return { ok: true, dimension: float32Vector.length };
|
|
6529
6803
|
}
|
|
6804
|
+
reportEmbed(ctx, fact, code, reason) {
|
|
6805
|
+
if (!ctx) return;
|
|
6806
|
+
emitDiagnostic(this.options, {
|
|
6807
|
+
code,
|
|
6808
|
+
operation: ctx.operation,
|
|
6809
|
+
trigger: ctx.trigger,
|
|
6810
|
+
entityId: fact.entity_id,
|
|
6811
|
+
detail: { factId: fact.id, reason }
|
|
6812
|
+
});
|
|
6813
|
+
}
|
|
6814
|
+
/** For callers that invoke `notifyEmbeddingPersisted` directly and catch its failure. */
|
|
6815
|
+
reportHookFailed(ctx, entityId, factId) {
|
|
6816
|
+
this.reportEmbed(ctx, { id: factId, entity_id: entityId }, "hook_failed", "on_embedding_persisted");
|
|
6817
|
+
}
|
|
6530
6818
|
/** Marker writes must never fail the caller. Only marker-eligible kinds reach here. */
|
|
6531
6819
|
async markFailure(id, kind) {
|
|
6532
6820
|
try {
|
|
@@ -6540,8 +6828,8 @@ var EmbeddingService = class {
|
|
|
6540
6828
|
* ImportExportService branch on `if (!embedded)`; a discriminated result
|
|
6541
6829
|
* object is always truthy and would silently disable that branch.
|
|
6542
6830
|
*/
|
|
6543
|
-
async embedFact(fact) {
|
|
6544
|
-
const result = await this.tryEmbedFact(fact);
|
|
6831
|
+
async embedFact(fact, ctx) {
|
|
6832
|
+
const result = await this.tryEmbedFact(fact, ctx);
|
|
6545
6833
|
return result.ok;
|
|
6546
6834
|
}
|
|
6547
6835
|
async notifyEmbeddingPersisted(entityId, factId, vector) {
|
|
@@ -7341,7 +7629,11 @@ var WriteService = class {
|
|
|
7341
7629
|
if (shouldRunLibrarian) {
|
|
7342
7630
|
try {
|
|
7343
7631
|
this.jobManager.acquireLock("librarian", entityId);
|
|
7344
|
-
|
|
7632
|
+
const stage = { job: "librarian" };
|
|
7633
|
+
this.runLibrarianThenMaybeHeal(entityId, librarianCount, prevMemoryCheckpoint, stage).catch((err) => {
|
|
7634
|
+
console.error(err);
|
|
7635
|
+
this.reportBackgroundFailure(entityId, stage.job);
|
|
7636
|
+
}).finally(() => {
|
|
7345
7637
|
this.jobManager.releaseLock("librarian", entityId);
|
|
7346
7638
|
});
|
|
7347
7639
|
} catch (e) {
|
|
@@ -7349,17 +7641,21 @@ var WriteService = class {
|
|
|
7349
7641
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
|
|
7350
7642
|
}
|
|
7351
7643
|
} else if (!this.jobManager.isBlocked("librarian", entityId)) {
|
|
7352
|
-
this.maybeRunHeal(entityId, eventCount).catch(
|
|
7644
|
+
this.maybeRunHeal(entityId, eventCount).catch((err) => {
|
|
7645
|
+
console.error(err);
|
|
7646
|
+
this.reportBackgroundFailure(entityId, "heal");
|
|
7647
|
+
});
|
|
7353
7648
|
}
|
|
7354
7649
|
}
|
|
7355
|
-
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
|
|
7650
|
+
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint, stage) {
|
|
7356
7651
|
try {
|
|
7357
|
-
await this.maintenanceService.doRunLibrarian(entityId);
|
|
7652
|
+
await this.maintenanceService.doRunLibrarian(entityId, void 0, "auto");
|
|
7358
7653
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: currentEventCount }, this.db);
|
|
7359
7654
|
} catch (e) {
|
|
7360
7655
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
|
|
7361
7656
|
throw e;
|
|
7362
7657
|
}
|
|
7658
|
+
stage.job = "heal";
|
|
7363
7659
|
await this.maybeRunHeal(entityId, currentEventCount);
|
|
7364
7660
|
}
|
|
7365
7661
|
/**
|
|
@@ -7375,7 +7671,7 @@ var WriteService = class {
|
|
|
7375
7671
|
const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
|
|
7376
7672
|
if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
|
|
7377
7673
|
try {
|
|
7378
|
-
const result = await this.maintenanceService.doRunHeal(entityId);
|
|
7674
|
+
const result = await this.maintenanceService.doRunHeal(entityId, { trigger: "auto" });
|
|
7379
7675
|
if (result.remaining === 0) {
|
|
7380
7676
|
await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
|
|
7381
7677
|
}
|
|
@@ -7384,6 +7680,15 @@ var WriteService = class {
|
|
|
7384
7680
|
}
|
|
7385
7681
|
}
|
|
7386
7682
|
}
|
|
7683
|
+
reportBackgroundFailure(entityId, job) {
|
|
7684
|
+
emitDiagnostic(this.options, {
|
|
7685
|
+
code: "background_job_failed",
|
|
7686
|
+
operation: job,
|
|
7687
|
+
trigger: "auto",
|
|
7688
|
+
entityId,
|
|
7689
|
+
detail: { reason: "unhandled_rejection" }
|
|
7690
|
+
});
|
|
7691
|
+
}
|
|
7387
7692
|
};
|
|
7388
7693
|
|
|
7389
7694
|
// src/prompts/ontology.ts
|
|
@@ -7472,6 +7777,17 @@ var OntologyService = class {
|
|
|
7472
7777
|
const canonical = resolveNodeType(rawType, manifest);
|
|
7473
7778
|
if (!canonical) {
|
|
7474
7779
|
if (strict) throw new WikiStrictOntologyViolation(opts?.entityId ?? "", "node", rawType);
|
|
7780
|
+
if (opts?.drops && Array.isArray(fact.edges)) {
|
|
7781
|
+
for (const edge of fact.edges) {
|
|
7782
|
+
opts.drops.push({
|
|
7783
|
+
reason: "no_source_type",
|
|
7784
|
+
sourceId: null,
|
|
7785
|
+
edgeType: typeof edge?.edge_type === "string" ? edge.edge_type : null,
|
|
7786
|
+
sourceNodeType: null,
|
|
7787
|
+
targetNodeType: null
|
|
7788
|
+
});
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7475
7791
|
return { okf_type: null, edges: [] };
|
|
7476
7792
|
}
|
|
7477
7793
|
const edges = validateInlineEdges(canonical, null, fact.edges ?? [], manifest, opts);
|
|
@@ -7484,18 +7800,39 @@ var OntologyService = class {
|
|
|
7484
7800
|
* writes inside their own transaction (e.g. `IngestionService.ingestDocument`
|
|
7485
7801
|
* when delegating to `upsertGraphCore`).
|
|
7486
7802
|
*/
|
|
7487
|
-
resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now) {
|
|
7488
|
-
if (
|
|
7803
|
+
resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops) {
|
|
7804
|
+
if (edges.length === 0) return [];
|
|
7805
|
+
if (!sourceType) {
|
|
7806
|
+
for (const edge of edges) {
|
|
7807
|
+
drops?.push({ reason: "no_source_type", sourceId, edgeType: edge.edge_type, sourceNodeType: null, targetNodeType: null });
|
|
7808
|
+
}
|
|
7809
|
+
return [];
|
|
7810
|
+
}
|
|
7489
7811
|
const out = [];
|
|
7490
7812
|
for (const edge of edges) {
|
|
7491
7813
|
const candidates = resolveEdgeDefinitions(edge.edge_type, manifest).filter((d) => typeSatisfies(d.source_type, sourceType, manifest));
|
|
7492
|
-
if (candidates.length === 0)
|
|
7814
|
+
if (candidates.length === 0) {
|
|
7815
|
+
drops?.push({ reason: "type_not_in_manifest", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7493
7818
|
const targetKey = normalizeTitleKey(edge.target_title);
|
|
7494
7819
|
const target = titleIndex.get(targetKey);
|
|
7495
|
-
if (!target)
|
|
7820
|
+
if (!target) {
|
|
7821
|
+
drops?.push({ reason: "target_not_found", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
|
|
7822
|
+
continue;
|
|
7823
|
+
}
|
|
7496
7824
|
const targetType = (target.okf_type ?? "").trim().toLowerCase();
|
|
7497
7825
|
const def = candidates.find((d) => d.target_type.trim().toLowerCase() === targetType) ?? candidates.find((d) => typeSatisfies(d.target_type, targetType, manifest));
|
|
7498
|
-
if (!def)
|
|
7826
|
+
if (!def) {
|
|
7827
|
+
drops?.push({
|
|
7828
|
+
reason: "target_type_mismatch",
|
|
7829
|
+
sourceId,
|
|
7830
|
+
edgeType: edge.edge_type,
|
|
7831
|
+
sourceNodeType: sourceType,
|
|
7832
|
+
targetNodeType: target.okf_type ?? null
|
|
7833
|
+
});
|
|
7834
|
+
continue;
|
|
7835
|
+
}
|
|
7499
7836
|
out.push({
|
|
7500
7837
|
id: generateId(),
|
|
7501
7838
|
entity_id: entityId,
|
|
@@ -7514,8 +7851,8 @@ var OntologyService = class {
|
|
|
7514
7851
|
* want self-contained edge persistence (e.g. MaintenanceService heal /
|
|
7515
7852
|
* backfill callers that do not have a separate edge-write step).
|
|
7516
7853
|
*/
|
|
7517
|
-
async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now) {
|
|
7518
|
-
const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now);
|
|
7854
|
+
async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now, drops) {
|
|
7855
|
+
const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops);
|
|
7519
7856
|
let persisted = 0;
|
|
7520
7857
|
for (const edge of resolved) {
|
|
7521
7858
|
const inserted = await this.edgeRepo.addIgnoreDuplicate(edge, tx);
|
|
@@ -8136,11 +8473,15 @@ var WikiMemory = class {
|
|
|
8136
8473
|
attemptedSourceRef: sourceRef
|
|
8137
8474
|
});
|
|
8138
8475
|
}
|
|
8139
|
-
|
|
8476
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
8477
|
+
const result = await this.ingestionService.upsertGraphCore(
|
|
8140
8478
|
entityId,
|
|
8141
8479
|
{ sourceRef, sourceHash, nodes: params.nodes, edges: params.edges },
|
|
8142
|
-
adapter
|
|
8480
|
+
adapter,
|
|
8481
|
+
{ diag: { buffer: diagBuffer, operation: "upsertGraph" } }
|
|
8143
8482
|
);
|
|
8483
|
+
diagBuffer.flush(this.options);
|
|
8484
|
+
return result;
|
|
8144
8485
|
}
|
|
8145
8486
|
/**
|
|
8146
8487
|
* Returns up to `limit` unprocessed outbox events, oldest first.
|