@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
|
@@ -168,6 +168,14 @@ var WikiInvalidReadOptions = class _WikiInvalidReadOptions extends Error {
|
|
|
168
168
|
Object.setPrototypeOf(this, _WikiInvalidReadOptions.prototype);
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
|
+
var WikiDraftNotFound = class _WikiDraftNotFound extends Error {
|
|
172
|
+
constructor() {
|
|
173
|
+
super("No draft fact with that id exists for this entity.");
|
|
174
|
+
this.code = "WIKI_DRAFT_NOT_FOUND";
|
|
175
|
+
this.name = "WikiDraftNotFound";
|
|
176
|
+
Object.setPrototypeOf(this, _WikiDraftNotFound.prototype);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
171
179
|
|
|
172
180
|
// src/utils/ids.ts
|
|
173
181
|
var _injectedGetRandomValues = null;
|
|
@@ -336,20 +344,27 @@ function mergeOntologyUpdates(current, updates) {
|
|
|
336
344
|
function validateInlineEdges(sourceType, _targetType, edges, manifest, opts) {
|
|
337
345
|
const strict = opts?.strict === true;
|
|
338
346
|
const entityId = opts?.entityId ?? "";
|
|
347
|
+
const drops = opts?.drops;
|
|
348
|
+
const drop = (reason, edgeType) => {
|
|
349
|
+
drops?.push({ reason, sourceId: null, edgeType, sourceNodeType: sourceType, targetNodeType: null });
|
|
350
|
+
};
|
|
339
351
|
if (!Array.isArray(edges)) {
|
|
340
352
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", "");
|
|
353
|
+
drop("invalid_shape", null);
|
|
341
354
|
return [];
|
|
342
355
|
}
|
|
343
356
|
const valid = [];
|
|
344
357
|
for (const edge of edges) {
|
|
345
358
|
if (typeof edge?.edge_type !== "string" || typeof edge?.target_title !== "string") {
|
|
346
359
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", String(edge?.edge_type ?? ""));
|
|
360
|
+
drop("invalid_shape", typeof edge?.edge_type === "string" ? edge.edge_type : null);
|
|
347
361
|
continue;
|
|
348
362
|
}
|
|
349
363
|
const defs = resolveEdgeDefinitions(edge.edge_type, manifest);
|
|
350
364
|
const match = defs.find((d) => typeSatisfies(d.source_type, sourceType, manifest));
|
|
351
365
|
if (!match) {
|
|
352
366
|
if (strict) throw new WikiStrictOntologyViolation(entityId, "edge", edge.edge_type);
|
|
367
|
+
drop("type_not_in_manifest", edge.edge_type);
|
|
353
368
|
continue;
|
|
354
369
|
}
|
|
355
370
|
valid.push({ edge_type: match.type, target_title: edge.target_title });
|
|
@@ -1526,6 +1541,17 @@ function validateTask(task) {
|
|
|
1526
1541
|
priority
|
|
1527
1542
|
};
|
|
1528
1543
|
}
|
|
1544
|
+
function factRejectionReason(raw) {
|
|
1545
|
+
const fact = raw;
|
|
1546
|
+
if (typeof fact?.title !== "string" || typeof fact?.body !== "string") return "invalid_shape";
|
|
1547
|
+
if (!clip(fact.title, 80)) return "missing_title";
|
|
1548
|
+
return "missing_body";
|
|
1549
|
+
}
|
|
1550
|
+
function taskRejectionReason(raw) {
|
|
1551
|
+
const task = raw;
|
|
1552
|
+
if (typeof task?.description !== "string") return "invalid_shape";
|
|
1553
|
+
return "missing_description";
|
|
1554
|
+
}
|
|
1529
1555
|
function normalizeSourceRef(value) {
|
|
1530
1556
|
if (typeof value !== "string") return null;
|
|
1531
1557
|
const cleaned = value.replace(/[^A-Za-z0-9._\- ]/g, "").trim().slice(0, 255);
|
|
@@ -1765,6 +1791,104 @@ function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
|
|
|
1765
1791
|
var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
|
|
1766
1792
|
var DEFAULT_CHUNK_OVERLAP = 400;
|
|
1767
1793
|
|
|
1794
|
+
// src/utils/diagnostics.ts
|
|
1795
|
+
var SEVERITY = {
|
|
1796
|
+
ingest_chunk_failed: "warn",
|
|
1797
|
+
fact_rejected: "warn",
|
|
1798
|
+
task_rejected: "warn",
|
|
1799
|
+
fact_deduplicated: "info",
|
|
1800
|
+
edge_dropped: "warn",
|
|
1801
|
+
embedding_failed: "warn",
|
|
1802
|
+
hook_failed: "warn",
|
|
1803
|
+
background_job_failed: "error",
|
|
1804
|
+
heal_skipped: "warn",
|
|
1805
|
+
grounding_missing: "warn",
|
|
1806
|
+
grounding_failed: "warn",
|
|
1807
|
+
classification_low_confidence: "info",
|
|
1808
|
+
classification_invalid: "warn"
|
|
1809
|
+
};
|
|
1810
|
+
var MESSAGE = {
|
|
1811
|
+
ingest_chunk_failed: "One or more document chunks failed to parse or generate.",
|
|
1812
|
+
fact_rejected: "An extracted fact failed validation and was not written.",
|
|
1813
|
+
task_rejected: "An extracted task failed validation and was not written.",
|
|
1814
|
+
fact_deduplicated: "An extracted fact duplicated an existing fact and was not written.",
|
|
1815
|
+
edge_dropped: "An extracted edge could not be resolved against the ontology and was not written.",
|
|
1816
|
+
embedding_failed: "A fact could not be embedded.",
|
|
1817
|
+
hook_failed: "A host hook threw or rejected.",
|
|
1818
|
+
background_job_failed: "A background maintenance job failed.",
|
|
1819
|
+
heal_skipped: "Heal skipped a candidate fact.",
|
|
1820
|
+
grounding_missing: "A fact carried no qualifying evidence and was stored as a draft.",
|
|
1821
|
+
grounding_failed: "A fact carried evidence not found in its source and was stored as a draft.",
|
|
1822
|
+
classification_low_confidence: "A classifier answer was below the confidence threshold and was not applied.",
|
|
1823
|
+
classification_invalid: "A classifier answer was invalid and was not applied."
|
|
1824
|
+
};
|
|
1825
|
+
var warnedNonFunction = /* @__PURE__ */ new WeakSet();
|
|
1826
|
+
function copyDetail(detail) {
|
|
1827
|
+
const copy = { ...detail };
|
|
1828
|
+
if (detail.chunkIndexes) copy.chunkIndexes = detail.chunkIndexes.slice();
|
|
1829
|
+
return copy;
|
|
1830
|
+
}
|
|
1831
|
+
function emitDiagnostic(options, input) {
|
|
1832
|
+
const hook = options.onDiagnostic;
|
|
1833
|
+
if (hook === void 0 || hook === null) return;
|
|
1834
|
+
if (typeof hook !== "function") {
|
|
1835
|
+
if (!warnedNonFunction.has(options)) {
|
|
1836
|
+
warnedNonFunction.add(options);
|
|
1837
|
+
console.warn("[WikiMemory] onDiagnostic is not a function; diagnostics are disabled.");
|
|
1838
|
+
}
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
const diagnostic = {
|
|
1842
|
+
code: input.code,
|
|
1843
|
+
severity: SEVERITY[input.code],
|
|
1844
|
+
operation: input.operation,
|
|
1845
|
+
trigger: input.trigger,
|
|
1846
|
+
entityId: input.entityId,
|
|
1847
|
+
at: Date.now(),
|
|
1848
|
+
message: MESSAGE[input.code],
|
|
1849
|
+
...input.detail ? { detail: copyDetail(input.detail) } : {}
|
|
1850
|
+
};
|
|
1851
|
+
try {
|
|
1852
|
+
const returned = hook(diagnostic);
|
|
1853
|
+
if (returned !== void 0) {
|
|
1854
|
+
Promise.resolve(returned).catch((err) => {
|
|
1855
|
+
console.warn("[WikiMemory] onDiagnostic hook rejected:", err);
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
console.warn("[WikiMemory] onDiagnostic hook threw:", err);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
var DiagnosticBuffer = class {
|
|
1863
|
+
constructor() {
|
|
1864
|
+
this.items = [];
|
|
1865
|
+
}
|
|
1866
|
+
push(input) {
|
|
1867
|
+
this.items.push(input);
|
|
1868
|
+
}
|
|
1869
|
+
get size() {
|
|
1870
|
+
return this.items.length;
|
|
1871
|
+
}
|
|
1872
|
+
flush(options) {
|
|
1873
|
+
const pending = this.items;
|
|
1874
|
+
this.items = [];
|
|
1875
|
+
for (const input of pending) emitDiagnostic(options, input);
|
|
1876
|
+
}
|
|
1877
|
+
discard() {
|
|
1878
|
+
this.items = [];
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
function edgeDropDiagnostic(drop, ctx) {
|
|
1882
|
+
const detail = { reason: drop.reason };
|
|
1883
|
+
const factId = ctx.factId ?? drop.sourceId;
|
|
1884
|
+
if (factId) detail.factId = factId;
|
|
1885
|
+
if (drop.edgeType) detail.edgeType = drop.edgeType;
|
|
1886
|
+
if (drop.sourceNodeType) detail.sourceNodeType = drop.sourceNodeType;
|
|
1887
|
+
if (drop.targetNodeType) detail.targetNodeType = drop.targetNodeType;
|
|
1888
|
+
if (ctx.sourceRef) detail.sourceRef = ctx.sourceRef;
|
|
1889
|
+
return { code: "edge_dropped", operation: ctx.operation, trigger: ctx.trigger, entityId: ctx.entityId, detail };
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1768
1892
|
// src/services/IngestionService.ts
|
|
1769
1893
|
function zeroChunkResult(duplicateOf) {
|
|
1770
1894
|
const result = {
|
|
@@ -1832,9 +1956,24 @@ var IngestionService = class {
|
|
|
1832
1956
|
try {
|
|
1833
1957
|
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
1834
1958
|
const result2 = parseJsonResponse(responseText);
|
|
1959
|
+
const rawFacts = Array.isArray(result2.facts) ? result2.facts : [];
|
|
1960
|
+
const facts = [];
|
|
1961
|
+
const itemIndexes = [];
|
|
1962
|
+
const rejected = [];
|
|
1963
|
+
rawFacts.forEach((raw, itemIndex) => {
|
|
1964
|
+
const valid = validateFact(raw);
|
|
1965
|
+
if (valid) {
|
|
1966
|
+
facts.push(valid);
|
|
1967
|
+
itemIndexes.push(itemIndex);
|
|
1968
|
+
} else {
|
|
1969
|
+
rejected.push({ itemIndex, reason: factRejectionReason(raw) });
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1835
1972
|
return {
|
|
1836
1973
|
status: "ok",
|
|
1837
|
-
facts
|
|
1974
|
+
facts,
|
|
1975
|
+
itemIndexes,
|
|
1976
|
+
rejected,
|
|
1838
1977
|
ontology_updates: result2.ontology_updates
|
|
1839
1978
|
};
|
|
1840
1979
|
} catch (e) {
|
|
@@ -1879,23 +2018,48 @@ var IngestionService = class {
|
|
|
1879
2018
|
const failures = [];
|
|
1880
2019
|
const seen = /* @__PURE__ */ new Set();
|
|
1881
2020
|
const orderedChunkFacts = [];
|
|
1882
|
-
|
|
2021
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
2022
|
+
const diagBase = { entityId, operation: "ingest", trigger: "call" };
|
|
2023
|
+
for (const [chunkIndex, slot] of chunkResults.entries()) {
|
|
1883
2024
|
if (slot.status === "failed") {
|
|
1884
2025
|
failedChunks++;
|
|
1885
2026
|
failures.push(slot.error);
|
|
1886
2027
|
continue;
|
|
1887
2028
|
}
|
|
1888
2029
|
ingestedChunks++;
|
|
2030
|
+
for (const r of slot.rejected) {
|
|
2031
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { sourceRef, chunkIndex, itemIndex: r.itemIndex, reason: r.reason } });
|
|
2032
|
+
}
|
|
1889
2033
|
const dedupedFacts = [];
|
|
1890
|
-
|
|
2034
|
+
slot.facts.forEach((fact, k) => {
|
|
1891
2035
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
1892
2036
|
if (!seen.has(normalizedTitle)) {
|
|
1893
2037
|
seen.add(normalizedTitle);
|
|
1894
2038
|
dedupedFacts.push(fact);
|
|
2039
|
+
} else {
|
|
2040
|
+
diagBuffer.push({
|
|
2041
|
+
...diagBase,
|
|
2042
|
+
code: "fact_deduplicated",
|
|
2043
|
+
detail: { sourceRef, chunkIndex, itemIndex: slot.itemIndexes[k], reason: "exact_title" }
|
|
2044
|
+
});
|
|
1895
2045
|
}
|
|
1896
|
-
}
|
|
2046
|
+
});
|
|
1897
2047
|
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: slot.ontology_updates });
|
|
1898
2048
|
}
|
|
2049
|
+
for (const reason of ["parse", "llm"]) {
|
|
2050
|
+
const ofReason = failures.filter((f) => f.source === reason);
|
|
2051
|
+
if (ofReason.length === 0) continue;
|
|
2052
|
+
diagBuffer.push({
|
|
2053
|
+
...diagBase,
|
|
2054
|
+
code: "ingest_chunk_failed",
|
|
2055
|
+
detail: {
|
|
2056
|
+
sourceRef,
|
|
2057
|
+
reason,
|
|
2058
|
+
count: ofReason.length,
|
|
2059
|
+
chunkIndexes: ofReason.map((f) => f.chunkIndex).sort((a, b) => a - b).slice(0, 20)
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
1899
2063
|
if (failedChunks === chunks.length) {
|
|
1900
2064
|
throw new WikiIngestEmptyError({
|
|
1901
2065
|
parseFailures: failures,
|
|
@@ -1908,7 +2072,7 @@ var IngestionService = class {
|
|
|
1908
2072
|
try {
|
|
1909
2073
|
if (failedChunks === 0) {
|
|
1910
2074
|
const fullResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1911
|
-
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx);
|
|
2075
|
+
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer);
|
|
1912
2076
|
});
|
|
1913
2077
|
deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
|
|
1914
2078
|
insertedFacts.push(...fullResult.insertedFacts);
|
|
@@ -1916,7 +2080,7 @@ var IngestionService = class {
|
|
|
1916
2080
|
const partialResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1917
2081
|
const flat = [];
|
|
1918
2082
|
for (const slot of orderedChunkFacts) flat.push(...slot.facts);
|
|
1919
|
-
return await this.appendPartialFacts(entityId, sourceRef, flat, tx);
|
|
2083
|
+
return await this.appendPartialFacts(entityId, sourceRef, flat, tx, diagBuffer);
|
|
1920
2084
|
});
|
|
1921
2085
|
insertedFacts.push(...partialResult.insertedDescriptors);
|
|
1922
2086
|
}
|
|
@@ -1930,6 +2094,7 @@ var IngestionService = class {
|
|
|
1930
2094
|
}
|
|
1931
2095
|
return zeroChunkResult(canonical);
|
|
1932
2096
|
}
|
|
2097
|
+
diagBuffer.flush(this.options);
|
|
1933
2098
|
await this.searchService.sync(entityId);
|
|
1934
2099
|
if (failedChunks === 0) {
|
|
1935
2100
|
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
@@ -1938,11 +2103,18 @@ var IngestionService = class {
|
|
|
1938
2103
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
1939
2104
|
} catch (hookErr) {
|
|
1940
2105
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during ingest for ${factId}:`, hookErr);
|
|
2106
|
+
emitDiagnostic(this.options, {
|
|
2107
|
+
entityId,
|
|
2108
|
+
operation: "ingest",
|
|
2109
|
+
trigger: "call",
|
|
2110
|
+
code: "hook_failed",
|
|
2111
|
+
detail: { factId, reason: "on_embedding_persisted" }
|
|
2112
|
+
});
|
|
1941
2113
|
}
|
|
1942
2114
|
}
|
|
1943
2115
|
}
|
|
1944
2116
|
for (const fact of insertedFacts) {
|
|
1945
|
-
await this.embeddingService.embedFact(fact);
|
|
2117
|
+
await this.embeddingService.embedFact(fact, { operation: "ingest", trigger: "call" });
|
|
1946
2118
|
}
|
|
1947
2119
|
this.searchService.evictCache(entityId);
|
|
1948
2120
|
const result = {
|
|
@@ -2088,6 +2260,19 @@ var IngestionService = class {
|
|
|
2088
2260
|
const match = candidates[0];
|
|
2089
2261
|
if (!match) {
|
|
2090
2262
|
if (strictEffective) throw new WikiStrictOntologyViolation(entityId, "edge", edge.type);
|
|
2263
|
+
opts?.diag?.buffer.push({
|
|
2264
|
+
entityId,
|
|
2265
|
+
operation: opts.diag.operation,
|
|
2266
|
+
trigger: "call",
|
|
2267
|
+
code: "edge_dropped",
|
|
2268
|
+
detail: {
|
|
2269
|
+
reason: "manifest_violation",
|
|
2270
|
+
factId: edge.sourceId,
|
|
2271
|
+
edgeType: edge.type,
|
|
2272
|
+
...sourceType ? { sourceNodeType: sourceType } : {},
|
|
2273
|
+
sourceRef: params.sourceRef
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2091
2276
|
continue;
|
|
2092
2277
|
}
|
|
2093
2278
|
validEdges.push({
|
|
@@ -2128,7 +2313,7 @@ var IngestionService = class {
|
|
|
2128
2313
|
* On the partial path the caller never invokes this method; the empty
|
|
2129
2314
|
* arrays stay empty.
|
|
2130
2315
|
*/
|
|
2131
|
-
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx) {
|
|
2316
|
+
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx, diagBuffer) {
|
|
2132
2317
|
const deletedSourceFactIds = [];
|
|
2133
2318
|
const insertedFacts = [];
|
|
2134
2319
|
deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
|
|
@@ -2153,8 +2338,12 @@ var IngestionService = class {
|
|
|
2153
2338
|
}
|
|
2154
2339
|
for (const fact of facts) {
|
|
2155
2340
|
const ontologyFact = fact;
|
|
2156
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
2157
2341
|
const id = generateId("fact_");
|
|
2342
|
+
const validationDrops = [];
|
|
2343
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
2344
|
+
for (const drop of validationDrops) {
|
|
2345
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef, factId: id }));
|
|
2346
|
+
}
|
|
2158
2347
|
hostNodes.push({
|
|
2159
2348
|
id,
|
|
2160
2349
|
type: ontologyFact.okf_type ?? "",
|
|
@@ -2179,6 +2368,7 @@ var IngestionService = class {
|
|
|
2179
2368
|
}
|
|
2180
2369
|
const hostEdges = [];
|
|
2181
2370
|
for (const req of rawEdgeRequests) {
|
|
2371
|
+
const resolveDrops = [];
|
|
2182
2372
|
const resolved = this.ontologyService?.resolveEdges(
|
|
2183
2373
|
entityId,
|
|
2184
2374
|
req.sourceId,
|
|
@@ -2186,8 +2376,12 @@ var IngestionService = class {
|
|
|
2186
2376
|
req.edges,
|
|
2187
2377
|
manifest,
|
|
2188
2378
|
titleIndex,
|
|
2189
|
-
now
|
|
2379
|
+
now,
|
|
2380
|
+
resolveDrops
|
|
2190
2381
|
) ?? [];
|
|
2382
|
+
for (const drop of resolveDrops) {
|
|
2383
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { entityId, operation: "ingest", trigger: "call", sourceRef }));
|
|
2384
|
+
}
|
|
2191
2385
|
for (const e of resolved) {
|
|
2192
2386
|
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
2193
2387
|
}
|
|
@@ -2196,7 +2390,7 @@ var IngestionService = class {
|
|
|
2196
2390
|
entityId,
|
|
2197
2391
|
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
2198
2392
|
tx,
|
|
2199
|
-
{ strict: false }
|
|
2393
|
+
{ strict: false, diag: { buffer: diagBuffer, operation: "ingest" } }
|
|
2200
2394
|
);
|
|
2201
2395
|
return { deletedSourceFactIds, insertedFacts };
|
|
2202
2396
|
}
|
|
@@ -2220,7 +2414,7 @@ var IngestionService = class {
|
|
|
2220
2414
|
* Runs INSIDE the caller's `tx`. Does not open a nested transaction.
|
|
2221
2415
|
* Returns `{ inserted, skippedDuplicate }` for observability.
|
|
2222
2416
|
*/
|
|
2223
|
-
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx) {
|
|
2417
|
+
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx, diagBuffer) {
|
|
2224
2418
|
const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
|
|
2225
2419
|
const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
|
|
2226
2420
|
const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
|
|
@@ -2231,6 +2425,13 @@ var IngestionService = class {
|
|
|
2231
2425
|
for (const fact of dedupedFacts) {
|
|
2232
2426
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
2233
2427
|
if (liveTitles.has(normalizedTitle)) {
|
|
2428
|
+
diagBuffer.push({
|
|
2429
|
+
entityId,
|
|
2430
|
+
operation: "ingest",
|
|
2431
|
+
trigger: "call",
|
|
2432
|
+
code: "fact_deduplicated",
|
|
2433
|
+
detail: { sourceRef, reason: "exact_title" }
|
|
2434
|
+
});
|
|
2234
2435
|
skippedDuplicate++;
|
|
2235
2436
|
continue;
|
|
2236
2437
|
}
|
|
@@ -2817,7 +3018,7 @@ var MaintenanceService = class {
|
|
|
2817
3018
|
permanentlyFailed++;
|
|
2818
3019
|
continue;
|
|
2819
3020
|
}
|
|
2820
|
-
const result = await this.embeddingService.tryEmbedFact(row);
|
|
3021
|
+
const result = await this.embeddingService.tryEmbedFact(row, { operation: "reembed", trigger: "call" });
|
|
2821
3022
|
if (result.ok) embedded++;
|
|
2822
3023
|
else failed++;
|
|
2823
3024
|
}
|
|
@@ -2938,7 +3139,7 @@ var MaintenanceService = class {
|
|
|
2938
3139
|
return { deleted: { entries, tasks: 0 } };
|
|
2939
3140
|
}
|
|
2940
3141
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
2941
|
-
async doRunLibrarian(entityId, promptOverride) {
|
|
3142
|
+
async doRunLibrarian(entityId, promptOverride, trigger = "call") {
|
|
2942
3143
|
const events = await this.eventRepo.getRecent(entityId, 50);
|
|
2943
3144
|
const currentFactsRows = await this.entryRepo.findRecentByEntityId(entityId, 100);
|
|
2944
3145
|
const currentFacts = currentFactsRows.map((f) => {
|
|
@@ -2960,8 +3161,25 @@ var MaintenanceService = class {
|
|
|
2960
3161
|
const facts = Array.isArray(result.facts) ? result.facts : [];
|
|
2961
3162
|
const tasks = Array.isArray(result.tasks) ? result.tasks : [];
|
|
2962
3163
|
const ontologyUpdates = result.ontology_updates;
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
3164
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
3165
|
+
const diagBase = { entityId, operation: "librarian", trigger };
|
|
3166
|
+
const validFacts = [];
|
|
3167
|
+
const validFactItemIndexes = [];
|
|
3168
|
+
facts.forEach((raw, itemIndex) => {
|
|
3169
|
+
const valid = validateFact(raw);
|
|
3170
|
+
if (valid) {
|
|
3171
|
+
validFacts.push(valid);
|
|
3172
|
+
validFactItemIndexes.push(itemIndex);
|
|
3173
|
+
} else {
|
|
3174
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
const validTasks = [];
|
|
3178
|
+
tasks.forEach((raw, itemIndex) => {
|
|
3179
|
+
const valid = validateTask(raw);
|
|
3180
|
+
if (valid) validTasks.push(valid);
|
|
3181
|
+
else diagBuffer.push({ ...diagBase, code: "task_rejected", detail: { itemIndex, reason: taskRejectionReason(raw) } });
|
|
3182
|
+
});
|
|
2965
3183
|
const now = Date.now();
|
|
2966
3184
|
const insertedFacts = [];
|
|
2967
3185
|
await this.db.withTransactionAsync(async (tx) => {
|
|
@@ -2978,7 +3196,7 @@ var MaintenanceService = class {
|
|
|
2978
3196
|
}
|
|
2979
3197
|
const factsForDedupe = await this.entryRepo.findRecentByEntityId(entityId, 100, tx);
|
|
2980
3198
|
const pendingEdges = [];
|
|
2981
|
-
for (const fact of validFacts) {
|
|
3199
|
+
for (const [k, fact] of validFacts.entries()) {
|
|
2982
3200
|
const newTokens = titleTokens(fact.title);
|
|
2983
3201
|
let skip = false;
|
|
2984
3202
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -2993,10 +3211,15 @@ var MaintenanceService = class {
|
|
|
2993
3211
|
}
|
|
2994
3212
|
}
|
|
2995
3213
|
}
|
|
2996
|
-
if (skip)
|
|
3214
|
+
if (skip) {
|
|
3215
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
2997
3218
|
const ontologyFact = fact;
|
|
2998
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
2999
3219
|
const id = generateId("fact_");
|
|
3220
|
+
const validationDrops = [];
|
|
3221
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false, drops: validationDrops }) ?? { okf_type: null, edges: [] };
|
|
3222
|
+
for (const drop of validationDrops) diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: id }));
|
|
3000
3223
|
const factObj = {
|
|
3001
3224
|
id,
|
|
3002
3225
|
entity_id: entityId,
|
|
@@ -3023,6 +3246,7 @@ var MaintenanceService = class {
|
|
|
3023
3246
|
}
|
|
3024
3247
|
}
|
|
3025
3248
|
for (const item of pendingEdges) {
|
|
3249
|
+
const resolveDrops = [];
|
|
3026
3250
|
await this.ontologyService?.resolveAndPersistEdges(
|
|
3027
3251
|
entityId,
|
|
3028
3252
|
item.sourceId,
|
|
@@ -3031,8 +3255,10 @@ var MaintenanceService = class {
|
|
|
3031
3255
|
manifest,
|
|
3032
3256
|
titleIndex,
|
|
3033
3257
|
tx,
|
|
3034
|
-
now
|
|
3258
|
+
now,
|
|
3259
|
+
resolveDrops
|
|
3035
3260
|
);
|
|
3261
|
+
for (const drop of resolveDrops) diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
3036
3262
|
}
|
|
3037
3263
|
for (const task of validTasks) {
|
|
3038
3264
|
const id = generateId("task_");
|
|
@@ -3050,9 +3276,10 @@ var MaintenanceService = class {
|
|
|
3050
3276
|
await this.taskRepo.upsert(taskObj, tx);
|
|
3051
3277
|
}
|
|
3052
3278
|
});
|
|
3279
|
+
diagBuffer.flush(this.options);
|
|
3053
3280
|
await this.searchService.sync(entityId);
|
|
3054
3281
|
for (const fact of insertedFacts) {
|
|
3055
|
-
await this.embeddingService.embedFact(fact);
|
|
3282
|
+
await this.embeddingService.embedFact(fact, { operation: "librarian", trigger });
|
|
3056
3283
|
}
|
|
3057
3284
|
this.searchService.evictCache(entityId);
|
|
3058
3285
|
}
|
|
@@ -3066,6 +3293,8 @@ var MaintenanceService = class {
|
|
|
3066
3293
|
async doRunHeal(entityId, options) {
|
|
3067
3294
|
const promptOverride = options?.promptOverride;
|
|
3068
3295
|
const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
|
|
3296
|
+
const trigger = options?.trigger ?? "call";
|
|
3297
|
+
const diagBase = { entityId, operation: "heal", trigger };
|
|
3069
3298
|
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
3070
3299
|
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
3071
3300
|
}
|
|
@@ -3101,6 +3330,7 @@ var MaintenanceService = class {
|
|
|
3101
3330
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
3102
3331
|
} catch (hookErr) {
|
|
3103
3332
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
|
|
3333
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
3104
3334
|
}
|
|
3105
3335
|
}
|
|
3106
3336
|
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
@@ -3173,22 +3403,38 @@ var MaintenanceService = class {
|
|
|
3173
3403
|
const safeDowngradedSet = /* @__PURE__ */ new Set();
|
|
3174
3404
|
const safeDeletedSet = /* @__PURE__ */ new Set();
|
|
3175
3405
|
const newFacts = [];
|
|
3406
|
+
const newFactItemIndexes = [];
|
|
3176
3407
|
for (const batchResult of outcome.results) {
|
|
3177
3408
|
const mutableIds = new Set(batchResult.batch.map((f) => f.id));
|
|
3178
3409
|
for (const id of batchResult.downgraded) if (mutableIds.has(id)) safeDowngradedSet.add(id);
|
|
3179
3410
|
for (const id of batchResult.deleted) if (mutableIds.has(id)) safeDeletedSet.add(id);
|
|
3180
|
-
newFacts.
|
|
3411
|
+
batchResult.newFacts.forEach((raw, itemIndex) => {
|
|
3412
|
+
newFacts.push(raw);
|
|
3413
|
+
newFactItemIndexes.push(itemIndex);
|
|
3414
|
+
});
|
|
3181
3415
|
}
|
|
3182
3416
|
const safeDowngraded = Array.from(safeDowngradedSet);
|
|
3183
3417
|
const safeDeleted = Array.from(safeDeletedSet);
|
|
3184
|
-
const
|
|
3418
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
3419
|
+
const validNewFacts = [];
|
|
3420
|
+
const validNewFactItemIndexes = [];
|
|
3421
|
+
newFacts.forEach((raw, k) => {
|
|
3422
|
+
const itemIndex = newFactItemIndexes[k];
|
|
3423
|
+
const valid = validateFact(raw);
|
|
3424
|
+
if (valid) {
|
|
3425
|
+
validNewFacts.push(valid);
|
|
3426
|
+
validNewFactItemIndexes.push(itemIndex);
|
|
3427
|
+
} else {
|
|
3428
|
+
diagBuffer.push({ ...diagBase, code: "fact_rejected", detail: { itemIndex, reason: factRejectionReason(raw) } });
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3185
3431
|
const insertedFacts = [];
|
|
3186
3432
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
3187
3433
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3188
3434
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
3189
3435
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
3190
3436
|
const healFactsForDedupe = await this.entryRepo.findInferredTitlesByEntityId(entityId, tx);
|
|
3191
|
-
for (const fact of validNewFacts) {
|
|
3437
|
+
for (const [k, fact] of validNewFacts.entries()) {
|
|
3192
3438
|
const newTokens = titleTokens(fact.title);
|
|
3193
3439
|
let skip = false;
|
|
3194
3440
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
@@ -3202,7 +3448,10 @@ var MaintenanceService = class {
|
|
|
3202
3448
|
}
|
|
3203
3449
|
}
|
|
3204
3450
|
}
|
|
3205
|
-
if (skip)
|
|
3451
|
+
if (skip) {
|
|
3452
|
+
diagBuffer.push({ ...diagBase, code: "fact_deduplicated", detail: { itemIndex: validNewFactItemIndexes[k], reason: "fuzzy_title" } });
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3206
3455
|
const id = generateId("fact_");
|
|
3207
3456
|
const factObj = {
|
|
3208
3457
|
id,
|
|
@@ -3237,16 +3486,21 @@ var MaintenanceService = class {
|
|
|
3237
3486
|
tx
|
|
3238
3487
|
);
|
|
3239
3488
|
});
|
|
3489
|
+
for (const { item, reason } of outcome.skipped) {
|
|
3490
|
+
diagBuffer.push({ ...diagBase, code: "heal_skipped", detail: { factId: item.id, reason } });
|
|
3491
|
+
}
|
|
3492
|
+
diagBuffer.flush(this.options);
|
|
3240
3493
|
await this.searchService.sync(entityId);
|
|
3241
3494
|
for (const factId of uniqueDeletedFactIds) {
|
|
3242
3495
|
try {
|
|
3243
3496
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
3244
3497
|
} catch (hookErr) {
|
|
3245
3498
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal for ${factId}:`, hookErr);
|
|
3499
|
+
emitDiagnostic(this.options, { ...diagBase, code: "hook_failed", detail: { factId, reason: "on_embedding_persisted" } });
|
|
3246
3500
|
}
|
|
3247
3501
|
}
|
|
3248
3502
|
for (const fact of insertedFacts) {
|
|
3249
|
-
await this.embeddingService.embedFact(fact);
|
|
3503
|
+
await this.embeddingService.embedFact(fact, { operation: "heal", trigger });
|
|
3250
3504
|
}
|
|
3251
3505
|
this.searchService.evictCache(entityId);
|
|
3252
3506
|
const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
|
|
@@ -3386,6 +3640,8 @@ var MaintenanceService = class {
|
|
|
3386
3640
|
let failedValidation = 0;
|
|
3387
3641
|
let edgesAdded = 0;
|
|
3388
3642
|
let abortedOntologyOff = false;
|
|
3643
|
+
const diagBuffer = new DiagnosticBuffer();
|
|
3644
|
+
const diagBase = { entityId, operation: "ontologyBackfill", trigger: "call" };
|
|
3389
3645
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3390
3646
|
let { mode: txMode, manifest } = await ontologyService.getEffectiveState(entityId, tx);
|
|
3391
3647
|
if (txMode === "off") {
|
|
@@ -3410,6 +3666,7 @@ var MaintenanceService = class {
|
|
|
3410
3666
|
continue;
|
|
3411
3667
|
}
|
|
3412
3668
|
applied.add(fact.id);
|
|
3669
|
+
const validationDrops = [];
|
|
3413
3670
|
const normalized = ontologyService.validateAndNormalizeFact(
|
|
3414
3671
|
{
|
|
3415
3672
|
title: fact.title,
|
|
@@ -3420,8 +3677,11 @@ var MaintenanceService = class {
|
|
|
3420
3677
|
edges: classification.edges
|
|
3421
3678
|
},
|
|
3422
3679
|
manifest,
|
|
3423
|
-
{ strict: false }
|
|
3680
|
+
{ strict: false, drops: validationDrops }
|
|
3424
3681
|
);
|
|
3682
|
+
for (const drop of validationDrops) {
|
|
3683
|
+
diagBuffer.push(edgeDropDiagnostic(drop, { ...diagBase, factId: fact.id }));
|
|
3684
|
+
}
|
|
3425
3685
|
if (!normalized.okf_type) {
|
|
3426
3686
|
failedValidation++;
|
|
3427
3687
|
continue;
|
|
@@ -3435,7 +3695,8 @@ var MaintenanceService = class {
|
|
|
3435
3695
|
}
|
|
3436
3696
|
}
|
|
3437
3697
|
for (const item of pendingEdges) {
|
|
3438
|
-
|
|
3698
|
+
const resolveDrops = [];
|
|
3699
|
+
const added = await ontologyService.resolveAndPersistEdges(
|
|
3439
3700
|
entityId,
|
|
3440
3701
|
item.sourceId,
|
|
3441
3702
|
item.sourceType,
|
|
@@ -3443,11 +3704,17 @@ var MaintenanceService = class {
|
|
|
3443
3704
|
manifest,
|
|
3444
3705
|
titleIndex,
|
|
3445
3706
|
tx,
|
|
3446
|
-
now
|
|
3707
|
+
now,
|
|
3708
|
+
resolveDrops
|
|
3447
3709
|
);
|
|
3710
|
+
edgesAdded += added;
|
|
3711
|
+
for (const drop of resolveDrops) {
|
|
3712
|
+
diagBuffer.push(edgeDropDiagnostic(drop, diagBase));
|
|
3713
|
+
}
|
|
3448
3714
|
}
|
|
3449
3715
|
await this.entryRepo.markOntologyChecked(batch.map((f) => f.id), entityId, now, tx);
|
|
3450
3716
|
});
|
|
3717
|
+
diagBuffer.flush(this.options);
|
|
3451
3718
|
return { typed, failedValidation, edgesAdded, abortedOntologyOff };
|
|
3452
3719
|
}
|
|
3453
3720
|
_validatePruneDuration(value, name) {
|
|
@@ -3849,7 +4116,7 @@ var ImportExportService = class {
|
|
|
3849
4116
|
title: clipped?.title ?? fact.title,
|
|
3850
4117
|
body: clipped?.body ?? fact.body,
|
|
3851
4118
|
tags: Array.isArray(fact.tags) || typeof fact.tags === "string" ? fact.tags : []
|
|
3852
|
-
});
|
|
4119
|
+
}, { operation: "importDump", trigger: "call" });
|
|
3853
4120
|
if (!embedded) {
|
|
3854
4121
|
await this.embeddingService.notifyEmbeddingPersisted(entityId, fact.id, null);
|
|
3855
4122
|
}
|
|
@@ -3874,6 +4141,11 @@ var ImportExportService = class {
|
|
|
3874
4141
|
`[WikiMemory] onEmbeddingPersisted hook failed for preserved-blob fact ${fact.id}:`,
|
|
3875
4142
|
hookErr
|
|
3876
4143
|
);
|
|
4144
|
+
this.embeddingService.reportHookFailed(
|
|
4145
|
+
{ operation: "importDump", trigger: "call" },
|
|
4146
|
+
entityId,
|
|
4147
|
+
fact.id
|
|
4148
|
+
);
|
|
3877
4149
|
}
|
|
3878
4150
|
}
|
|
3879
4151
|
}
|
|
@@ -3890,6 +4162,11 @@ var ImportExportService = class {
|
|
|
3890
4162
|
`[WikiMemory] onEmbeddingPersisted(vector=null) hook failed for soft-deleted fact ${factId}:`,
|
|
3891
4163
|
hookErr
|
|
3892
4164
|
);
|
|
4165
|
+
this.embeddingService.reportHookFailed(
|
|
4166
|
+
{ operation: "importDump", trigger: "call" },
|
|
4167
|
+
entityId,
|
|
4168
|
+
factId
|
|
4169
|
+
);
|
|
3893
4170
|
}
|
|
3894
4171
|
}
|
|
3895
4172
|
}
|
|
@@ -4052,7 +4329,7 @@ var EmbeddingService = class {
|
|
|
4052
4329
|
});
|
|
4053
4330
|
}
|
|
4054
4331
|
}
|
|
4055
|
-
async tryEmbedFact(fact) {
|
|
4332
|
+
async tryEmbedFact(fact, ctx) {
|
|
4056
4333
|
const embedFn = this.options.llmProvider.embed;
|
|
4057
4334
|
if (typeof embedFn !== "function") return { ok: false, kind: "no_provider" };
|
|
4058
4335
|
let tagsStr;
|
|
@@ -4074,6 +4351,7 @@ var EmbeddingService = class {
|
|
|
4074
4351
|
const vector = await embedFn(text);
|
|
4075
4352
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
4076
4353
|
console.warn(`[WikiMemory] embedFact: embed() returned an invalid vector for ${fact.id}; skipping.`);
|
|
4354
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "invalid_vector");
|
|
4077
4355
|
await this.markFailure(fact.id, "invalid_vector");
|
|
4078
4356
|
return { ok: false, kind: "invalid_vector" };
|
|
4079
4357
|
}
|
|
@@ -4087,11 +4365,13 @@ var EmbeddingService = class {
|
|
|
4087
4365
|
}
|
|
4088
4366
|
if (hasNonFinite) {
|
|
4089
4367
|
console.warn(`[WikiMemory] embedFact: embed() returned values that overflow float32 for ${fact.id}; skipping.`);
|
|
4368
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "float32_overflow");
|
|
4090
4369
|
await this.markFailure(fact.id, "float32_overflow");
|
|
4091
4370
|
return { ok: false, kind: "float32_overflow" };
|
|
4092
4371
|
}
|
|
4093
4372
|
} catch (err) {
|
|
4094
4373
|
console.warn(`[WikiMemory] embedFact failed for ${fact.id}:`, err);
|
|
4374
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "embed_threw");
|
|
4095
4375
|
await this.markFailure(fact.id, "provider_error");
|
|
4096
4376
|
return { ok: false, kind: "provider_error" };
|
|
4097
4377
|
}
|
|
@@ -4101,15 +4381,31 @@ var EmbeddingService = class {
|
|
|
4101
4381
|
await this.entryRepo.updateEmbeddingBlob(fact.id, blob);
|
|
4102
4382
|
} catch (err) {
|
|
4103
4383
|
console.warn(`[WikiMemory] embedFact: persisting embedding failed for ${fact.id}:`, err);
|
|
4384
|
+
this.reportEmbed(ctx, fact, "embedding_failed", "persist_failed");
|
|
4104
4385
|
return { ok: false, kind: "storage_error" };
|
|
4105
4386
|
}
|
|
4106
4387
|
try {
|
|
4107
4388
|
await this.notifyEmbeddingPersisted(fact.entity_id, fact.id, float32Vector);
|
|
4108
4389
|
} catch (hookErr) {
|
|
4109
4390
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed for ${fact.id}:`, hookErr);
|
|
4391
|
+
this.reportEmbed(ctx, fact, "hook_failed", "on_embedding_persisted");
|
|
4110
4392
|
}
|
|
4111
4393
|
return { ok: true, dimension: float32Vector.length };
|
|
4112
4394
|
}
|
|
4395
|
+
reportEmbed(ctx, fact, code, reason) {
|
|
4396
|
+
if (!ctx) return;
|
|
4397
|
+
emitDiagnostic(this.options, {
|
|
4398
|
+
code,
|
|
4399
|
+
operation: ctx.operation,
|
|
4400
|
+
trigger: ctx.trigger,
|
|
4401
|
+
entityId: fact.entity_id,
|
|
4402
|
+
detail: { factId: fact.id, reason }
|
|
4403
|
+
});
|
|
4404
|
+
}
|
|
4405
|
+
/** For callers that invoke `notifyEmbeddingPersisted` directly and catch its failure. */
|
|
4406
|
+
reportHookFailed(ctx, entityId, factId) {
|
|
4407
|
+
this.reportEmbed(ctx, { id: factId, entity_id: entityId }, "hook_failed", "on_embedding_persisted");
|
|
4408
|
+
}
|
|
4113
4409
|
/** Marker writes must never fail the caller. Only marker-eligible kinds reach here. */
|
|
4114
4410
|
async markFailure(id, kind) {
|
|
4115
4411
|
try {
|
|
@@ -4123,8 +4419,8 @@ var EmbeddingService = class {
|
|
|
4123
4419
|
* ImportExportService branch on `if (!embedded)`; a discriminated result
|
|
4124
4420
|
* object is always truthy and would silently disable that branch.
|
|
4125
4421
|
*/
|
|
4126
|
-
async embedFact(fact) {
|
|
4127
|
-
const result = await this.tryEmbedFact(fact);
|
|
4422
|
+
async embedFact(fact, ctx) {
|
|
4423
|
+
const result = await this.tryEmbedFact(fact, ctx);
|
|
4128
4424
|
return result.ok;
|
|
4129
4425
|
}
|
|
4130
4426
|
async notifyEmbeddingPersisted(entityId, factId, vector) {
|
|
@@ -4264,6 +4560,7 @@ function selectWithFloors(sortedRows, floors, maxResults) {
|
|
|
4264
4560
|
}
|
|
4265
4561
|
|
|
4266
4562
|
// src/services/RetrievalService.ts
|
|
4563
|
+
var EMPTY_ID_SET = /* @__PURE__ */ new Set();
|
|
4267
4564
|
var RetrievalService = class {
|
|
4268
4565
|
constructor(options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService) {
|
|
4269
4566
|
this.options = options;
|
|
@@ -4296,6 +4593,7 @@ var RetrievalService = class {
|
|
|
4296
4593
|
}
|
|
4297
4594
|
const rawMaxResults = options?.maxResults ?? config?.maxResults ?? config?.maxFtsResults ?? 10;
|
|
4298
4595
|
const maxResults = Number.isFinite(rawMaxResults) ? Math.max(0, Math.trunc(rawMaxResults)) : 10;
|
|
4596
|
+
const excludeDrafts = (options?.excludeDrafts ?? config?.excludeDrafts ?? false) === true;
|
|
4299
4597
|
const trimmedQuery = query.trim();
|
|
4300
4598
|
const sanitizedTierFloors = trimmedQuery && exposeMetadata ? validateTierFloors(
|
|
4301
4599
|
entityIds,
|
|
@@ -4315,6 +4613,9 @@ var RetrievalService = class {
|
|
|
4315
4613
|
if (maxResults === 0) ; else if (trimmedQuery) {
|
|
4316
4614
|
let usedEmbed = false;
|
|
4317
4615
|
const scoredEntityIds = this._filterScoredEntities(entityIds, sanitizedTierWeights, options?.includeZeroWeightEntities);
|
|
4616
|
+
const draftIds = excludeDrafts && scoredEntityIds.length > 0 ? await this.entryRepo.findDraftIdsByEntityIds(scoredEntityIds) : EMPTY_ID_SET;
|
|
4617
|
+
const draftPad = draftIds.size;
|
|
4618
|
+
const padLimit = (n) => n >= Number.MAX_SAFE_INTEGER ? n : n + draftPad;
|
|
4318
4619
|
if (scoredEntityIds.length === 0) {
|
|
4319
4620
|
usedEmbed = true;
|
|
4320
4621
|
} else if (!skipEmbed && embedFn) {
|
|
@@ -4348,7 +4649,7 @@ var RetrievalService = class {
|
|
|
4348
4649
|
let miniSearchScores;
|
|
4349
4650
|
if (effectivePreFilterLimit !== void 0) {
|
|
4350
4651
|
populateCache = false;
|
|
4351
|
-
const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER);
|
|
4652
|
+
const preResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, Number.MAX_SAFE_INTEGER).filter((r) => !draftIds.has(r.id));
|
|
4352
4653
|
if (preResults.length === 0) {
|
|
4353
4654
|
candidateRows = null;
|
|
4354
4655
|
} else {
|
|
@@ -4370,9 +4671,9 @@ var RetrievalService = class {
|
|
|
4370
4671
|
}
|
|
4371
4672
|
} else {
|
|
4372
4673
|
if (useRanker) {
|
|
4373
|
-
candidateRows = await this.entryRepo.findMetadataByEntityIds(scoredEntityIds);
|
|
4674
|
+
candidateRows = this._withoutDrafts(await this.entryRepo.findMetadataByEntityIds(scoredEntityIds), draftIds);
|
|
4374
4675
|
} else {
|
|
4375
|
-
candidateRows = await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds);
|
|
4676
|
+
candidateRows = this._withoutDrafts(await this.entryRepo.findWithEmbeddingsByEntityIds(scoredEntityIds), draftIds);
|
|
4376
4677
|
}
|
|
4377
4678
|
if (weight !== void 0 && weight < 1) {
|
|
4378
4679
|
miniSearchScores = this.searchService.getMiniSearchScores(trimmedQuery, scoredEntityIds);
|
|
@@ -4402,7 +4703,7 @@ var RetrievalService = class {
|
|
|
4402
4703
|
candidateRows: rowsForEntity,
|
|
4403
4704
|
weight,
|
|
4404
4705
|
miniSearchScores,
|
|
4405
|
-
limit: Math.max(maxResults * 2, maxResults + 50)
|
|
4706
|
+
limit: padLimit(Math.max(maxResults * 2, maxResults + 50))
|
|
4406
4707
|
});
|
|
4407
4708
|
return ranked.map((row) => ({ ...row, entity_id: scopedEntityId }));
|
|
4408
4709
|
})
|
|
@@ -4616,7 +4917,7 @@ var RetrievalService = class {
|
|
|
4616
4917
|
});
|
|
4617
4918
|
} else if (policy === "keyword") {
|
|
4618
4919
|
const hasActiveFloorsKeywordFallback = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
|
|
4619
|
-
const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
|
|
4920
|
+
const keywordOversampledLimit = hasActiveFloorsKeywordFallback ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
|
|
4620
4921
|
const preFilteredIds = effectivePreFilterLimit !== void 0 ? new Set(candidateRows.map((r) => r.id)) : void 0;
|
|
4621
4922
|
const keywordResults = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, keywordOversampledLimit);
|
|
4622
4923
|
const topResults = preFilteredIds === void 0 ? keywordResults : keywordResults.filter((r) => preFilteredIds.has(r.id));
|
|
@@ -4657,6 +4958,7 @@ var RetrievalService = class {
|
|
|
4657
4958
|
// read() re-sorts after applying tier weights
|
|
4658
4959
|
});
|
|
4659
4960
|
}
|
|
4961
|
+
if (draftPad > 0) scored = scored.filter((s) => !draftIds.has(s.id));
|
|
4660
4962
|
if (scored.length > 0) {
|
|
4661
4963
|
scored = scored.map((row) => ({
|
|
4662
4964
|
...row,
|
|
@@ -4719,8 +5021,8 @@ var RetrievalService = class {
|
|
|
4719
5021
|
}
|
|
4720
5022
|
if (!usedEmbed && scoredEntityIds.length > 0) {
|
|
4721
5023
|
const hasActiveFloors = sanitizedTierFloors !== void 0 && Object.values(sanitizedTierFloors).some((f) => f > 0);
|
|
4722
|
-
const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : Math.max(maxResults * 2, maxResults + 50);
|
|
4723
|
-
const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit);
|
|
5024
|
+
const fallbackOversampledLimit = hasActiveFloors ? Number.MAX_SAFE_INTEGER : padLimit(Math.max(maxResults * 2, maxResults + 50));
|
|
5025
|
+
const results = this.searchService.searchKeyword(trimmedQuery, scoredEntityIds, fallbackOversampledLimit).filter((r) => !draftIds.has(r.id));
|
|
4724
5026
|
const candidates = results.map((r) => ({
|
|
4725
5027
|
id: r.id,
|
|
4726
5028
|
entity_id: r.entity_id,
|
|
@@ -4744,7 +5046,7 @@ var RetrievalService = class {
|
|
|
4744
5046
|
await this.entryRepo.trackAccess(ids, now);
|
|
4745
5047
|
}
|
|
4746
5048
|
} else {
|
|
4747
|
-
facts = await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
|
|
5049
|
+
facts = excludeDrafts ? await this.entryRepo.findRecentByEntityIds(entityIds, maxResults, void 0, { excludeDrafts: true }) : await this.entryRepo.findRecentByEntityIds(entityIds, maxResults);
|
|
4748
5050
|
}
|
|
4749
5051
|
const eventsLimit = Math.min(10 * entityIds.length, 100);
|
|
4750
5052
|
const [tasks, events] = await Promise.all([
|
|
@@ -4780,6 +5082,9 @@ var RetrievalService = class {
|
|
|
4780
5082
|
_tieBreakSort(items) {
|
|
4781
5083
|
items.sort((a, b) => this._compareScoredRows(a, b));
|
|
4782
5084
|
}
|
|
5085
|
+
_withoutDrafts(rows, draftIds) {
|
|
5086
|
+
return draftIds.size === 0 ? rows : rows.filter((row) => !draftIds.has(row.id));
|
|
5087
|
+
}
|
|
4783
5088
|
/**
|
|
4784
5089
|
* Comparator for score + deterministic tie-break fields.
|
|
4785
5090
|
* Negative return means "a ranks ahead of b" for descending score order.
|
|
@@ -4924,7 +5229,11 @@ var WriteService = class {
|
|
|
4924
5229
|
if (shouldRunLibrarian) {
|
|
4925
5230
|
try {
|
|
4926
5231
|
this.jobManager.acquireLock("librarian", entityId);
|
|
4927
|
-
|
|
5232
|
+
const stage = { job: "librarian" };
|
|
5233
|
+
this.runLibrarianThenMaybeHeal(entityId, librarianCount, prevMemoryCheckpoint, stage).catch((err) => {
|
|
5234
|
+
console.error(err);
|
|
5235
|
+
this.reportBackgroundFailure(entityId, stage.job);
|
|
5236
|
+
}).finally(() => {
|
|
4928
5237
|
this.jobManager.releaseLock("librarian", entityId);
|
|
4929
5238
|
});
|
|
4930
5239
|
} catch (e) {
|
|
@@ -4932,17 +5241,21 @@ var WriteService = class {
|
|
|
4932
5241
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
|
|
4933
5242
|
}
|
|
4934
5243
|
} else if (!this.jobManager.isBlocked("librarian", entityId)) {
|
|
4935
|
-
this.maybeRunHeal(entityId, eventCount).catch(
|
|
5244
|
+
this.maybeRunHeal(entityId, eventCount).catch((err) => {
|
|
5245
|
+
console.error(err);
|
|
5246
|
+
this.reportBackgroundFailure(entityId, "heal");
|
|
5247
|
+
});
|
|
4936
5248
|
}
|
|
4937
5249
|
}
|
|
4938
|
-
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
|
|
5250
|
+
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint, stage) {
|
|
4939
5251
|
try {
|
|
4940
|
-
await this.maintenanceService.doRunLibrarian(entityId);
|
|
5252
|
+
await this.maintenanceService.doRunLibrarian(entityId, void 0, "auto");
|
|
4941
5253
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: currentEventCount }, this.db);
|
|
4942
5254
|
} catch (e) {
|
|
4943
5255
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
|
|
4944
5256
|
throw e;
|
|
4945
5257
|
}
|
|
5258
|
+
stage.job = "heal";
|
|
4946
5259
|
await this.maybeRunHeal(entityId, currentEventCount);
|
|
4947
5260
|
}
|
|
4948
5261
|
/**
|
|
@@ -4958,7 +5271,7 @@ var WriteService = class {
|
|
|
4958
5271
|
const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
|
|
4959
5272
|
if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
|
|
4960
5273
|
try {
|
|
4961
|
-
const result = await this.maintenanceService.doRunHeal(entityId);
|
|
5274
|
+
const result = await this.maintenanceService.doRunHeal(entityId, { trigger: "auto" });
|
|
4962
5275
|
if (result.remaining === 0) {
|
|
4963
5276
|
await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
|
|
4964
5277
|
}
|
|
@@ -4967,8 +5280,17 @@ var WriteService = class {
|
|
|
4967
5280
|
}
|
|
4968
5281
|
}
|
|
4969
5282
|
}
|
|
5283
|
+
reportBackgroundFailure(entityId, job) {
|
|
5284
|
+
emitDiagnostic(this.options, {
|
|
5285
|
+
code: "background_job_failed",
|
|
5286
|
+
operation: job,
|
|
5287
|
+
trigger: "auto",
|
|
5288
|
+
entityId,
|
|
5289
|
+
detail: { reason: "unhandled_rejection" }
|
|
5290
|
+
});
|
|
5291
|
+
}
|
|
4970
5292
|
};
|
|
4971
5293
|
|
|
4972
|
-
export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
|
|
4973
|
-
//# sourceMappingURL=chunk-
|
|
4974
|
-
//# sourceMappingURL=chunk-
|
|
5294
|
+
export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, DEFAULT_MAX_EMBED_CHARS, DiagnosticBuffer, EMBED_CHARS_CEILING, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiDraftNotFound, WikiDuplicateHashError, WikiGraphNodeOwnershipConflict, WikiIngestEmptyError, WikiInvalidReadOptions, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, typeSatisfies, validateInlineEdges, validateManifest };
|
|
5295
|
+
//# sourceMappingURL=chunk-G5OR2VWD.mjs.map
|
|
5296
|
+
//# sourceMappingURL=chunk-G5OR2VWD.mjs.map
|