@equationalapplications/core-llm-wiki 5.1.0 → 5.2.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/dist/{chunk-DM7BKDCV.mjs → chunk-J3N3WRK7.mjs} +85 -8
- package/dist/chunk-J3N3WRK7.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +305 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +224 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-DCRNie7k.d.mts → testing-DyXISRwS.d.mts} +137 -1
- package/dist/{testing-DCRNie7k.d.ts → testing-DyXISRwS.d.ts} +137 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +82 -5
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-DM7BKDCV.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-DyXISRwS.mjs';
|
|
2
|
+
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as WikiDuplicateHashError, J as WikiEdge, K as WikiEvent, N as WikiFact, Q as WikiMemoryTestAccess, T as WikiOutboxEvent, U as WikiTask, X as WikiTransactionError } from './testing-DyXISRwS.mjs';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-DyXISRwS.js';
|
|
2
|
+
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as WikiDuplicateHashError, J as WikiEdge, K as WikiEvent, N as WikiFact, Q as WikiMemoryTestAccess, T as WikiOutboxEvent, U as WikiTask, X as WikiTransactionError } from './testing-DyXISRwS.js';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -175,6 +175,15 @@ var WikiBusyError = class extends Error {
|
|
|
175
175
|
this.entityId = entityId;
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
+
var WikiDuplicateHashError = class extends Error {
|
|
179
|
+
constructor(params) {
|
|
180
|
+
super(`Duplicate source hash for entity ${params.entityId}; another ref already holds this content`);
|
|
181
|
+
this.name = "WikiDuplicateHashError";
|
|
182
|
+
this.canonical = params.canonical;
|
|
183
|
+
this.sourceHash = params.sourceHash;
|
|
184
|
+
this.entityId = params.entityId;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
178
187
|
var WikiTransactionError = class extends Error {
|
|
179
188
|
constructor(message, options) {
|
|
180
189
|
super(message, options);
|
|
@@ -1080,12 +1089,154 @@ var EntryRepository = class extends BaseRepository {
|
|
|
1080
1089
|
const row = await executor.getFirstAsync(
|
|
1081
1090
|
`SELECT source_hash FROM ${this.prefix}entries
|
|
1082
1091
|
WHERE entity_id = ? AND source_ref = ? AND deleted_at IS NULL
|
|
1083
|
-
ORDER BY updated_at DESC
|
|
1092
|
+
ORDER BY updated_at DESC, id ASC
|
|
1084
1093
|
LIMIT 1`,
|
|
1085
1094
|
[entityId, sourceRef]
|
|
1086
1095
|
);
|
|
1087
1096
|
return row?.source_hash ?? null;
|
|
1088
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Batch version of {@link findLatestSourceHash}. Returns a Map covering every
|
|
1100
|
+
* requested ref, where the value is the source_hash from the row with the
|
|
1101
|
+
* most-recently-updated live fact for that ref, or null when no live row exists.
|
|
1102
|
+
*
|
|
1103
|
+
* The SQL uses ROW_NUMBER() OVER (PARTITION BY source_ref ORDER BY updated_at
|
|
1104
|
+
* DESC, id ASC) — NOT MAX(source_hash). Aggregation with MAX(source_hash) is
|
|
1105
|
+
* wrong because MAX computes independently across grouped rows; the hash must
|
|
1106
|
+
* come from the exact row that wins MAX(updated_at). The `id ASC` tie-break
|
|
1107
|
+
* keeps selection deterministic when two live rows share `updated_at`.
|
|
1108
|
+
*
|
|
1109
|
+
* Source refs are de-duplicated and processed in chunks so the per-query bind
|
|
1110
|
+
* parameter count stays under SQLite's `SQLITE_MAX_VARIABLE_NUMBER` (default
|
|
1111
|
+
* 999, leaving one slot for `entity_id`).
|
|
1112
|
+
*
|
|
1113
|
+
* Empty input is a synchronous early return with zero SQL calls.
|
|
1114
|
+
*/
|
|
1115
|
+
async findLatestSourceHashes(entityId, sourceRefs, tx) {
|
|
1116
|
+
const out = /* @__PURE__ */ new Map();
|
|
1117
|
+
if (sourceRefs.length === 0) return out;
|
|
1118
|
+
const dedupedRefs = Array.from(new Set(sourceRefs));
|
|
1119
|
+
for (const ref of dedupedRefs) {
|
|
1120
|
+
out.set(ref, null);
|
|
1121
|
+
}
|
|
1122
|
+
const executor = this.getExecutor(tx);
|
|
1123
|
+
const chunkLimit = Math.max(1, this.chunkSize - 1);
|
|
1124
|
+
for (let i = 0; i < dedupedRefs.length; i += chunkLimit) {
|
|
1125
|
+
const chunk = dedupedRefs.slice(i, i + chunkLimit);
|
|
1126
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
1127
|
+
const rows = await executor.getAllAsync(
|
|
1128
|
+
`WITH ranked AS (
|
|
1129
|
+
SELECT source_ref, source_hash,
|
|
1130
|
+
ROW_NUMBER() OVER (
|
|
1131
|
+
PARTITION BY source_ref
|
|
1132
|
+
ORDER BY updated_at DESC, id ASC
|
|
1133
|
+
) as rn
|
|
1134
|
+
FROM ${this.prefix}entries
|
|
1135
|
+
WHERE entity_id = ? AND source_ref IN (${placeholders}) AND deleted_at IS NULL
|
|
1136
|
+
)
|
|
1137
|
+
SELECT source_ref, source_hash
|
|
1138
|
+
FROM ranked
|
|
1139
|
+
WHERE rn = 1`,
|
|
1140
|
+
[entityId, ...chunk]
|
|
1141
|
+
);
|
|
1142
|
+
for (const r of rows) {
|
|
1143
|
+
out.set(r.source_ref, r.source_hash);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Return the live source_refs for an entity that hold the given source_hash.
|
|
1150
|
+
* Used by the ingestDocument duplicate-hash guard and by hosts auditing
|
|
1151
|
+
* duplicate-content collisions. Sorted `COLLATE BINARY` so the canonical
|
|
1152
|
+
* ref (the code-unit-minimum of the set) is stable across deploys.
|
|
1153
|
+
*/
|
|
1154
|
+
async findSourceRefsByHash(entityId, sourceHash, tx) {
|
|
1155
|
+
const executor = this.getExecutor(tx);
|
|
1156
|
+
const rows = await executor.getAllAsync(
|
|
1157
|
+
`SELECT source_ref FROM ${this.prefix}entries
|
|
1158
|
+
WHERE entity_id = ? AND source_hash = ? AND deleted_at IS NULL
|
|
1159
|
+
AND source_ref IS NOT NULL
|
|
1160
|
+
GROUP BY source_ref
|
|
1161
|
+
ORDER BY source_ref COLLATE BINARY`,
|
|
1162
|
+
[entityId, sourceHash]
|
|
1163
|
+
);
|
|
1164
|
+
return rows.map((r) => r.source_ref);
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Per-sourceRef rollup for an entity: one row per live source_ref, with the
|
|
1168
|
+
* most-recently-updated live hash (NOT the lexically-max hash) and a live
|
|
1169
|
+
* fact count.
|
|
1170
|
+
*
|
|
1171
|
+
* The hash comes from the same row that wins ROW_NUMBER() over updated_at
|
|
1172
|
+
* DESC, so a ref with multiple live hashes (e.g. from import) reports the
|
|
1173
|
+
* latest one — matching single-doc `findLatestSourceHash` semantics.
|
|
1174
|
+
*
|
|
1175
|
+
* Sort is COLLATE BINARY: locale-dependent ordering re-mints identity on
|
|
1176
|
+
* every deploy, which is the bug this whole section exists to prevent.
|
|
1177
|
+
*/
|
|
1178
|
+
async listSourceRefs(entityId, tx) {
|
|
1179
|
+
const executor = this.getExecutor(tx);
|
|
1180
|
+
const rows = await executor.getAllAsync(
|
|
1181
|
+
`WITH ranked AS (
|
|
1182
|
+
SELECT source_ref, source_hash, updated_at,
|
|
1183
|
+
ROW_NUMBER() OVER (
|
|
1184
|
+
PARTITION BY source_ref
|
|
1185
|
+
ORDER BY updated_at DESC, id ASC
|
|
1186
|
+
) AS rn,
|
|
1187
|
+
COUNT(*) OVER (PARTITION BY source_ref) AS fact_count
|
|
1188
|
+
FROM ${this.prefix}entries
|
|
1189
|
+
WHERE entity_id = ? AND deleted_at IS NULL AND source_ref IS NOT NULL
|
|
1190
|
+
)
|
|
1191
|
+
SELECT source_ref,
|
|
1192
|
+
source_hash AS source_hash,
|
|
1193
|
+
fact_count AS fact_count,
|
|
1194
|
+
updated_at AS last_ingested_at
|
|
1195
|
+
FROM ranked
|
|
1196
|
+
WHERE rn = 1
|
|
1197
|
+
ORDER BY source_ref COLLATE BINARY`,
|
|
1198
|
+
[entityId]
|
|
1199
|
+
);
|
|
1200
|
+
return rows.map((r) => ({
|
|
1201
|
+
sourceRef: r.source_ref,
|
|
1202
|
+
sourceHash: r.source_hash,
|
|
1203
|
+
factCount: Number(r.fact_count),
|
|
1204
|
+
lastIngestedAt: Number(r.last_ingested_at)
|
|
1205
|
+
}));
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Count live entries for an entity across all source_refs/source_hashes.
|
|
1209
|
+
* Used by forget({ dryRun, clearAll: true }).
|
|
1210
|
+
*/
|
|
1211
|
+
async countLiveByEntityId(entityId, tx) {
|
|
1212
|
+
const executor = this.getExecutor(tx);
|
|
1213
|
+
const row = await executor.getFirstAsync(
|
|
1214
|
+
`SELECT COUNT(*) AS cnt FROM ${this.prefix}entries
|
|
1215
|
+
WHERE entity_id = ? AND deleted_at IS NULL`,
|
|
1216
|
+
[entityId]
|
|
1217
|
+
);
|
|
1218
|
+
return row?.cnt ?? 0;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Count live entries matching a source filter (either or both may be null).
|
|
1222
|
+
* Used by forget({ dryRun: true }) with sourceRef/sourceHash.
|
|
1223
|
+
*/
|
|
1224
|
+
async countLiveBySource(entityId, sourceRef, sourceHash, tx) {
|
|
1225
|
+
const executor = this.getExecutor(tx);
|
|
1226
|
+
let q = `SELECT COUNT(*) AS cnt FROM ${this.prefix}entries
|
|
1227
|
+
WHERE entity_id = ? AND deleted_at IS NULL`;
|
|
1228
|
+
const args = [entityId];
|
|
1229
|
+
if (sourceRef !== null) {
|
|
1230
|
+
q += ` AND source_ref = ?`;
|
|
1231
|
+
args.push(sourceRef);
|
|
1232
|
+
}
|
|
1233
|
+
if (sourceHash !== null) {
|
|
1234
|
+
q += ` AND source_hash = ?`;
|
|
1235
|
+
args.push(sourceHash);
|
|
1236
|
+
}
|
|
1237
|
+
const row = await executor.getFirstAsync(q, args);
|
|
1238
|
+
return row?.cnt ?? 0;
|
|
1239
|
+
}
|
|
1089
1240
|
async findMetadataByIds(ids, tx) {
|
|
1090
1241
|
if (ids.length === 0) return [];
|
|
1091
1242
|
const executor = this.getExecutor(tx);
|
|
@@ -1637,6 +1788,18 @@ var TaskRepository = class extends BaseRepository {
|
|
|
1637
1788
|
}
|
|
1638
1789
|
return result.changes;
|
|
1639
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Count live tasks for an entity. Used by forget({ dryRun, clearAll: true }).
|
|
1793
|
+
*/
|
|
1794
|
+
async countLiveByEntityId(entityId, tx) {
|
|
1795
|
+
const executor = this.getExecutor(tx);
|
|
1796
|
+
const row = await executor.getFirstAsync(
|
|
1797
|
+
`SELECT COUNT(*) AS cnt FROM ${this.prefix}tasks
|
|
1798
|
+
WHERE entity_id = ? AND deleted_at IS NULL`,
|
|
1799
|
+
[entityId]
|
|
1800
|
+
);
|
|
1801
|
+
return row?.cnt ?? 0;
|
|
1802
|
+
}
|
|
1640
1803
|
};
|
|
1641
1804
|
|
|
1642
1805
|
// src/repositories/EventRepository.ts
|
|
@@ -2614,6 +2777,19 @@ var JobManager = class {
|
|
|
2614
2777
|
return this.activeMaintenanceJobs.has(this.lockKeyFns[operation](entityId)) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this._isReembedActive(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2615
2778
|
case "prune":
|
|
2616
2779
|
return this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._healKey(entityId)) || this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId)) || this._isReembedActive(entityId) || this._isIngestActiveFor(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2780
|
+
case "reembed":
|
|
2781
|
+
return this.activeMaintenanceJobs.has(this._reembedKey(entityId)) || this.activeMaintenanceJobs.has(this._globalReembedKey()) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._healKey(entityId)) || this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId)) || this._isIngestActiveFor(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2782
|
+
case "global_reembed":
|
|
2783
|
+
return this.activeMaintenanceJobs.has(this._globalReembedKey()) || this._isAnyMaintenanceActiveWithSuffix(":reembed") || this._isAnyMaintenanceActiveWithSuffix(":prune") || this._isAnyMaintenanceActiveWithSuffix(":librarian") || this._isAnyMaintenanceActiveWithSuffix(":heal") || this._isAnyMaintenanceActiveWithSuffix(":ontologyBackfill") || this.activeIngestJobs.size > 0 || this._isAnyMaintenanceActiveWithSuffix(":import") || this._isAnyMaintenanceActiveWithSuffix(":forget");
|
|
2784
|
+
case "import":
|
|
2785
|
+
case "forget": {
|
|
2786
|
+
const selfKey = operation === "import" ? this._importKey(entityId) : this._forgetKey(entityId);
|
|
2787
|
+
return this.activeMaintenanceJobs.has(selfKey) || this.activeMaintenanceJobs.has(this._librarianKey(entityId)) || this.activeMaintenanceJobs.has(this._healKey(entityId)) || this.activeMaintenanceJobs.has(this._ontologyBackfillKey(entityId)) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this._isReembedActive(entityId) || this._isIngestActiveFor(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2788
|
+
}
|
|
2789
|
+
case "global_import":
|
|
2790
|
+
return this.activeMaintenanceJobs.has(this._globalImportKey());
|
|
2791
|
+
case "ingest":
|
|
2792
|
+
return this._hasIngestJob(entityId) || this.activeMaintenanceJobs.has(this._pruneKey(entityId)) || this._isReembedActive(entityId) || this._isImportActiveFor(entityId) || this._isForgetActiveFor(entityId);
|
|
2617
2793
|
default:
|
|
2618
2794
|
return false;
|
|
2619
2795
|
}
|
|
@@ -3080,11 +3256,23 @@ var IngestionService = class {
|
|
|
3080
3256
|
this.ontologyService = ontologyService;
|
|
3081
3257
|
this.promptService = promptService ?? new PromptService(this.options.config?.prompts);
|
|
3082
3258
|
}
|
|
3083
|
-
async ingestDocument(entityId, params) {
|
|
3259
|
+
async ingestDocument(entityId, params, opts) {
|
|
3084
3260
|
const sourceRef = normalizeSourceRef(params.sourceRef);
|
|
3085
3261
|
if (!sourceRef) throw new Error("Invalid sourceRef");
|
|
3086
3262
|
const sourceHash = normalizeSourceHash(params.sourceHash);
|
|
3087
3263
|
if (!sourceHash) throw new Error("Invalid sourceHash (must be 64-char hex string)");
|
|
3264
|
+
const onDuplicateHash = opts?.onDuplicateHash ?? "ingest";
|
|
3265
|
+
if (onDuplicateHash !== "ingest") {
|
|
3266
|
+
const refs = await this.entryRepo.findSourceRefsByHash(entityId, sourceHash);
|
|
3267
|
+
const others = refs.filter((r) => r !== sourceRef);
|
|
3268
|
+
if (others.length > 0) {
|
|
3269
|
+
const canonical = others[0];
|
|
3270
|
+
if (onDuplicateHash === "throw") {
|
|
3271
|
+
throw new WikiDuplicateHashError({ canonical, sourceHash, entityId });
|
|
3272
|
+
}
|
|
3273
|
+
return { truncated: false, chunks: 0, duplicateOf: canonical };
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3088
3276
|
const maxChunkLength = params.maxChunkLength ?? this.options.config?.maxChunkLength ?? DEFAULT_MAX_CHUNK_LENGTH;
|
|
3089
3277
|
const rawOverlap = params.chunkOverlap ?? this.options.config?.chunkOverlap ?? DEFAULT_CHUNK_OVERLAP;
|
|
3090
3278
|
const chunkOverlap = Math.min(
|
|
@@ -3497,10 +3685,13 @@ var MaintenanceService = class {
|
|
|
3497
3685
|
this.jobManager.releaseLock(op, entityId ?? "*");
|
|
3498
3686
|
}
|
|
3499
3687
|
}
|
|
3500
|
-
async forget(entityId, params) {
|
|
3688
|
+
async forget(entityId, params, opts) {
|
|
3501
3689
|
if (params.clearAll && (params.entryId !== void 0 || params.taskId !== void 0 || params.sourceRef !== void 0 || params.sourceHash !== void 0)) {
|
|
3502
3690
|
throw new Error("forget() clearAll is mutually exclusive with entryId, taskId, sourceRef, and sourceHash");
|
|
3503
3691
|
}
|
|
3692
|
+
if (opts?.dryRun === true) {
|
|
3693
|
+
return this.forgetDryRun(entityId, params);
|
|
3694
|
+
}
|
|
3504
3695
|
this.jobManager.acquireLock("forget", entityId);
|
|
3505
3696
|
try {
|
|
3506
3697
|
const now = Date.now();
|
|
@@ -3561,11 +3752,42 @@ var MaintenanceService = class {
|
|
|
3561
3752
|
throw new Error(`forget(${entityId}/${factId}) failed: ANN cleanup hook rejected`, { cause: this._sanitizeRankerError(hookErr) });
|
|
3562
3753
|
}
|
|
3563
3754
|
}
|
|
3564
|
-
return { deleted: { entries: deletedEntries, tasks: deletedTasks } };
|
|
3755
|
+
return params.clearAll ? { deleted: { entries: deletedEntries, tasks: deletedTasks }, metadataReset: true } : { deleted: { entries: deletedEntries, tasks: deletedTasks } };
|
|
3565
3756
|
} finally {
|
|
3566
3757
|
this.jobManager.releaseLock("forget", entityId);
|
|
3567
3758
|
}
|
|
3568
3759
|
}
|
|
3760
|
+
/**
|
|
3761
|
+
* Read-only dry-run preview of {@link forget}. Returns the same shape as a
|
|
3762
|
+
* real call would, without acquiring the lock, opening a transaction, staging
|
|
3763
|
+
* outbox events, or firing embedding hooks. Counts may be off-by-N during a
|
|
3764
|
+
* concurrent real forget — this is accepted, not considered a bug.
|
|
3765
|
+
*
|
|
3766
|
+
* `metadataReset` mirrors the real call: only `clearAll: true` returns true,
|
|
3767
|
+
* because only `clearAll` actually resets the metadata checkpoint.
|
|
3768
|
+
* `standard` (sourceRef/sourceHash) returns no metadataReset field, just like
|
|
3769
|
+
* the real call. `entryId`/`taskId` selectors are rejected with a clear error
|
|
3770
|
+
* (the spec keeps dry-run narrow).
|
|
3771
|
+
*/
|
|
3772
|
+
async forgetDryRun(entityId, params) {
|
|
3773
|
+
if (params.entryId !== void 0 || params.taskId !== void 0) {
|
|
3774
|
+
throw new Error("forget({ dryRun: true }) does not support entryId/taskId selectors; use sourceRef/sourceHash or clearAll");
|
|
3775
|
+
}
|
|
3776
|
+
if (params.clearAll) {
|
|
3777
|
+
const entries2 = await this.entryRepo.countLiveByEntityId(entityId);
|
|
3778
|
+
const tasks = await this.taskRepo.countLiveByEntityId(entityId);
|
|
3779
|
+
return { deleted: { entries: entries2, tasks }, metadataReset: true };
|
|
3780
|
+
}
|
|
3781
|
+
const sourceRef = params.sourceRef !== void 0 ? normalizeSourceRef(params.sourceRef) : null;
|
|
3782
|
+
if (params.sourceRef !== void 0 && !sourceRef) throw new Error("Invalid sourceRef");
|
|
3783
|
+
const sourceHash = params.sourceHash !== void 0 ? normalizeSourceHash(params.sourceHash) : null;
|
|
3784
|
+
if (params.sourceHash !== void 0 && !sourceHash) throw new Error("Invalid sourceHash (must be 64-char hex string)");
|
|
3785
|
+
if (sourceRef === null && sourceHash === null) {
|
|
3786
|
+
return { deleted: { entries: 0, tasks: 0 } };
|
|
3787
|
+
}
|
|
3788
|
+
const entries = await this.entryRepo.countLiveBySource(entityId, sourceRef, sourceHash);
|
|
3789
|
+
return { deleted: { entries, tasks: 0 } };
|
|
3790
|
+
}
|
|
3569
3791
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
3570
3792
|
async doRunLibrarian(entityId, promptOverride) {
|
|
3571
3793
|
const events = await this.eventRepo.getRecent(entityId, 50);
|
|
@@ -3795,10 +4017,10 @@ var MaintenanceService = class {
|
|
|
3795
4017
|
const validNewFacts = newFacts.map(validateFact).filter((f) => f !== null);
|
|
3796
4018
|
const insertedFacts = [];
|
|
3797
4019
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
3798
|
-
const healFactsForDedupe = (await this.entryRepo.findInferredTitlesByEntityId(entityId)).filter((f) => !safeDeletedSet.has(f.id));
|
|
3799
4020
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3800
4021
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
3801
4022
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
4023
|
+
const healFactsForDedupe = await this.entryRepo.findInferredTitlesByEntityId(entityId, tx);
|
|
3802
4024
|
for (const fact of validNewFacts) {
|
|
3803
4025
|
const newTokens = titleTokens(fact.title);
|
|
3804
4026
|
let skip = false;
|
|
@@ -4289,6 +4511,15 @@ var ImportExportService = class {
|
|
|
4289
4511
|
const safeTitle = clip(String(fact.title ?? ""), IMPORT_TITLE_MAX);
|
|
4290
4512
|
const safeBody = clip(String(fact.body ?? ""), IMPORT_BODY_MAX);
|
|
4291
4513
|
clippedTextByFactId.set(fact.id, { title: safeTitle, body: safeBody });
|
|
4514
|
+
let normalizedSourceRef = null;
|
|
4515
|
+
if (fact.source_ref !== null && fact.source_ref !== void 0) {
|
|
4516
|
+
normalizedSourceRef = normalizeSourceRef(fact.source_ref);
|
|
4517
|
+
if (normalizedSourceRef === null) {
|
|
4518
|
+
throw new Error(
|
|
4519
|
+
`importDump: invalid source_ref ${JSON.stringify(fact.source_ref)} for entity "${entityId}" fact "${fact.id}" (must normalize to a non-empty string; see ingestDocument's sourceRef validation)`
|
|
4520
|
+
);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4292
4523
|
const factObj = {
|
|
4293
4524
|
id: fact.id,
|
|
4294
4525
|
entity_id: entityId,
|
|
@@ -4298,7 +4529,7 @@ var ImportExportService = class {
|
|
|
4298
4529
|
confidence: fact.confidence,
|
|
4299
4530
|
source_type: sourceType,
|
|
4300
4531
|
source_hash: fact.source_hash,
|
|
4301
|
-
source_ref:
|
|
4532
|
+
source_ref: normalizedSourceRef,
|
|
4302
4533
|
created_at: fact.created_at,
|
|
4303
4534
|
updated_at: safeUpdatedAt,
|
|
4304
4535
|
last_accessed_at: fact.last_accessed_at,
|
|
@@ -5616,19 +5847,69 @@ var WikiMemory = class {
|
|
|
5616
5847
|
});
|
|
5617
5848
|
await this.searchService.sync();
|
|
5618
5849
|
}
|
|
5619
|
-
async hasChanged(entityId,
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5850
|
+
async hasChanged(entityId, sourceRefOrEntries, sourceHashArg) {
|
|
5851
|
+
if (typeof sourceRefOrEntries === "string") {
|
|
5852
|
+
const sourceRef = normalizeSourceRef(sourceRefOrEntries);
|
|
5853
|
+
if (!sourceRef) {
|
|
5854
|
+
throw new Error(`Invalid sourceRef: ${JSON.stringify(sourceRefOrEntries)}`);
|
|
5855
|
+
}
|
|
5856
|
+
const sourceHash = normalizeSourceHash(sourceHashArg);
|
|
5857
|
+
if (!sourceHash) {
|
|
5858
|
+
throw new Error("Invalid sourceHash: must be a 64-character hex string (normalized to lowercase)");
|
|
5859
|
+
}
|
|
5860
|
+
const storedHash = await this.entryRepo.findLatestSourceHash(entityId, sourceRef);
|
|
5861
|
+
if (storedHash === null) return true;
|
|
5862
|
+
const normalizedStoredHash = normalizeSourceHash(storedHash);
|
|
5863
|
+
return normalizedStoredHash !== sourceHash;
|
|
5864
|
+
}
|
|
5865
|
+
const entries = sourceRefOrEntries;
|
|
5866
|
+
if (entries.length === 0) return [];
|
|
5867
|
+
const normalized = entries.map((e) => {
|
|
5868
|
+
const r = normalizeSourceRef(e.sourceRef);
|
|
5869
|
+
if (!r) throw new Error(`Invalid sourceRef: ${JSON.stringify(e.sourceRef)}`);
|
|
5870
|
+
const h = normalizeSourceHash(e.sourceHash);
|
|
5871
|
+
if (!h) throw new Error("Invalid sourceHash: must be a 64-character hex string (normalized to lowercase)");
|
|
5872
|
+
return { rawSourceRef: e.sourceRef, sourceRef: r, sourceHash: h };
|
|
5873
|
+
});
|
|
5874
|
+
const latestHashes = await this.entryRepo.findLatestSourceHashes(entityId, normalized.map((e) => e.sourceRef));
|
|
5875
|
+
const distinctHashes = Array.from(new Set(normalized.map((e) => e.sourceHash)));
|
|
5876
|
+
const dupRefs = await Promise.all(
|
|
5877
|
+
distinctHashes.map((h) => this.entryRepo.findSourceRefsByHash(entityId, h))
|
|
5878
|
+
);
|
|
5879
|
+
const dupMap = /* @__PURE__ */ new Map();
|
|
5880
|
+
for (let i = 0; i < distinctHashes.length; i++) {
|
|
5881
|
+
dupMap.set(distinctHashes[i], dupRefs[i]);
|
|
5882
|
+
}
|
|
5883
|
+
return normalized.map((e) => {
|
|
5884
|
+
const stored = latestHashes.get(e.sourceRef);
|
|
5885
|
+
const changed = stored === void 0 || stored === null || normalizeSourceHash(stored) !== e.sourceHash;
|
|
5886
|
+
const allRefs = dupMap.get(e.sourceHash) ?? [];
|
|
5887
|
+
const others = allRefs.filter((r) => r !== e.sourceRef);
|
|
5888
|
+
if (others.length === 0) {
|
|
5889
|
+
return { sourceRef: e.rawSourceRef, changed };
|
|
5890
|
+
}
|
|
5891
|
+
const canonical = others[0];
|
|
5892
|
+
return { sourceRef: e.rawSourceRef, changed, duplicateOf: canonical };
|
|
5893
|
+
});
|
|
5894
|
+
}
|
|
5895
|
+
/**
|
|
5896
|
+
* Returns the live source_refs for an entity, one row per ref, with the most
|
|
5897
|
+
* recently-updated live `source_hash` and a live fact count. Refs are sorted
|
|
5898
|
+
* `COLLATE BINARY` (no locale dependency). Used by hosts to reconcile stored
|
|
5899
|
+
* state against a live source, or audit duplicate-hash collisions via
|
|
5900
|
+
* `findSourceRefsByHash`.
|
|
5901
|
+
*/
|
|
5902
|
+
async listSourceRefs(entityId) {
|
|
5903
|
+
return this.entryRepo.listSourceRefs(entityId);
|
|
5904
|
+
}
|
|
5905
|
+
/**
|
|
5906
|
+
* Returns the live source_refs for an entity that hold the given source_hash,
|
|
5907
|
+
* sorted `COLLATE BINARY` ascending. The first element is the canonical ref
|
|
5908
|
+
* under the code-unit-minimum rule (no locale dependency). Used by the
|
|
5909
|
+
* ingestDocument guard and by hosts auditing duplicate-content collisions.
|
|
5910
|
+
*/
|
|
5911
|
+
async findSourceRefsByHash(entityId, sourceHash) {
|
|
5912
|
+
return this.entryRepo.findSourceRefsByHash(entityId, sourceHash);
|
|
5632
5913
|
}
|
|
5633
5914
|
async runPrune(entityId, options) {
|
|
5634
5915
|
return this.maintenanceService.runPrune(entityId, options);
|
|
@@ -5715,16 +5996,16 @@ var WikiMemory = class {
|
|
|
5715
5996
|
async importDump(dump, opts) {
|
|
5716
5997
|
return this.importExportService.importDump(dump, opts);
|
|
5717
5998
|
}
|
|
5718
|
-
async forget(entityId, params) {
|
|
5719
|
-
return this.maintenanceService.forget(entityId, params);
|
|
5999
|
+
async forget(entityId, params, opts) {
|
|
6000
|
+
return this.maintenanceService.forget(entityId, params, opts);
|
|
5720
6001
|
}
|
|
5721
6002
|
/**
|
|
5722
6003
|
* @param params.promptOverride - Overrides the system prompt for this ingest call only.
|
|
5723
6004
|
* For persistent customization, set `options.config.prompts.ingestSystemPrompt` at
|
|
5724
6005
|
* WikiMemory construction time.
|
|
5725
6006
|
*/
|
|
5726
|
-
async ingestDocument(entityId, params) {
|
|
5727
|
-
return this.ingestionService.ingestDocument(entityId, params);
|
|
6007
|
+
async ingestDocument(entityId, params, opts) {
|
|
6008
|
+
return this.ingestionService.ingestDocument(entityId, params, opts);
|
|
5728
6009
|
}
|
|
5729
6010
|
/**
|
|
5730
6011
|
* Returns up to `limit` unprocessed outbox events, oldest first.
|
|
@@ -6543,6 +6824,7 @@ exports.ONTOLOGY_BACKFILL_SYSTEM_PROMPT = ONTOLOGY_BACKFILL_SYSTEM_PROMPT;
|
|
|
6543
6824
|
exports.PromptService = PromptService;
|
|
6544
6825
|
exports.PrunePartialFailureError = PrunePartialFailureError;
|
|
6545
6826
|
exports.WikiBusyError = WikiBusyError;
|
|
6827
|
+
exports.WikiDuplicateHashError = WikiDuplicateHashError;
|
|
6546
6828
|
exports.WikiMemory = WikiMemory;
|
|
6547
6829
|
exports.WikiTransactionError = WikiTransactionError;
|
|
6548
6830
|
exports.chunkText = chunkText;
|