@equationalapplications/core-llm-wiki 7.2.1 → 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 +48 -0
- package/dist/{chunk-TTECJ3S3.mjs → chunk-G5OR2VWD.mjs} +371 -49
- package/dist/chunk-G5OR2VWD.mjs.map +1 -0
- package/dist/index.d.mts +3 -5
- package/dist/index.d.ts +3 -5
- package/dist/index.js +501 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -13
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-DYeMd9d1.d.mts → testing-Dmh1kfkd.d.mts} +171 -7
- package/dist/{testing-DYeMd9d1.d.ts → testing-Dmh1kfkd.d.ts} +171 -7
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +353 -46
- 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
|
@@ -333,6 +333,14 @@ var WikiInvalidReadOptions = class _WikiInvalidReadOptions extends Error {
|
|
|
333
333
|
Object.setPrototypeOf(this, _WikiInvalidReadOptions.prototype);
|
|
334
334
|
}
|
|
335
335
|
};
|
|
336
|
+
var WikiDraftNotFound = class _WikiDraftNotFound extends Error {
|
|
337
|
+
constructor() {
|
|
338
|
+
super("No draft fact with that id exists for this entity.");
|
|
339
|
+
this.code = "WIKI_DRAFT_NOT_FOUND";
|
|
340
|
+
this.name = "WikiDraftNotFound";
|
|
341
|
+
Object.setPrototypeOf(this, _WikiDraftNotFound.prototype);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
336
344
|
|
|
337
345
|
// src/db/serializedAdapter.ts
|
|
338
346
|
function isDriverError(err) {
|
|
@@ -1757,16 +1765,52 @@ var EntryRepository = class extends BaseRepository {
|
|
|
1757
1765
|
`UPDATE ${this.prefix}entries SET source_type = 'librarian_inferred' WHERE source_type = 'agent_inferred';`
|
|
1758
1766
|
].join("\n");
|
|
1759
1767
|
}
|
|
1760
|
-
async findRecentByEntityIds(entityIds, limit, tx) {
|
|
1768
|
+
async findRecentByEntityIds(entityIds, limit, tx, opts) {
|
|
1761
1769
|
if (entityIds.length === 0) return [];
|
|
1762
1770
|
const executor = this.getExecutor(tx);
|
|
1763
1771
|
const placeholders = entityIds.map(() => "?").join(",");
|
|
1772
|
+
const draftClause = opts?.excludeDrafts === true ? ` AND lifecycle_status != 'draft'` : "";
|
|
1764
1773
|
const rows = await executor.getAllAsync(
|
|
1765
|
-
`SELECT * FROM ${this.prefix}entries WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL ORDER BY updated_at DESC LIMIT ?`,
|
|
1774
|
+
`SELECT * FROM ${this.prefix}entries WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL${draftClause} ORDER BY updated_at DESC LIMIT ?`,
|
|
1766
1775
|
[...entityIds, limit]
|
|
1767
1776
|
);
|
|
1768
1777
|
return rows.map(mapRowToFact);
|
|
1769
1778
|
}
|
|
1779
|
+
/** IDs of live draft facts for the given entities (spec §5.1). Callers pass ≤ 100 entity ids. */
|
|
1780
|
+
async findDraftIdsByEntityIds(entityIds, tx) {
|
|
1781
|
+
if (entityIds.length === 0) return /* @__PURE__ */ new Set();
|
|
1782
|
+
const executor = this.getExecutor(tx);
|
|
1783
|
+
const placeholders = entityIds.map(() => "?").join(",");
|
|
1784
|
+
const rows = await executor.getAllAsync(
|
|
1785
|
+
`SELECT id FROM ${this.prefix}entries
|
|
1786
|
+
WHERE entity_id IN (${placeholders}) AND deleted_at IS NULL AND lifecycle_status = 'draft'`,
|
|
1787
|
+
[...entityIds]
|
|
1788
|
+
);
|
|
1789
|
+
return new Set(rows.map((r) => r.id));
|
|
1790
|
+
}
|
|
1791
|
+
/** Live drafts for one entity, newest first, keyset-paged by (created_at, id). */
|
|
1792
|
+
async listDraftsByEntityId(entityId, limit, after, tx) {
|
|
1793
|
+
const executor = this.getExecutor(tx);
|
|
1794
|
+
const afterClause = after ? ` AND (created_at < ? OR (created_at = ? AND id < ?))` : "";
|
|
1795
|
+
const args = after ? [entityId, after.createdAt, after.createdAt, after.id, limit] : [entityId, limit];
|
|
1796
|
+
const rows = await executor.getAllAsync(
|
|
1797
|
+
`SELECT * FROM ${this.prefix}entries
|
|
1798
|
+
WHERE entity_id = ? AND deleted_at IS NULL AND lifecycle_status = 'draft'${afterClause}
|
|
1799
|
+
ORDER BY created_at DESC, id DESC LIMIT ?`,
|
|
1800
|
+
args
|
|
1801
|
+
);
|
|
1802
|
+
return rows.map(mapRowToFact);
|
|
1803
|
+
}
|
|
1804
|
+
/** True iff `entryId` is a live draft owned by `entityId`. */
|
|
1805
|
+
async isLiveDraft(entryId, entityId, tx) {
|
|
1806
|
+
const executor = this.getExecutor(tx);
|
|
1807
|
+
const row = await executor.getFirstAsync(
|
|
1808
|
+
`SELECT id FROM ${this.prefix}entries
|
|
1809
|
+
WHERE id = ? AND entity_id = ? AND deleted_at IS NULL AND lifecycle_status = 'draft'`,
|
|
1810
|
+
[entryId, entityId]
|
|
1811
|
+
);
|
|
1812
|
+
return row != null;
|
|
1813
|
+
}
|
|
1770
1814
|
/**
|
|
1771
1815
|
* Live untyped facts eligible for ontology backfill, oldest first.
|
|
1772
1816
|
* Skips facts checked within the recheck cooldown (ontology_checked_at > recheckCutoff).
|
|
@@ -2577,6 +2621,7 @@ var EdgeRepository = class extends BaseRepository {
|
|
|
2577
2621
|
END
|
|
2578
2622
|
) >= ?
|
|
2579
2623
|
AND n.source_type NOT IN (${excludeSourceTypesPlaceholders})
|
|
2624
|
+
AND (? = 0 OR n.lifecycle_status != 'draft')
|
|
2580
2625
|
WHERE w.depth < ?
|
|
2581
2626
|
AND instr(w.visited, ',' || (CASE WHEN e.source_id = w.node_id THEN e.target_id ELSE e.source_id END) || ',') = 0
|
|
2582
2627
|
)
|
|
@@ -2596,6 +2641,7 @@ var EdgeRepository = class extends BaseRepository {
|
|
|
2596
2641
|
entityId,
|
|
2597
2642
|
minConfidenceRank,
|
|
2598
2643
|
...opts.excludeSourceTypes,
|
|
2644
|
+
opts.excludeDrafts === true ? 1 : 0,
|
|
2599
2645
|
opts.maxDepth,
|
|
2600
2646
|
opts.maxNodes
|
|
2601
2647
|
];
|
|
@@ -2768,20 +2814,27 @@ function mergeOntologyUpdates(current, updates) {
|
|
|
2768
2814
|
function validateInlineEdges(sourceType, _targetType, edges, manifest, opts) {
|
|
2769
2815
|
const strict = opts?.strict === true;
|
|
2770
2816
|
const entityId = opts?.entityId ?? "";
|
|
2817
|
+
const drops = opts?.drops;
|
|
2818
|
+
const drop = (reason, edgeType) => {
|
|
2819
|
+
drops?.push({ reason, sourceId: null, edgeType, sourceNodeType: sourceType, targetNodeType: null });
|
|
2820
|
+
};
|
|
2771
2821
|
if (!Array.isArray(edges)) {
|
|
2772
2822
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", "");
|
|
2823
|
+
drop("invalid_shape", null);
|
|
2773
2824
|
return [];
|
|
2774
2825
|
}
|
|
2775
2826
|
const valid = [];
|
|
2776
2827
|
for (const edge of edges) {
|
|
2777
2828
|
if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") {
|
|
2778
2829
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", String(edge?.edge_type ?? ""));
|
|
2830
|
+
drop("invalid_shape", typeof edge?.edge_type === "string" ? edge.edge_type : null);
|
|
2779
2831
|
continue;
|
|
2780
2832
|
}
|
|
2781
2833
|
const defs = resolveEdgeDefinitions(edge.edge_type, manifest);
|
|
2782
2834
|
const match = defs.find((d) => typeSatisfies(d.source_type, sourceType, manifest));
|
|
2783
2835
|
if (!match) {
|
|
2784
2836
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", edge.edge_type);
|
|
2837
|
+
drop("type_not_in_manifest", edge.edge_type);
|
|
2785
2838
|
continue;
|
|
2786
2839
|
}
|
|
2787
2840
|
valid.push({ edge_type: match.type, target_title: edge.target_title });
|
|
@@ -4129,6 +4182,17 @@ function validateTask(task) {
|
|
|
4129
4182
|
priority
|
|
4130
4183
|
};
|
|
4131
4184
|
}
|
|
4185
|
+
function factRejectionReason(raw) {
|
|
4186
|
+
const fact = raw;
|
|
4187
|
+
if (typeof fact?.title !== "string" || typeof fact?.body !== "string") return "invalid_shape";
|
|
4188
|
+
if (!clip(fact.title, 80)) return "missing_title";
|
|
4189
|
+
return "missing_body";
|
|
4190
|
+
}
|
|
4191
|
+
function taskRejectionReason(raw) {
|
|
4192
|
+
const task = raw;
|
|
4193
|
+
if (typeof task?.description !== "string") return "invalid_shape";
|
|
4194
|
+
return "missing_description";
|
|
4195
|
+
}
|
|
4132
4196
|
function normalizeSourceRef(value) {
|
|
4133
4197
|
if (typeof value !== "string") return null;
|
|
4134
4198
|
const cleaned = value.replace(/[^A-Za-z0-9._\- ]/g, "").trim().slice(0, 255);
|
|
@@ -4148,6 +4212,104 @@ function jaccardScore(a, b) {
|
|
|
4148
4212
|
return intersection.size / union.size;
|
|
4149
4213
|
}
|
|
4150
4214
|
|
|
4215
|
+
// src/utils/diagnostics.ts
|
|
4216
|
+
var SEVERITY = {
|
|
4217
|
+
ingest_chunk_failed: "warn",
|
|
4218
|
+
fact_rejected: "warn",
|
|
4219
|
+
task_rejected: "warn",
|
|
4220
|
+
fact_deduplicated: "info",
|
|
4221
|
+
edge_dropped: "warn",
|
|
4222
|
+
embedding_failed: "warn",
|
|
4223
|
+
hook_failed: "warn",
|
|
4224
|
+
background_job_failed: "error",
|
|
4225
|
+
heal_skipped: "warn",
|
|
4226
|
+
grounding_missing: "warn",
|
|
4227
|
+
grounding_failed: "warn",
|
|
4228
|
+
classification_low_confidence: "info",
|
|
4229
|
+
classification_invalid: "warn"
|
|
4230
|
+
};
|
|
4231
|
+
var MESSAGE = {
|
|
4232
|
+
ingest_chunk_failed: "One or more document chunks failed to parse or generate.",
|
|
4233
|
+
fact_rejected: "An extracted fact failed validation and was not written.",
|
|
4234
|
+
task_rejected: "An extracted task failed validation and was not written.",
|
|
4235
|
+
fact_deduplicated: "An extracted fact duplicated an existing fact and was not written.",
|
|
4236
|
+
edge_dropped: "An extracted edge could not be resolved against the ontology and was not written.",
|
|
4237
|
+
embedding_failed: "A fact could not be embedded.",
|
|
4238
|
+
hook_failed: "A host hook threw or rejected.",
|
|
4239
|
+
background_job_failed: "A background maintenance job failed.",
|
|
4240
|
+
heal_skipped: "Heal skipped a candidate fact.",
|
|
4241
|
+
grounding_missing: "A fact carried no qualifying evidence and was stored as a draft.",
|
|
4242
|
+
grounding_failed: "A fact carried evidence not found in its source and was stored as a draft.",
|
|
4243
|
+
classification_low_confidence: "A classifier answer was below the confidence threshold and was not applied.",
|
|
4244
|
+
classification_invalid: "A classifier answer was invalid and was not applied."
|
|
4245
|
+
};
|
|
4246
|
+
var warnedNonFunction = /* @__PURE__ */ new WeakSet();
|
|
4247
|
+
function copyDetail(detail) {
|
|
4248
|
+
const copy = { ...detail };
|
|
4249
|
+
if (detail.chunkIndexes) copy.chunkIndexes = detail.chunkIndexes.slice();
|
|
4250
|
+
return copy;
|
|
4251
|
+
}
|
|
4252
|
+
function emitDiagnostic(options, input) {
|
|
4253
|
+
const hook = options.onDiagnostic;
|
|
4254
|
+
if (hook === void 0 || hook === null) return;
|
|
4255
|
+
if (typeof hook !== "function") {
|
|
4256
|
+
if (!warnedNonFunction.has(options)) {
|
|
4257
|
+
warnedNonFunction.add(options);
|
|
4258
|
+
console.warn("[WikiMemory] onDiagnostic is not a function; diagnostics are disabled.");
|
|
4259
|
+
}
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
const diagnostic = {
|
|
4263
|
+
code: input.code,
|
|
4264
|
+
severity: SEVERITY[input.code],
|
|
4265
|
+
operation: input.operation,
|
|
4266
|
+
trigger: input.trigger,
|
|
4267
|
+
entityId: input.entityId,
|
|
4268
|
+
at: Date.now(),
|
|
4269
|
+
message: MESSAGE[input.code],
|
|
4270
|
+
...input.detail ? { detail: copyDetail(input.detail) } : {}
|
|
4271
|
+
};
|
|
4272
|
+
try {
|
|
4273
|
+
const returned = hook(diagnostic);
|
|
4274
|
+
if (returned !== void 0) {
|
|
4275
|
+
Promise.resolve(returned).catch((err) => {
|
|
4276
|
+
console.warn("[WikiMemory] onDiagnostic hook rejected:", err);
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
} catch (err) {
|
|
4280
|
+
console.warn("[WikiMemory] onDiagnostic hook threw:", err);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
var DiagnosticBuffer = class {
|
|
4284
|
+
constructor() {
|
|
4285
|
+
this.items = [];
|
|
4286
|
+
}
|
|
4287
|
+
push(input) {
|
|
4288
|
+
this.items.push(input);
|
|
4289
|
+
}
|
|
4290
|
+
get size() {
|
|
4291
|
+
return this.items.length;
|
|
4292
|
+
}
|
|
4293
|
+
flush(options) {
|
|
4294
|
+
const pending = this.items;
|
|
4295
|
+
this.items = [];
|
|
4296
|
+
for (const input of pending) emitDiagnostic(options, input);
|
|
4297
|
+
}
|
|
4298
|
+
discard() {
|
|
4299
|
+
this.items = [];
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
function edgeDropDiagnostic(drop, ctx) {
|
|
4303
|
+
const detail = { reason: drop.reason };
|
|
4304
|
+
const factId = ctx.factId ?? drop.sourceId;
|
|
4305
|
+
if (factId) detail.factId = factId;
|
|
4306
|
+
if (drop.edgeType) detail.edgeType = drop.edgeType;
|
|
4307
|
+
if (drop.sourceNodeType) detail.sourceNodeType = drop.sourceNodeType;
|
|
4308
|
+
if (drop.targetNodeType) detail.targetNodeType = drop.targetNodeType;
|
|
4309
|
+
if (ctx.sourceRef) detail.sourceRef = ctx.sourceRef;
|
|
4310
|
+
return { code: "edge_dropped", operation: ctx.operation, trigger: ctx.trigger, entityId: ctx.entityId, detail };
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4151
4313
|
// src/prompts.ts
|
|
4152
4314
|
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
4315
|
Return ONLY a valid JSON object matching this schema:
|
|
@@ -4435,9 +4597,24 @@ var IngestionService = class {
|
|
|
4435
4597
|
try {
|
|
4436
4598
|
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
4437
4599
|
const result2 = parseJsonResponse(responseText);
|
|
4600
|
+
const rawFacts = Array.isArray(result2.facts) ? result2.facts : [];
|
|
4601
|
+
const facts = [];
|
|
4602
|
+
const itemIndexes = [];
|
|
4603
|
+
const rejected = [];
|
|
4604
|
+
rawFacts.forEach((raw, itemIndex) => {
|
|
4605
|
+
const valid = validateFact(raw);
|
|
4606
|
+
if (valid) {
|
|
4607
|
+
facts.push(valid);
|
|
4608
|
+
itemIndexes.push(itemIndex);
|
|
4609
|
+
} else {
|
|
4610
|
+
rejected.push({ itemIndex, reason: factRejectionReason(raw) });
|
|
4611
|
+
}
|
|
4612
|
+
});
|
|
4438
4613
|
return {
|
|
4439
4614
|
status: "ok",
|
|
4440
|
-
facts
|
|
4615
|
+
facts,
|
|
4616
|
+
itemIndexes,
|
|
4617
|
+
rejected,
|
|
4441
4618
|
ontology_updates: result2.ontology_updates
|
|
4442
4619
|
};
|
|
4443
4620
|
} catch (e) {
|
|
@@ -4482,23 +4659,48 @@ var IngestionService = class {
|
|
|
4482
4659
|
const failures = [];
|
|
4483
4660
|
const seen = /* @__PURE__ */ new Set();
|
|
4484
4661
|
const orderedChunkFacts = [];
|
|
4485
|
-
|
|
4662
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
4663
|
+
const diagBase = { entityId, operation: "ingest", trigger: "call" };
|
|
4664
|
+
for (const [chunkIndex, slot] of chunkResults.entries()) {
|
|
4486
4665
|
if (slot.status === "failed") {
|
|
4487
4666
|
failedChunks++;
|
|
4488
4667
|
failures.push(slot.error);
|
|
4489
4668
|
continue;
|
|
4490
4669
|
}
|
|
4491
4670
|
ingestedChunks++;
|
|
4671
|
+
for (const r of slot.rejected) {
|
|
4672
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { sourceRef, chunkIndex, itemIndex: r.itemIndex, reason: r.reason } });
|
|
4673
|
+
}
|
|
4492
4674
|
const dedupedFacts = [];
|
|
4493
|
-
|
|
4675
|
+
slot.facts.forEach((fact, k) => {
|
|
4494
4676
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
4495
4677
|
if (!seen.has(normalizedTitle)) {
|
|
4496
4678
|
seen.add(normalizedTitle);
|
|
4497
4679
|
dedupedFacts.push(fact);
|
|
4680
|
+
} else {
|
|
4681
|
+
diagBuffer.push({
|
|
4682
|
+
...diagBase,
|
|
4683
|
+
code: "fact_deduplicated",
|
|
4684
|
+
detail: { sourceRef, chunkIndex, itemIndex: slot.itemIndexes[k], reason: "exact_title" }
|
|
4685
|
+
});
|
|
4498
4686
|
}
|
|
4499
|
-
}
|
|
4687
|
+
});
|
|
4500
4688
|
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: slot.ontology_updates });
|
|
4501
4689
|
}
|
|
4690
|
+
for (const reason of ["parse", "llm"]) {
|
|
4691
|
+
const ofReason = failures.filter((f) => f.source === reason);
|
|
4692
|
+
if (ofReason.length === 0) continue;
|
|
4693
|
+
diagBuffer.push({
|
|
4694
|
+
...diagBase,
|
|
4695
|
+
code: "ingest_chunk_failed",
|
|
4696
|
+
detail: {
|
|
4697
|
+
sourceRef,
|
|
4698
|
+
reason,
|
|
4699
|
+
count: ofReason.length,
|
|
4700
|
+
chunkIndexes: ofReason.map((f) => f.chunkIndex).sort((a, b) => a - b).slice(0, 20)
|
|
4701
|
+
}
|
|
4702
|
+
});
|
|
4703
|
+
}
|
|
4502
4704
|
if (failedChunks === chunks.length) {
|
|
4503
4705
|
throw new WikiIngestEmptyError({
|
|
4504
4706
|
parseFailures: failures,
|
|
@@ -4511,7 +4713,7 @@ var IngestionService = class {
|
|
|
4511
4713
|
try {
|
|
4512
4714
|
if (failedChunks === 0) {
|
|
4513
4715
|
const fullResult = await this.db.withTransactionAsync(async (tx) => {
|
|
4514
|
-
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx);
|
|
4716
|
+
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer);
|
|
4515
4717
|
});
|
|
4516
4718
|
deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
|
|
4517
4719
|
insertedFacts.push(...fullResult.insertedFacts);
|
|
@@ -4519,7 +4721,7 @@ var IngestionService = class {
|
|
|
4519
4721
|
const partialResult = await this.db.withTransactionAsync(async (tx) => {
|
|
4520
4722
|
const flat = [];
|
|
4521
4723
|
for (const slot of orderedChunkFacts) flat.push(...slot.facts);
|
|
4522
|
-
return await this.appendPartialFacts(entityId, sourceRef, flat, tx);
|
|
4724
|
+
return await this.appendPartialFacts(entityId, sourceRef, flat, tx, diagBuffer);
|
|
4523
4725
|
});
|
|
4524
4726
|
insertedFacts.push(...partialResult.insertedDescriptors);
|
|
4525
4727
|
}
|
|
@@ -4533,6 +4735,7 @@ var IngestionService = class {
|
|
|
4533
4735
|
}
|
|
4534
4736
|
return zeroChunkResult(canonical);
|
|
4535
4737
|
}
|
|
4738
|
+
diagBuffer.flush(this.options);
|
|
4536
4739
|
await this.searchService.sync(entityId);
|
|
4537
4740
|
if (failedChunks === 0) {
|
|
4538
4741
|
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
@@ -4541,11 +4744,18 @@ var IngestionService = class {
|
|
|
4541
4744
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
4542
4745
|
} catch (hookErr) {
|
|
4543
4746
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during ingest for ${factId}:`, hookErr);
|
|
4747
|
+
emitDiagnostic(this.options, {
|
|
4748
|
+
entityId,
|
|
4749
|
+
operation: "ingest",
|
|
4750
|
+
trigger: "call",
|
|
4751
|
+
code: "hook_failed",
|
|
4752
|
+
detail: { factId, reason: "on_embedding_persisted" }
|
|
4753
|
+
});
|
|
4544
4754
|
}
|
|
4545
4755
|
}
|
|
4546
4756
|
}
|
|
4547
4757
|
for (const fact of insertedFacts) {
|
|
4548
|
-
await this.embeddingService.embedFact(fact);
|
|
4758
|
+
await this.embeddingService.embedFact(fact, { operation: "ingest", trigger: "call" });
|
|
4549
4759
|
}
|
|
4550
4760
|
this.searchService.evictCache(entityId);
|
|
4551
4761
|
const result = {
|
|
@@ -4691,6 +4901,19 @@ var IngestionService = class {
|
|
|
4691
4901
|
const match = candidates[0];
|
|
4692
4902
|
if (!match) {
|
|
4693
4903
|
if (strictEffective) throw new WikiStrictOntologyViolation(entityId, "edge", edge.type);
|
|
4904
|
+
opts?.diag?.buffer.push({
|
|
4905
|
+
entityId,
|
|
4906
|
+
operation: opts.diag.operation,
|
|
4907
|
+
trigger: "call",
|
|
4908
|
+
code: "edge_dropped",
|
|
4909
|
+
detail: {
|
|
4910
|
+
reason: "manifest_violation",
|
|
4911
|
+
factId: edge.sourceId,
|
|
4912
|
+
edgeType: edge.type,
|
|
4913
|
+
...sourceType ? { sourceNodeType: sourceType } : {},
|
|
4914
|
+
sourceRef: params.sourceRef
|
|
4915
|
+
}
|
|
4916
|
+
});
|
|
4694
4917
|
continue;
|
|
4695
4918
|
}
|
|
4696
4919
|
validEdges.push({
|
|
@@ -4731,7 +4954,7 @@ var IngestionService = class {
|
|
|
4731
4954
|
* On the partial path the caller never invokes this method; the empty
|
|
4732
4955
|
* arrays stay empty.
|
|
4733
4956
|
*/
|
|
4734
|
-
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx) {
|
|
4957
|
+
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer) {
|
|
4735
4958
|
const deletedSourceFactIds = [];
|
|
4736
4959
|
const insertedFacts = [];
|
|
4737
4960
|
deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
|
|
@@ -4756,8 +4979,12 @@ var IngestionService = class {
|
|
|
4756
4979
|
}
|
|
4757
4980
|
for (const fact of facts) {
|
|
4758
4981
|
const ontologyFact = fact;
|
|
4759
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
4760
4982
|
const id = generateId("fact_");
|
|
4983
|
+
const validationDrops = [];
|
|
4984
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
4985
|
+
for (const drop of validationDrops) {
|
|
4986
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef, factId: id }));
|
|
4987
|
+
}
|
|
4761
4988
|
hostNodes.push({
|
|
4762
4989
|
id,
|
|
4763
4990
|
type: ontologyFact.okf_type ?? "",
|
|
@@ -4782,6 +5009,7 @@ var IngestionService = class {
|
|
|
4782
5009
|
}
|
|
4783
5010
|
const hostEdges = [];
|
|
4784
5011
|
for (const req of rawEdgeRequests) {
|
|
5012
|
+
const resolveDrops = [];
|
|
4785
5013
|
const resolved = this.ontologyService?.resolveEdges(
|
|
4786
5014
|
entityId,
|
|
4787
5015
|
req.sourceId,
|
|
@@ -4789,8 +5017,12 @@ var IngestionService = class {
|
|
|
4789
5017
|
req.edges,
|
|
4790
5018
|
manifest,
|
|
4791
5019
|
titleIndex,
|
|
4792
|
-
now
|
|
5020
|
+
now,
|
|
5021
|
+
resolveDrops
|
|
4793
5022
|
) ?? [];
|
|
5023
|
+
for (const drop of resolveDrops) {
|
|
5024
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef }));
|
|
5025
|
+
}
|
|
4794
5026
|
for (const e of resolved) {
|
|
4795
5027
|
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
4796
5028
|
}
|
|
@@ -4799,7 +5031,7 @@ var IngestionService = class {
|
|
|
4799
5031
|
entityId,
|
|
4800
5032
|
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
4801
5033
|
tx,
|
|
4802
|
-
{ strict: false }
|
|
5034
|
+
{ strict: false, diag: { buffer: diagBuffer, operation: "ingest" } }
|
|
4803
5035
|
);
|
|
4804
5036
|
return { deletedSourceFactIds, insertedFacts };
|
|
4805
5037
|
}
|
|
@@ -4823,7 +5055,7 @@ var IngestionService = class {
|
|
|
4823
5055
|
* Runs INSIDE the caller's `tx`. Does not open a nested transaction.
|
|
4824
5056
|
* Returns `{ inserted, skippedDuplicate }` for observability.
|
|
4825
5057
|
*/
|
|
4826
|
-
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx) {
|
|
5058
|
+
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx, diagBuffer) {
|
|
4827
5059
|
const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
|
|
4828
5060
|
const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
|
|
4829
5061
|
const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
|
|
@@ -4834,6 +5066,13 @@ var IngestionService = class {
|
|
|
4834
5066
|
for (const fact of dedupedFacts) {
|
|
4835
5067
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
4836
5068
|
if (liveTitles.has(normalizedTitle)) {
|
|
5069
|
+
diagBuffer.push({
|
|
5070
|
+
entityId,
|
|
5071
|
+
operation: "ingest",
|
|
5072
|
+
trigger: "call",
|
|
5073
|
+
code: "fact_deduplicated",
|
|
5074
|
+
detail: { sourceRef, reason: "exact_title" }
|
|
5075
|
+
});
|
|
4837
5076
|
skippedDuplicate++;
|
|
4838
5077
|
continue;
|
|
4839
5078
|
}
|
|
@@ -5234,7 +5473,7 @@ var MaintenanceService = class {
|
|
|
5234
5473
|
permanentlyFailed++;
|
|
5235
5474
|
continue;
|
|
5236
5475
|
}
|
|
5237
|
-
const result = await this.embeddingService.tryEmbedFact(row);
|
|
5476
|
+
const result = await this.embeddingService.tryEmbedFact(row, { operation: "reembed", trigger: "call" });
|
|
5238
5477
|
if (result.ok) embedded++;
|
|
5239
5478
|
else failed++;
|
|
5240
5479
|
}
|
|
@@ -5355,7 +5594,7 @@ var MaintenanceService = class {
|
|
|
5355
5594
|
return { deleted: { entries, tasks: 0 } };
|
|
5356
5595
|
}
|
|
5357
5596
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
5358
|
-
async doRunLibrarian(entityId, promptOverride) {
|
|
5597
|
+
async doRunLibrarian(entityId, promptOverride, trigger = "call") {
|
|
5359
5598
|
const events = await this.eventRepo.getRecent(entityId, 50);
|
|
5360
5599
|
const currentFactsRows = await this.entryRepo.findRecentByEntityId(entityId, 100);
|
|
5361
5600
|
const currentFacts = currentFactsRows.map((f) => {
|
|
@@ -5377,8 +5616,25 @@ var MaintenanceService = class {
|
|
|
5377
5616
|
const facts = Array.isArray(result.facts) ? result.facts : [];
|
|
5378
5617
|
const tasks = Array.isArray(result.tasks) ? result.tasks : [];
|
|
5379
5618
|
const ontologyUpdates = result.ontology_updates;
|
|
5380
|
-
const
|
|
5381
|
-
const
|
|
5619
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
5620
|
+
const diagBase = { entityId, operation: "librarian", trigger };
|
|
5621
|
+
const validFacts = [];
|
|
5622
|
+
const validFactItemIndexes = [];
|
|
5623
|
+
facts.forEach((raw, itemIndex) => {
|
|
5624
|
+
const valid = validateFact(raw);
|
|
5625
|
+
if (valid) {
|
|
5626
|
+
validFacts.push(valid);
|
|
5627
|
+
validFactItemIndexes.push(itemIndex);
|
|
5628
|
+
} else {
|
|
5629
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
5630
|
+
}
|
|
5631
|
+
});
|
|
5632
|
+
const validTasks = [];
|
|
5633
|
+
tasks.forEach((raw, itemIndex) => {
|
|
5634
|
+
const valid = validateTask(raw);
|
|
5635
|
+
if (valid) validTasks.push(valid);
|
|
5636
|
+
else diagBuffer.push({ ...diagBase, code: "task_rejected", detail: { itemIndex, reason: taskRejectionReason(raw) } });
|
|
5637
|
+
});
|
|
5382
5638
|
const now = Date.now();
|
|
5383
5639
|
const insertedFacts = [];
|
|
5384
5640
|
await this.db.withTransactionAsync(async (tx) => {
|
|
@@ -5395,7 +5651,7 @@ var MaintenanceService = class {
|
|
|
5395
5651
|
}
|
|
5396
5652
|
const factsForDedupe = await this.entryRepo.findRecentByEntityId(entityId, 100, tx);
|
|
5397
5653
|
const pendingEdges = [];
|
|
5398
|
-
for (const fact of validFacts) {
|
|
5654
|
+
for (const [k, fact] of validFacts.entries()) {
|
|
5399
5655
|
const newTokens = titleTokens(fact.title);
|
|
5400
5656
|
let skip = false;
|
|
5401
5657
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -5410,10 +5666,15 @@ var MaintenanceService = class {
|
|
|
5410
5666
|
}
|
|
5411
5667
|
}
|
|
5412
5668
|
}
|
|
5413
|
-
if (skip)
|
|
5669
|
+
if (skip) {
|
|
5670
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
5671
|
+
continue;
|
|
5672
|
+
}
|
|
5414
5673
|
const ontologyFact = fact;
|
|
5415
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
5416
5674
|
const id = generateId("fact_");
|
|
5675
|
+
const validationDrops = [];
|
|
5676
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
5677
|
+
for (const drop of validationDrops) diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: id }));
|
|
5417
5678
|
const factObj = {
|
|
5418
5679
|
id,
|
|
5419
5680
|
entity_id: entityId,
|
|
@@ -5440,6 +5701,7 @@ var MaintenanceService = class {
|
|
|
5440
5701
|
}
|
|
5441
5702
|
}
|
|
5442
5703
|
for (const item of pendingEdges) {
|
|
5704
|
+
const resolveDrops = [];
|
|
5443
5705
|
await this.ontologyService?.resolveAndPersistEdges(
|
|
5444
5706
|
entityId,
|
|
5445
5707
|
item.sourceId,
|
|
@@ -5448,8 +5710,10 @@ var MaintenanceService = class {
|
|
|
5448
5710
|
manifest,
|
|
5449
5711
|
titleIndex,
|
|
5450
5712
|
tx,
|
|
5451
|
-
now
|
|
5713
|
+
now,
|
|
5714
|
+
resolveDrops
|
|
5452
5715
|
);
|
|
5716
|
+
for (const drop of resolveDrops) diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
5453
5717
|
}
|
|
5454
5718
|
for (const task of validTasks) {
|
|
5455
5719
|
const id = generateId("task_");
|
|
@@ -5467,9 +5731,10 @@ var MaintenanceService = class {
|
|
|
5467
5731
|
await this.taskRepo.upsert(taskObj, tx);
|
|
5468
5732
|
}
|
|
5469
5733
|
});
|
|
5734
|
+
diagBuffer.flush(this.options);
|
|
5470
5735
|
await this.searchService.sync(entityId);
|
|
5471
5736
|
for (const fact of insertedFacts) {
|
|
5472
|
-
await this.embeddingService.embedFact(fact);
|
|
5737
|
+
await this.embeddingService.embedFact(fact, { operation: "librarian", trigger });
|
|
5473
5738
|
}
|
|
5474
5739
|
this.searchService.evictCache(entityId);
|
|
5475
5740
|
}
|
|
@@ -5483,6 +5748,8 @@ var MaintenanceService = class {
|
|
|
5483
5748
|
async doRunHeal(entityId, options) {
|
|
5484
5749
|
const promptOverride = options?.promptOverride;
|
|
5485
5750
|
const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
|
|
5751
|
+
const trigger = options?.trigger ?? "call";
|
|
5752
|
+
const diagBase = { entityId, operation: "heal", trigger };
|
|
5486
5753
|
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
5487
5754
|
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
5488
5755
|
}
|
|
@@ -5518,6 +5785,7 @@ var MaintenanceService = class {
|
|
|
5518
5785
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
5519
5786
|
} catch (hookErr) {
|
|
5520
5787
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
|
|
5788
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
5521
5789
|
}
|
|
5522
5790
|
}
|
|
5523
5791
|
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
@@ -5590,22 +5858,38 @@ var MaintenanceService = class {
|
|
|
5590
5858
|
const safeDowngradedSet = /* @__PURE__ */ new Set();
|
|
5591
5859
|
const safeDeletedSet = /* @__PURE__ */ new Set();
|
|
5592
5860
|
const newFacts = [];
|
|
5861
|
+
const newFactItemIndexes = [];
|
|
5593
5862
|
for (const batchResult of outcome.results) {
|
|
5594
5863
|
const mutableIds = new Set(batchResult.batch.map((f) => f.id));
|
|
5595
5864
|
for (const id of batchResult.downgraded) if (mutableIds.has(id)) safeDowngradedSet.add(id);
|
|
5596
5865
|
for (const id of batchResult.deleted) if (mutableIds.has(id)) safeDeletedSet.add(id);
|
|
5597
|
-
newFacts.
|
|
5866
|
+
batchResult.newFacts.forEach((raw, itemIndex) => {
|
|
5867
|
+
newFacts.push(raw);
|
|
5868
|
+
newFactItemIndexes.push(itemIndex);
|
|
5869
|
+
});
|
|
5598
5870
|
}
|
|
5599
5871
|
const safeDowngraded = Array.from(safeDowngradedSet);
|
|
5600
5872
|
const safeDeleted = Array.from(safeDeletedSet);
|
|
5601
|
-
const
|
|
5873
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
5874
|
+
const validNewFacts = [];
|
|
5875
|
+
const validNewFactItemIndexes = [];
|
|
5876
|
+
newFacts.forEach((raw, k) => {
|
|
5877
|
+
const itemIndex = newFactItemIndexes[k];
|
|
5878
|
+
const valid = validateFact(raw);
|
|
5879
|
+
if (valid) {
|
|
5880
|
+
validNewFacts.push(valid);
|
|
5881
|
+
validNewFactItemIndexes.push(itemIndex);
|
|
5882
|
+
} else {
|
|
5883
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
5884
|
+
}
|
|
5885
|
+
});
|
|
5602
5886
|
const insertedFacts = [];
|
|
5603
5887
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
5604
5888
|
await this.db.withTransactionAsync(async (tx) => {
|
|
5605
5889
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
5606
5890
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
5607
5891
|
const healFactsForDedupe = await this.entryRepo.findInferredTitlesByEntityId(entityId, tx);
|
|
5608
|
-
for (const fact of validNewFacts) {
|
|
5892
|
+
for (const [k, fact] of validNewFacts.entries()) {
|
|
5609
5893
|
const newTokens = titleTokens(fact.title);
|
|
5610
5894
|
let skip = false;
|
|
5611
5895
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -5619,7 +5903,10 @@ var MaintenanceService = class {
|
|
|
5619
5903
|
}
|
|
5620
5904
|
}
|
|
5621
5905
|
}
|
|
5622
|
-
if (skip)
|
|
5906
|
+
if (skip) {
|
|
5907
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validNewFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
5908
|
+
continue;
|
|
5909
|
+
}
|
|
5623
5910
|
const id = generateId("fact_");
|
|
5624
5911
|
const factObj = {
|
|
5625
5912
|
id,
|
|
@@ -5654,16 +5941,21 @@ var MaintenanceService = class {
|
|
|
5654
5941
|
tx
|
|
5655
5942
|
);
|
|
5656
5943
|
});
|
|
5944
|
+
for (const { item, reason } of outcome.skipped) {
|
|
5945
|
+
diagBuffer.push({ ...diagBase, code: "heal_skipped", detail: { factId: item.id, reason } });
|
|
5946
|
+
}
|
|
5947
|
+
diagBuffer.flush(this.options);
|
|
5657
5948
|
await this.searchService.sync(entityId);
|
|
5658
5949
|
for (const factId of uniqueDeletedFactIds) {
|
|
5659
5950
|
try {
|
|
5660
5951
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
5661
5952
|
} catch (hookErr) {
|
|
5662
5953
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal for ${factId}:`, hookErr);
|
|
5954
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
5663
5955
|
}
|
|
5664
5956
|
}
|
|
5665
5957
|
for (const fact of insertedFacts) {
|
|
5666
|
-
await this.embeddingService.embedFact(fact);
|
|
5958
|
+
await this.embeddingService.embedFact(fact, { operation: "heal", trigger });
|
|
5667
5959
|
}
|
|
5668
5960
|
this.searchService.evictCache(entityId);
|
|
5669
5961
|
const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
|
|
@@ -5803,6 +6095,8 @@ var MaintenanceService = class {
|
|
|
5803
6095
|
let failedValidation = 0;
|
|
5804
6096
|
let edgesAdded = 0;
|
|
5805
6097
|
let abortedOntologyOff = false;
|
|
6098
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
6099
|
+
const diagBase = { entityId, operation: "ontologyBackfill", trigger: "call" };
|
|
5806
6100
|
await this.db.withTransactionAsync(async (tx) => {
|
|
5807
6101
|
let { mode: txMode, manifest } = await ontologyService.getEffectiveState(entityId, tx);
|
|
5808
6102
|
if (txMode === "off") {
|
|
@@ -5827,6 +6121,7 @@ var MaintenanceService = class {
|
|
|
5827
6121
|
continue;
|
|
5828
6122
|
}
|
|
5829
6123
|
applied.add(fact.id);
|
|
6124
|
+
const validationDrops = [];
|
|
5830
6125
|
const normalized = ontologyService.validateAndNormalizeFact(
|
|
5831
6126
|
{
|
|
5832
6127
|
title: fact.title,
|
|
@@ -5837,8 +6132,11 @@ var MaintenanceService = class {
|
|
|
5837
6132
|
edges: classification.edges
|
|
5838
6133
|
},
|
|
5839
6134
|
manifest,
|
|
5840
|
-
{ strict: false }
|
|
6135
|
+
{ strict: false, drops: validationDrops }
|
|
5841
6136
|
);
|
|
6137
|
+
for (const drop of validationDrops) {
|
|
6138
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: fact.id }));
|
|
6139
|
+
}
|
|
5842
6140
|
if (!normalized.okf_type) {
|
|
5843
6141
|
failedValidation++;
|
|
5844
6142
|
continue;
|
|
@@ -5852,7 +6150,8 @@ var MaintenanceService = class {
|
|
|
5852
6150
|
}
|
|
5853
6151
|
}
|
|
5854
6152
|
for (const item of pendingEdges) {
|
|
5855
|
-
|
|
6153
|
+
const resolveDrops = [];
|
|
6154
|
+
const added = await ontologyService.resolveAndPersistEdges(
|
|
5856
6155
|
entityId,
|
|
5857
6156
|
item.sourceId,
|
|
5858
6157
|
item.sourceType,
|
|
@@ -5860,11 +6159,17 @@ var MaintenanceService = class {
|
|
|
5860
6159
|
manifest,
|
|
5861
6160
|
titleIndex,
|
|
5862
6161
|
tx,
|
|
5863
|
-
now
|
|
6162
|
+
now,
|
|
6163
|
+
resolveDrops
|
|
5864
6164
|
);
|
|
6165
|
+
edgesAdded += added;
|
|
6166
|
+
for (const drop of resolveDrops) {
|
|
6167
|
+
diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
6168
|
+
}
|
|
5865
6169
|
}
|
|
5866
6170
|
await this.entryRepo.markOntologyChecked(batch.map((f) => f.id), entityId, now, tx);
|
|
5867
6171
|
});
|
|
6172
|
+
diagBuffer.flush(this.options);
|
|
5868
6173
|
return { typed, failedValidation, edgesAdded, abortedOntologyOff };
|
|
5869
6174
|
}
|
|
5870
6175
|
_validatePruneDuration(value, name) {
|
|
@@ -6266,7 +6571,7 @@ var ImportExportService = class {
|
|
|
6266
6571
|
title: clipped?.title ?? fact.title,
|
|
6267
6572
|
body: clipped?.body ?? fact.body,
|
|
6268
6573
|
tags: Array.isArray(fact.tags) || typeof fact.tags === "string" ? fact.tags : []
|
|
6269
|
-
});
|
|
6574
|
+
}, { operation: "importDump", trigger: "call" });
|
|
6270
6575
|
if (!embedded) {
|
|
6271
6576
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, fact.id, null);
|
|
6272
6577
|
}
|
|
@@ -6291,6 +6596,11 @@ var ImportExportService = class {
|
|
|
6291
6596
|
`[WikiMemory] onEmbeddingPersisted hook failed for preserved-blob fact ${fact.id}:`,
|
|
6292
6597
|
hookErr
|
|
6293
6598
|
);
|
|
6599
|
+
this.embeddingService.reportHookFailed(
|
|
6600
|
+
{ operation: "importDump", trigger: "call" },
|
|
6601
|
+
entityId,
|
|
6602
|
+
fact.id
|
|
6603
|
+
);
|
|
6294
6604
|
}
|
|
6295
6605
|
}
|
|
6296
6606
|
}
|
|
@@ -6307,6 +6617,11 @@ var ImportExportService = class {
|
|
|
6307
6617
|
`[WikiMemory] onEmbeddingPersisted(vector=null) hook failed for soft-deleted fact ${factId}:`,
|
|
6308
6618
|
hookErr
|
|
6309
6619
|
);
|
|
6620
|
+
this.embeddingService.reportHookFailed(
|
|
6621
|
+
{ operation: "importDump", trigger: "call" },
|
|
6622
|
+
entityId,
|
|
6623
|
+
factId
|
|
6624
|
+
);
|
|
6310
6625
|
}
|
|
6311
6626
|
}
|
|
6312
6627
|
}
|
|
@@ -6469,7 +6784,7 @@ var EmbeddingService = class {
|
|
|
6469
6784
|
});
|
|
6470
6785
|
}
|
|
6471
6786
|
}
|
|
6472
|
-
async tryEmbedFact(fact) {
|
|
6787
|
+
async tryEmbedFact(fact, ctx) {
|
|
6473
6788
|
const embedFn = this.options.llmProvider.embed;
|
|
6474
6789
|
if (typeof embedFn !== "function") return { ok: false, kind: "no_provider" };
|
|
6475
6790
|
let tagsStr;
|
|
@@ -6491,6 +6806,7 @@ var EmbeddingService = class {
|
|
|
6491
6806
|
const vector = await embedFn(text);
|
|
6492
6807
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
6493
6808
|
console.warn(`[WikiMemory] embedFact: embed() returned an invalid vector for ${fact.id}; skipping.`);
|
|
6809
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "invalid_vector");
|
|
6494
6810
|
await this.markFailure(fact.id, "invalid_vector");
|
|
6495
6811
|
return { ok: false, kind: "invalid_vector" };
|
|
6496
6812
|
}
|
|
@@ -6504,11 +6820,13 @@ var EmbeddingService = class {
|
|
|
6504
6820
|
}
|
|
6505
6821
|
if (hasNonFinite) {
|
|
6506
6822
|
console.warn(`[WikiMemory] embedFact: embed() returned values that overflow float32 for ${fact.id}; skipping.`);
|
|
6823
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "float32_overflow");
|
|
6507
6824
|
await this.markFailure(fact.id, "float32_overflow");
|
|
6508
6825
|
return { ok: false, kind: "float32_overflow" };
|
|
6509
6826
|
}
|
|
6510
6827
|
} catch (err) {
|
|
6511
6828
|
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
6829
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "embed_threw");
|
|
6512
6830
|
await this.markFailure(fact.id, "provider_error");
|
|
6513
6831
|
return { ok: false, kind: "provider_error" };
|
|
6514
6832
|
}
|
|
@@ -6518,15 +6836,31 @@ var EmbeddingService = class {
|
|
|
6518
6836
|
await this.entryRepo.updateEmbeddingBlob(fact.id, blob);
|
|
6519
6837
|
} catch (err) {
|
|
6520
6838
|
console.warn(`[WikiMemory] embedFact: persisting embedding failed for ${fact.id}:`, err);
|
|
6839
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "persist_failed");
|
|
6521
6840
|
return { ok: false, kind: "storage_error" };
|
|
6522
6841
|
}
|
|
6523
6842
|
try {
|
|
6524
6843
|
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
6525
6844
|
} catch (hookErr) {
|
|
6526
6845
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
6846
|
+
this.reportEmbed(ctx, fact, "hook_failed", "on_embedding_persisted");
|
|
6527
6847
|
}
|
|
6528
6848
|
return { ok: true, dimension: float32Vector.length };
|
|
6529
6849
|
}
|
|
6850
|
+
reportEmbed(ctx, fact, code, reason) {
|
|
6851
|
+
if (!ctx) return;
|
|
6852
|
+
emitDiagnostic(this.options, {
|
|
6853
|
+
code,
|
|
6854
|
+
operation: ctx.operation,
|
|
6855
|
+
trigger: ctx.trigger,
|
|
6856
|
+
entityId: fact.entity_id,
|
|
6857
|
+
detail: { factId: fact.id, reason }
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6860
|
+
/** For callers that invoke `notifyEmbeddingPersisted` directly and catch its failure. */
|
|
6861
|
+
reportHookFailed(ctx, entityId, factId) {
|
|
6862
|
+
this.reportEmbed(ctx, { id: factId, entity_id: entityId }, "hook_failed", "on_embedding_persisted");
|
|
6863
|
+
}
|
|
6530
6864
|
/** Marker writes must never fail the caller. Only marker-eligible kinds reach here. */
|
|
6531
6865
|
async markFailure(id, kind) {
|
|
6532
6866
|
try {
|
|
@@ -6540,8 +6874,8 @@ var EmbeddingService = class {
|
|
|
6540
6874
|
* ImportExportService branch on `if (!embedded)`; a discriminated result
|
|
6541
6875
|
* object is always truthy and would silently disable that branch.
|
|
6542
6876
|
*/
|
|
6543
|
-
async embedFact(fact) {
|
|
6544
|
-
const result = await this.tryEmbedFact(fact);
|
|
6877
|
+
async embedFact(fact, ctx) {
|
|
6878
|
+
const result = await this.tryEmbedFact(fact, ctx);
|
|
6545
6879
|
return result.ok;
|
|
6546
6880
|
}
|
|
6547
6881
|
async notifyEmbeddingPersisted(entityId, factId, vector) {
|
|
@@ -6681,6 +7015,7 @@ function selectWithFloors(sortedRows, floors, maxResults) {
|
|
|
6681
7015
|
}
|
|
6682
7016
|
|
|
6683
7017
|
// src/services/RetrievalService.ts
|
|
7018
|
+
var EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
6684
7019
|
var RetrievalService = class {
|
|
6685
7020
|
constructor(options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService) {
|
|
6686
7021
|
this.options = options;
|
|
@@ -6713,6 +7048,7 @@ var RetrievalService = class {
|
|
|
6713
7048
|
}
|
|
6714
7049
|
const rawMaxResults = options?.maxResults ?? config?.maxResults ?? config?.maxFtsResults ?? 10;
|
|
6715
7050
|
const maxResults = Number.isFinite(rawMaxResults) ? Math.max(0, Math.trunc(rawMaxResults)) : 10;
|
|
7051
|
+
const excludeDrafts = (options?.excludeDrafts ?? config?.excludeDrafts ?? false) === true;
|
|
6716
7052
|
const trimmedQuery = query.trim();
|
|
6717
7053
|
const sanitizedTierFloors = trimmedQuery && exposeMetadata ? validateTierFloors(
|
|
6718
7054
|
entityIds,
|
|
@@ -6732,6 +7068,9 @@ var RetrievalService = class {
|
|
|
6732
7068
|
if (maxResults === 0) ; else if (trimmedQuery) {
|
|
6733
7069
|
let usedEmbed = false;
|
|
6734
7070
|
const scoredEntityIds = this._filterScoredEntities(entityIds, sanitizedTierWeights, options?.includeZeroWeightEntities);
|
|
7071
|
+
const draftIds = excludeDrafts && scoredEntityIds.length > 0 ? await this.entryRepo.findDraftIdsByEntityIds(scoredEntityIds) : EMPTY_ID_SET;
|
|
7072
|
+
const draftPad = draftIds.size;
|
|
7073
|
+
const padLimit = (n) => n >= Number.MAX_SAFE_INTEGER ? n : n + draftPad;
|
|
6735
7074
|
if (scoredEntityIds.length === 0) {
|
|
6736
7075
|
usedEmbed = true;
|
|
6737
7076
|
} else if (!skipEmbed && embedFn) {
|
|
@@ -6765,7 +7104,7 @@ var RetrievalService = class {
|
|
|
6765
7104
|
let miniSearchScores;
|
|
6766
7105
|
if (effectivePreFilterLimit !== void 0) {
|
|
6767
7106
|
populateCache = false;
|
|
6768
|
-
const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER);
|
|
7107
|
+
const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER).filter((r) => !draftIds.has(r.id));
|
|
6769
7108
|
if (preResults.length === 0) {
|
|
6770
7109
|
candidateRows = null;
|
|
6771
7110
|
} else {
|
|
@@ -6787,9 +7126,9 @@ var RetrievalService = class {
|
|
|
6787
7126
|
}
|
|
6788
7127
|
} else {
|
|
6789
7128
|
if (useRanker) {
|
|
6790
|
-
candidateRows = await this.entryRepo.findMetadataByEntityIds(scoredEntityIds);
|
|
7129
|
+
candidateRows = this._withoutDrafts(await this.entryRepo.findMetadataByEntityIds(scoredEntityIds), draftIds);
|
|
6791
7130
|
} else {
|
|
6792
|
-
candidateRows = await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds);
|
|
7131
|
+
candidateRows = this._withoutDrafts(await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds), draftIds);
|
|
6793
7132
|
}
|
|
6794
7133
|
if (weight !== void 0 && weight < 1) {
|
|
6795
7134
|
miniSearchScores = this.searchService.getMiniSearchScores(trimmedQuery, scoredEntityIds);
|
|
@@ -6819,7 +7158,7 @@ var RetrievalService = class {
|
|
|
6819
7158
|
candidateRows: rowsForEntity,
|
|
6820
7159
|
weight,
|
|
6821
7160
|
miniSearchScores,
|
|
6822
|
-
limit: Math.max(maxResults * 2, maxResults + 50)
|
|
7161
|
+
limit: padLimit(Math.max(maxResults * 2, maxResults + 50))
|
|
6823
7162
|
});
|
|
6824
7163
|
return ranked.map((row) => ({ ...row, entity_id: scopedEntityId }));
|
|
6825
7164
|
})
|
|
@@ -7033,7 +7372,7 @@ var RetrievalService = class {
|
|
|
7033
7372
|
});
|
|
7034
7373
|
} else if (policy === "keyword") {
|
|
7035
7374
|
const hasActiveFloorsKeywordFallback = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
|
|
7036
|
-
const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
|
|
7375
|
+
const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
|
|
7037
7376
|
const preFilteredIds = effectivePreFilterLimit !== void 0 ? new Set(candidateRows.map((r) => r.id)) : void 0;
|
|
7038
7377
|
const keywordResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, keywordOversampledLimit);
|
|
7039
7378
|
const topResults = preFilteredIds === void 0 ? keywordResults : keywordResults.filter((r) => preFilteredIds.has(r.id));
|
|
@@ -7074,6 +7413,7 @@ var RetrievalService = class {
|
|
|
7074
7413
|
// read() re-sorts after applying tier weights
|
|
7075
7414
|
});
|
|
7076
7415
|
}
|
|
7416
|
+
if (draftPad > 0) scored = scored.filter((s) => !draftIds.has(s.id));
|
|
7077
7417
|
if (scored.length > 0) {
|
|
7078
7418
|
scored = scored.map((row) => ({
|
|
7079
7419
|
...row,
|
|
@@ -7136,8 +7476,8 @@ var RetrievalService = class {
|
|
|
7136
7476
|
}
|
|
7137
7477
|
if (!usedEmbed && scoredEntityIds.length > 0) {
|
|
7138
7478
|
const hasActiveFloors = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
|
|
7139
|
-
const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
|
|
7140
|
-
const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit);
|
|
7479
|
+
const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
|
|
7480
|
+
const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit).filter((r) => !draftIds.has(r.id));
|
|
7141
7481
|
const candidates = results.map((r) => ({
|
|
7142
7482
|
id: r.id,
|
|
7143
7483
|
entity_id: r.entity_id,
|
|
@@ -7161,7 +7501,7 @@ var RetrievalService = class {
|
|
|
7161
7501
|
await this.entryRepo.trackAccess(ids, now);
|
|
7162
7502
|
}
|
|
7163
7503
|
} else {
|
|
7164
|
-
facts = await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
|
|
7504
|
+
facts = excludeDrafts ? await this.entryRepo.findRecentByEntityIds(entityIds, maxResults, void 0, { excludeDrafts: true }) : await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
|
|
7165
7505
|
}
|
|
7166
7506
|
const eventsLimit = Math.min(10 * entityIds.length, 100);
|
|
7167
7507
|
const [tasks, events] = await Promise.all([
|
|
@@ -7197,6 +7537,9 @@ var RetrievalService = class {
|
|
|
7197
7537
|
_tieBreakSort(items) {
|
|
7198
7538
|
items.sort((a, b) => this._compareScoredRows(a, b));
|
|
7199
7539
|
}
|
|
7540
|
+
_withoutDrafts(rows, draftIds) {
|
|
7541
|
+
return draftIds.size === 0 ? rows : rows.filter((row) => !draftIds.has(row.id));
|
|
7542
|
+
}
|
|
7200
7543
|
/**
|
|
7201
7544
|
* Comparator for score + deterministic tie-break fields.
|
|
7202
7545
|
* Negative return means "a ranks ahead of b" for descending score order.
|
|
@@ -7341,7 +7684,11 @@ var WriteService = class {
|
|
|
7341
7684
|
if (shouldRunLibrarian) {
|
|
7342
7685
|
try {
|
|
7343
7686
|
this.jobManager.acquireLock("librarian", entityId);
|
|
7344
|
-
|
|
7687
|
+
const stage = { job: "librarian" };
|
|
7688
|
+
this.runLibrarianThenMaybeHeal(entityId, librarianCount, prevMemoryCheckpoint, stage).catch((err) => {
|
|
7689
|
+
console.error(err);
|
|
7690
|
+
this.reportBackgroundFailure(entityId, stage.job);
|
|
7691
|
+
}).finally(() => {
|
|
7345
7692
|
this.jobManager.releaseLock("librarian", entityId);
|
|
7346
7693
|
});
|
|
7347
7694
|
} catch (e) {
|
|
@@ -7349,17 +7696,21 @@ var WriteService = class {
|
|
|
7349
7696
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
|
|
7350
7697
|
}
|
|
7351
7698
|
} else if (!this.jobManager.isBlocked("librarian", entityId)) {
|
|
7352
|
-
this.maybeRunHeal(entityId, eventCount).catch(
|
|
7699
|
+
this.maybeRunHeal(entityId, eventCount).catch((err) => {
|
|
7700
|
+
console.error(err);
|
|
7701
|
+
this.reportBackgroundFailure(entityId, "heal");
|
|
7702
|
+
});
|
|
7353
7703
|
}
|
|
7354
7704
|
}
|
|
7355
|
-
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
|
|
7705
|
+
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint, stage) {
|
|
7356
7706
|
try {
|
|
7357
|
-
await this.maintenanceService.doRunLibrarian(entityId);
|
|
7707
|
+
await this.maintenanceService.doRunLibrarian(entityId, void 0, "auto");
|
|
7358
7708
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: currentEventCount }, this.db);
|
|
7359
7709
|
} catch (e) {
|
|
7360
7710
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
|
|
7361
7711
|
throw e;
|
|
7362
7712
|
}
|
|
7713
|
+
stage.job = "heal";
|
|
7363
7714
|
await this.maybeRunHeal(entityId, currentEventCount);
|
|
7364
7715
|
}
|
|
7365
7716
|
/**
|
|
@@ -7375,7 +7726,7 @@ var WriteService = class {
|
|
|
7375
7726
|
const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
|
|
7376
7727
|
if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
|
|
7377
7728
|
try {
|
|
7378
|
-
const result = await this.maintenanceService.doRunHeal(entityId);
|
|
7729
|
+
const result = await this.maintenanceService.doRunHeal(entityId, { trigger: "auto" });
|
|
7379
7730
|
if (result.remaining === 0) {
|
|
7380
7731
|
await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
|
|
7381
7732
|
}
|
|
@@ -7384,6 +7735,15 @@ var WriteService = class {
|
|
|
7384
7735
|
}
|
|
7385
7736
|
}
|
|
7386
7737
|
}
|
|
7738
|
+
reportBackgroundFailure(entityId, job) {
|
|
7739
|
+
emitDiagnostic(this.options, {
|
|
7740
|
+
code: "background_job_failed",
|
|
7741
|
+
operation: job,
|
|
7742
|
+
trigger: "auto",
|
|
7743
|
+
entityId,
|
|
7744
|
+
detail: { reason: "unhandled_rejection" }
|
|
7745
|
+
});
|
|
7746
|
+
}
|
|
7387
7747
|
};
|
|
7388
7748
|
|
|
7389
7749
|
// src/prompts/ontology.ts
|
|
@@ -7472,6 +7832,17 @@ var OntologyService = class {
|
|
|
7472
7832
|
const canonical = resolveNodeType(rawType, manifest);
|
|
7473
7833
|
if (!canonical) {
|
|
7474
7834
|
if (strict) throw new WikiStrictOntologyViolation(opts?.entityId ?? "", "node", rawType);
|
|
7835
|
+
if (opts?.drops && Array.isArray(fact.edges)) {
|
|
7836
|
+
for (const edge of fact.edges) {
|
|
7837
|
+
opts.drops.push({
|
|
7838
|
+
reason: "no_source_type",
|
|
7839
|
+
sourceId: null,
|
|
7840
|
+
edgeType: typeof edge?.edge_type === "string" ? edge.edge_type : null,
|
|
7841
|
+
sourceNodeType: null,
|
|
7842
|
+
targetNodeType: null
|
|
7843
|
+
});
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7475
7846
|
return { okf_type: null, edges: [] };
|
|
7476
7847
|
}
|
|
7477
7848
|
const edges = validateInlineEdges(canonical, null, fact.edges ?? [], manifest, opts);
|
|
@@ -7484,18 +7855,39 @@ var OntologyService = class {
|
|
|
7484
7855
|
* writes inside their own transaction (e.g. `IngestionService.ingestDocument`
|
|
7485
7856
|
* when delegating to `upsertGraphCore`).
|
|
7486
7857
|
*/
|
|
7487
|
-
resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now) {
|
|
7488
|
-
if (
|
|
7858
|
+
resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops) {
|
|
7859
|
+
if (edges.length === 0) return [];
|
|
7860
|
+
if (!sourceType) {
|
|
7861
|
+
for (const edge of edges) {
|
|
7862
|
+
drops?.push({ reason: "no_source_type", sourceId, edgeType: edge.edge_type, sourceNodeType: null, targetNodeType: null });
|
|
7863
|
+
}
|
|
7864
|
+
return [];
|
|
7865
|
+
}
|
|
7489
7866
|
const out = [];
|
|
7490
7867
|
for (const edge of edges) {
|
|
7491
7868
|
const candidates = resolveEdgeDefinitions(edge.edge_type, manifest).filter((d) => typeSatisfies(d.source_type, sourceType, manifest));
|
|
7492
|
-
if (candidates.length === 0)
|
|
7869
|
+
if (candidates.length === 0) {
|
|
7870
|
+
drops?.push({ reason: "type_not_in_manifest", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
|
|
7871
|
+
continue;
|
|
7872
|
+
}
|
|
7493
7873
|
const targetKey = normalizeTitleKey(edge.target_title);
|
|
7494
7874
|
const target = titleIndex.get(targetKey);
|
|
7495
|
-
if (!target)
|
|
7875
|
+
if (!target) {
|
|
7876
|
+
drops?.push({ reason: "target_not_found", sourceId, edgeType: edge.edge_type, sourceNodeType: sourceType, targetNodeType: null });
|
|
7877
|
+
continue;
|
|
7878
|
+
}
|
|
7496
7879
|
const targetType = (target.okf_type ?? "").trim().toLowerCase();
|
|
7497
7880
|
const def = candidates.find((d) => d.target_type.trim().toLowerCase() === targetType) ?? candidates.find((d) => typeSatisfies(d.target_type, targetType, manifest));
|
|
7498
|
-
if (!def)
|
|
7881
|
+
if (!def) {
|
|
7882
|
+
drops?.push({
|
|
7883
|
+
reason: "target_type_mismatch",
|
|
7884
|
+
sourceId,
|
|
7885
|
+
edgeType: edge.edge_type,
|
|
7886
|
+
sourceNodeType: sourceType,
|
|
7887
|
+
targetNodeType: target.okf_type ?? null
|
|
7888
|
+
});
|
|
7889
|
+
continue;
|
|
7890
|
+
}
|
|
7499
7891
|
out.push({
|
|
7500
7892
|
id: generateId(),
|
|
7501
7893
|
entity_id: entityId,
|
|
@@ -7514,8 +7906,8 @@ var OntologyService = class {
|
|
|
7514
7906
|
* want self-contained edge persistence (e.g. MaintenanceService heal /
|
|
7515
7907
|
* backfill callers that do not have a separate edge-write step).
|
|
7516
7908
|
*/
|
|
7517
|
-
async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now) {
|
|
7518
|
-
const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now);
|
|
7909
|
+
async resolveAndPersistEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, tx, now, drops) {
|
|
7910
|
+
const resolved = this.resolveEdges(entityId, sourceId, sourceType, edges, manifest, titleIndex, now, drops);
|
|
7519
7911
|
let persisted = 0;
|
|
7520
7912
|
for (const edge of resolved) {
|
|
7521
7913
|
const inserted = await this.edgeRepo.addIgnoreDuplicate(edge, tx);
|
|
@@ -7544,6 +7936,7 @@ var GraphTraversalService = class {
|
|
|
7544
7936
|
edgeTypes: options.edgeTypes,
|
|
7545
7937
|
minConfidence: options.minTraversalConfidence ?? this.config.minTraversalConfidence ?? "tentative",
|
|
7546
7938
|
excludeSourceTypes: options.excludeSourceTypes ?? this.config.excludeSourceTypes ?? [],
|
|
7939
|
+
excludeDrafts: options.excludeDrafts ?? this.config.excludeDrafts ?? false,
|
|
7547
7940
|
maxNodes
|
|
7548
7941
|
};
|
|
7549
7942
|
const { nodeIds, edges } = await this.edgeRepo.getNeighborhood(entityId, options.sourceId, opts);
|
|
@@ -7717,6 +8110,15 @@ var OkfTrustWritesRepository = class {
|
|
|
7717
8110
|
|
|
7718
8111
|
// src/WikiMemory.ts
|
|
7719
8112
|
var TABLE_PREFIX_PATTERN = /^[A-Za-z][A-Za-z0-9_]{0,30}_$/;
|
|
8113
|
+
function decodeDraftCursor(cursor) {
|
|
8114
|
+
const sep = typeof cursor === "string" ? cursor.indexOf(":") : -1;
|
|
8115
|
+
const createdAt = sep > 0 ? Number(cursor.slice(0, sep)) : Number.NaN;
|
|
8116
|
+
const id = sep > 0 ? cursor.slice(sep + 1) : "";
|
|
8117
|
+
if (!Number.isSafeInteger(createdAt) || id.length === 0) {
|
|
8118
|
+
throw new TypeError("Invalid listDrafts cursor.");
|
|
8119
|
+
}
|
|
8120
|
+
return { createdAt, id };
|
|
8121
|
+
}
|
|
7720
8122
|
var _testAccessNonTestEnvWarned;
|
|
7721
8123
|
var WikiMemory = class {
|
|
7722
8124
|
constructor(db, options) {
|
|
@@ -8136,11 +8538,15 @@ var WikiMemory = class {
|
|
|
8136
8538
|
attemptedSourceRef: sourceRef
|
|
8137
8539
|
});
|
|
8138
8540
|
}
|
|
8139
|
-
|
|
8541
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
8542
|
+
const result = await this.ingestionService.upsertGraphCore(
|
|
8140
8543
|
entityId,
|
|
8141
8544
|
{ sourceRef, sourceHash, nodes: params.nodes, edges: params.edges },
|
|
8142
|
-
adapter
|
|
8545
|
+
adapter,
|
|
8546
|
+
{ diag: { buffer: diagBuffer, operation: "upsertGraph" } }
|
|
8143
8547
|
);
|
|
8548
|
+
diagBuffer.flush(this.options);
|
|
8549
|
+
return result;
|
|
8144
8550
|
}
|
|
8145
8551
|
/**
|
|
8146
8552
|
* Returns up to `limit` unprocessed outbox events, oldest first.
|
|
@@ -8257,6 +8663,43 @@ var WikiMemory = class {
|
|
|
8257
8663
|
async setLifecycleStatus(entryId, entityId, status) {
|
|
8258
8664
|
return this.okfTrustWrites.setLifecycleStatus(entryId, entityId, status);
|
|
8259
8665
|
}
|
|
8666
|
+
/**
|
|
8667
|
+
* Live draft facts for one entity, newest first (spec §5.2). Default page
|
|
8668
|
+
* size 50, clamped to [1, 500]. Pass `nextCursor` back unchanged for the next page.
|
|
8669
|
+
*/
|
|
8670
|
+
async listDrafts(entityId, options) {
|
|
8671
|
+
const rawLimit = options?.limit;
|
|
8672
|
+
const limit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(500, Math.max(1, Math.floor(rawLimit))) : 50;
|
|
8673
|
+
const after = options?.cursor === void 0 ? null : decodeDraftCursor(options.cursor);
|
|
8674
|
+
const rows = await this.entryRepo.listDraftsByEntityId(entityId, limit + 1, after);
|
|
8675
|
+
const facts = rows.slice(0, limit);
|
|
8676
|
+
const last = facts[facts.length - 1];
|
|
8677
|
+
return {
|
|
8678
|
+
facts,
|
|
8679
|
+
nextCursor: rows.length > limit && last ? `${last.created_at}:${last.id}` : null
|
|
8680
|
+
};
|
|
8681
|
+
}
|
|
8682
|
+
/**
|
|
8683
|
+
* Promote a draft to `stable` and record who reviewed it, atomically
|
|
8684
|
+
* (spec §5.2). Metadata writes only: `updated_at` is not bumped and no
|
|
8685
|
+
* outbox event is pushed. Pass `by: 'human:<id>'` so `trustTier` becomes
|
|
8686
|
+
* `'human-reviewed'`.
|
|
8687
|
+
*
|
|
8688
|
+
* @throws WikiDraftNotFound when no live draft with that id exists for the entity.
|
|
8689
|
+
*/
|
|
8690
|
+
async promoteDraft(entryId, entityId, reviewer) {
|
|
8691
|
+
const by = reviewer?.by;
|
|
8692
|
+
if (typeof by !== "string" || by.trim().length === 0) {
|
|
8693
|
+
throw new TypeError("promoteDraft requires reviewer.by to be a non-empty string.");
|
|
8694
|
+
}
|
|
8695
|
+
await this.db.withTransactionAsync(async (tx) => {
|
|
8696
|
+
if (!await this.entryRepo.isLiveDraft(entryId, entityId, tx)) {
|
|
8697
|
+
throw new WikiDraftNotFound();
|
|
8698
|
+
}
|
|
8699
|
+
await this.okfTrustWrites.setLifecycleStatus(entryId, entityId, "stable", tx);
|
|
8700
|
+
await this.okfTrustWrites.writeOkfTrust(entryId, entityId, [{ by, at: (/* @__PURE__ */ new Date()).toISOString() }], tx);
|
|
8701
|
+
});
|
|
8702
|
+
}
|
|
8260
8703
|
/** Set a fact's stale_after (epoch ms) or clear it. Does NOT touch `updated_at`. */
|
|
8261
8704
|
async setStaleAfter(entryId, entityId, date) {
|
|
8262
8705
|
return this.okfTrustWrites.setStaleAfter(entryId, entityId, date);
|
|
@@ -9208,6 +9651,7 @@ exports.ONTOLOGY_BACKFILL_SYSTEM_PROMPT = ONTOLOGY_BACKFILL_SYSTEM_PROMPT;
|
|
|
9208
9651
|
exports.PromptService = PromptService;
|
|
9209
9652
|
exports.PrunePartialFailureError = PrunePartialFailureError;
|
|
9210
9653
|
exports.WikiBusyError = WikiBusyError;
|
|
9654
|
+
exports.WikiDraftNotFound = WikiDraftNotFound;
|
|
9211
9655
|
exports.WikiDuplicateHashError = WikiDuplicateHashError;
|
|
9212
9656
|
exports.WikiGraphNodeOwnershipConflict = WikiGraphNodeOwnershipConflict;
|
|
9213
9657
|
exports.WikiIngestEmptyError = WikiIngestEmptyError;
|