@equationalapplications/core-llm-wiki 4.18.1 → 4.20.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 CHANGED
@@ -590,6 +590,14 @@ A `WikiEdge` represents a directed link between two concepts (`source_id`, `targ
590
590
 
591
591
  See [`docs/okf-profile.md`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/docs/okf-profile.md) for the full normative spec and [`packages/okf/fixtures/`](https://github.com/equationalapplications/expo-llm-wiki/tree/main/packages/okf/fixtures) for conformance bundles.
592
592
 
593
+ `importDump` persists an imported entity summary (profile ≥ 1 bundles) and
594
+ `exportDump` re-emits it, so the profile §4 round-trip holds across storage.
595
+ Read it directly with `wiki.getEntitySummary(entityId)`. The value lives in the
596
+ `{prefix}meta` table under `entity_summary:{entity_id}` and is removed by
597
+ `forget(entityId, { clearAll: true })`. See the
598
+ [design spec](../../docs/superpowers/specs/2026-07-05-okf-summary-persistence-design.md)
599
+ for import/merge semantics and wipe-path cleanup.
600
+
593
601
  ### The `okf_type` Field
594
602
 
595
603
  Facts and tasks include a nullable `okf_type` column. This preserves the literal OKF `type` string from an imported bundle frontmatter, independent of whether the item was routed to the `entries` or `tasks` table. When `formatOkfBundle` runs, it restores this specific string, falling back to `'fact'` or `'task'` if the field is null (ensuring non-imported rows export cleanly).
@@ -838,6 +846,31 @@ const adapter: SQLiteAdapter = {
838
846
  };
839
847
  ```
840
848
 
849
+ ## Concurrency
850
+
851
+ All write APIs are safe to call from concurrent async contexts. Transactions are
852
+ serialized internally on the single database connection — you never need to know that
853
+ SQLite forbids nested `BEGIN`, and you never need to throttle callers yourself.
854
+
855
+ - One connection per database file per process is the supported topology.
856
+ - Non-transactional reads are **not** serialized, so read latency is unaffected.
857
+ - Inside a transaction callback, use only the provided `tx` handle — never the outer
858
+ database handle. Using the outer handle deadlocks; opening a nested transaction throws.
859
+
860
+ ### `WikiTransactionError`
861
+
862
+ Thrown when a SQLite driver error escapes a transaction (nested `BEGIN`,
863
+ `SQLITE_BUSY`, constraint violation). Stable `instanceof` target for observability:
864
+
865
+ - `error.cause` — the original driver error (the chain bottoms out here, not at a
866
+ rollback red herring).
867
+ - `error.sqliteErrorCode` — best-effort SQLite code (e.g. `'SQLITE_BUSY'`), present
868
+ when it can be parsed. Group on this in Sentry/Datadog instead of inspecting
869
+ `error.cause` recursively.
870
+
871
+ Domain errors thrown from your own callback logic (validation, `WikiBusyError`)
872
+ pass through unwrapped with their original type intact.
873
+
841
874
  ## How It Works
842
875
 
843
876
  ```mermaid
@@ -8,6 +8,83 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
8
8
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
9
9
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
10
10
 
11
+ // src/db/sqliteCodes.ts
12
+ var SQLITE_RESULT_CODE_NAMES = {
13
+ 1: "SQLITE_ERROR",
14
+ 2: "SQLITE_INTERNAL",
15
+ 3: "SQLITE_PERM",
16
+ 4: "SQLITE_ABORT",
17
+ 5: "SQLITE_BUSY",
18
+ 6: "SQLITE_LOCKED",
19
+ 7: "SQLITE_NOMEM",
20
+ 8: "SQLITE_READONLY",
21
+ 9: "SQLITE_INTERRUPT",
22
+ 10: "SQLITE_IOERR",
23
+ 11: "SQLITE_CORRUPT",
24
+ 12: "SQLITE_NOTFOUND",
25
+ 13: "SQLITE_FULL",
26
+ 14: "SQLITE_CANTOPEN",
27
+ 15: "SQLITE_PROTOCOL",
28
+ 16: "SQLITE_EMPTY",
29
+ 17: "SQLITE_SCHEMA",
30
+ 18: "SQLITE_TOOBIG",
31
+ 19: "SQLITE_CONSTRAINT",
32
+ 20: "SQLITE_MISMATCH",
33
+ 21: "SQLITE_MISUSE",
34
+ 22: "SQLITE_NOLFS",
35
+ 23: "SQLITE_AUTH",
36
+ 24: "SQLITE_FORMAT",
37
+ 25: "SQLITE_RANGE",
38
+ 26: "SQLITE_NOTADB",
39
+ 27: "SQLITE_NOTICE",
40
+ 28: "SQLITE_WARNING",
41
+ 100: "SQLITE_ROW",
42
+ 101: "SQLITE_DONE"
43
+ };
44
+ function nameForSqliteResultCode(n) {
45
+ return SQLITE_RESULT_CODE_NAMES[n] ?? `SQLITE_${n}`;
46
+ }
47
+ function extractSqliteCode(err) {
48
+ if (typeof err !== "object" || err === null) return void 0;
49
+ const { code, message } = err;
50
+ if (typeof code === "string" && code.startsWith("SQLITE_")) return code;
51
+ if (typeof message === "string") {
52
+ const m = /^Error code (\d+):/.exec(message);
53
+ if (m) return nameForSqliteResultCode(Number(m[1]));
54
+ }
55
+ return void 0;
56
+ }
57
+
58
+ // src/types.ts
59
+ var WikiBusyError = class extends Error {
60
+ constructor(operation, entityId) {
61
+ super(`${operation} already running for entity ${entityId}`);
62
+ this.name = "WikiBusyError";
63
+ this.operation = operation;
64
+ this.entityId = entityId;
65
+ }
66
+ };
67
+ var WikiTransactionError = class extends Error {
68
+ constructor(message, options) {
69
+ super(message, options);
70
+ this.name = "WikiTransactionError";
71
+ this.sqliteErrorCode = extractSqliteCode(options.cause);
72
+ }
73
+ };
74
+ var PrunePartialFailureError = class extends Error {
75
+ constructor(deleted, failedAt, remaining, cause, deletedTasks = 0, deletedEvents = 0) {
76
+ super(`Prune partially failed: deleted ${deleted}, failed at ${failedAt}, ${remaining} remaining`);
77
+ this.name = "PrunePartialFailureError";
78
+ this.deleted = deleted;
79
+ this.failedAt = failedAt;
80
+ this.remaining = remaining;
81
+ this.deletedTasks = deletedTasks;
82
+ this.deletedEvents = deletedEvents;
83
+ this.cause = cause;
84
+ }
85
+ };
86
+ var HOOK_TIMEOUT_MARKER = /* @__PURE__ */ Symbol("WikiMemoryHookTimeout");
87
+
11
88
  // src/utils/ids.ts
12
89
  var _injectedGetRandomValues = null;
13
90
  function configureRandomSource(fn) {
@@ -278,29 +355,6 @@ _SearchService.MAX_VECTOR_CACHE_ENTITIES = 16;
278
355
  _SearchService.MAX_VECTOR_CACHE_FACTS_PER_ENTITY = 500;
279
356
  var SearchService = _SearchService;
280
357
 
281
- // src/types.ts
282
- var WikiBusyError = class extends Error {
283
- constructor(operation, entityId) {
284
- super(`${operation} already running for entity ${entityId}`);
285
- this.name = "WikiBusyError";
286
- this.operation = operation;
287
- this.entityId = entityId;
288
- }
289
- };
290
- var PrunePartialFailureError = class extends Error {
291
- constructor(deleted, failedAt, remaining, cause, deletedTasks = 0, deletedEvents = 0) {
292
- super(`Prune partially failed: deleted ${deleted}, failed at ${failedAt}, ${remaining} remaining`);
293
- this.name = "PrunePartialFailureError";
294
- this.deleted = deleted;
295
- this.failedAt = failedAt;
296
- this.remaining = remaining;
297
- this.deletedTasks = deletedTasks;
298
- this.deletedEvents = deletedEvents;
299
- this.cause = cause;
300
- }
301
- };
302
- var HOOK_TIMEOUT_MARKER = /* @__PURE__ */ Symbol("WikiMemoryHookTimeout");
303
-
304
358
  // src/services/JobManager.ts
305
359
  var JobManager = class {
306
360
  constructor(prefix) {
@@ -1156,6 +1210,169 @@ var IngestionService = class {
1156
1210
  }
1157
1211
  };
1158
1212
 
1213
+ // src/repositories/BaseRepository.ts
1214
+ var BaseRepository = class {
1215
+ constructor(db, prefix) {
1216
+ this.db = db;
1217
+ this.prefix = prefix;
1218
+ }
1219
+ /**
1220
+ * Return the DB executor for a given transaction handle.
1221
+ * If tx is provided, use it; otherwise fall back to this.db.
1222
+ */
1223
+ getExecutor(tx) {
1224
+ return tx ?? this.db;
1225
+ }
1226
+ };
1227
+
1228
+ // src/repositories/MetadataRepository.ts
1229
+ function entitySummaryMetaKey(entityId) {
1230
+ return `entity_summary:${entityId}`;
1231
+ }
1232
+ var MetadataRepository = class extends BaseRepository {
1233
+ // CHECKPOINTS TABLE METHODS
1234
+ async getCheckpoint(entityId, tx) {
1235
+ const executor = this.getExecutor(tx);
1236
+ const row = await executor.getFirstAsync(
1237
+ `SELECT memory_checkpoint, heal_checkpoint FROM ${this.prefix}checkpoints WHERE entity_id = ?`,
1238
+ [entityId]
1239
+ );
1240
+ if (!row) return {};
1241
+ return {
1242
+ memory: row.memory_checkpoint ?? void 0,
1243
+ heal: row.heal_checkpoint ?? void 0
1244
+ };
1245
+ }
1246
+ async updateCheckpoint(entityId, updates, tx) {
1247
+ const fields = [];
1248
+ const values = [];
1249
+ if (updates.memory !== void 0) {
1250
+ fields.push("memory_checkpoint = ?");
1251
+ values.push(updates.memory);
1252
+ }
1253
+ if (updates.heal !== void 0) {
1254
+ fields.push("heal_checkpoint = ?");
1255
+ values.push(updates.heal);
1256
+ }
1257
+ if (fields.length === 0) return;
1258
+ const executor = this.getExecutor(tx);
1259
+ await executor.runAsync(
1260
+ `INSERT INTO ${this.prefix}checkpoints (entity_id, memory_checkpoint, heal_checkpoint)
1261
+ VALUES (?, ?, ?)
1262
+ ON CONFLICT(entity_id) DO UPDATE SET ${fields.join(", ")}`,
1263
+ [entityId, updates.memory ?? 0, updates.heal ?? 0, ...values]
1264
+ );
1265
+ }
1266
+ async deleteCheckpoint(entityId, tx) {
1267
+ const executor = this.getExecutor(tx);
1268
+ await executor.runAsync(
1269
+ `DELETE FROM ${this.prefix}checkpoints WHERE entity_id = ?`,
1270
+ [entityId]
1271
+ );
1272
+ }
1273
+ // META TABLE METHODS
1274
+ async getMeta(key, tx) {
1275
+ const executor = this.getExecutor(tx);
1276
+ const row = await executor.getFirstAsync(
1277
+ `SELECT value FROM ${this.prefix}meta WHERE key = ?`,
1278
+ [key]
1279
+ );
1280
+ return row ? row.value : null;
1281
+ }
1282
+ async setMeta(key, value, tx) {
1283
+ const executor = this.getExecutor(tx);
1284
+ await executor.runAsync(
1285
+ `INSERT INTO ${this.prefix}meta (key, value) VALUES (?, ?)
1286
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
1287
+ [key, value]
1288
+ );
1289
+ }
1290
+ async deleteMeta(key, tx) {
1291
+ const executor = this.getExecutor(tx);
1292
+ await executor.runAsync(
1293
+ `DELETE FROM ${this.prefix}meta WHERE key = ?`,
1294
+ [key]
1295
+ );
1296
+ }
1297
+ async clearDimensionMismatch(tx) {
1298
+ const executor = this.getExecutor(tx);
1299
+ await executor.runAsync(
1300
+ `DELETE FROM ${this.prefix}meta WHERE key = 'embedding_dimension_mismatch'`
1301
+ );
1302
+ }
1303
+ async tableExists(tableName, tx) {
1304
+ const executor = this.getExecutor(tx);
1305
+ const row = await executor.getFirstAsync(
1306
+ `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
1307
+ [tableName]
1308
+ );
1309
+ return row != null;
1310
+ }
1311
+ async getTableDdl(tableName, tx) {
1312
+ const executor = this.getExecutor(tx);
1313
+ const row = await executor.getFirstAsync(
1314
+ `SELECT sql FROM sqlite_master WHERE type='table' AND name=?`,
1315
+ [tableName]
1316
+ );
1317
+ return row?.sql ?? null;
1318
+ }
1319
+ async vacuum() {
1320
+ await this.db.execAsync(`PRAGMA wal_checkpoint(TRUNCATE)`);
1321
+ await this.db.execAsync(`VACUUM`);
1322
+ }
1323
+ async getDistinctEntityIds(tx) {
1324
+ const executor = this.getExecutor(tx);
1325
+ const rows = await executor.getAllAsync(
1326
+ `SELECT DISTINCT entity_id FROM (
1327
+ SELECT entity_id FROM ${this.prefix}entries WHERE deleted_at IS NULL
1328
+ UNION
1329
+ SELECT entity_id FROM ${this.prefix}tasks WHERE deleted_at IS NULL
1330
+ UNION
1331
+ SELECT entity_id FROM ${this.prefix}events
1332
+ ) ORDER BY entity_id`
1333
+ );
1334
+ return rows.map((r) => r.entity_id);
1335
+ }
1336
+ async getManifest(entityId, tx) {
1337
+ const executor = this.getExecutor(tx);
1338
+ const row = await executor.getFirstAsync(`SELECT mode, manifest_json FROM ${this.prefix}entity_manifests WHERE entity_id = ?`, [entityId]);
1339
+ if (!row) return null;
1340
+ if (row.mode !== "off" && row.mode !== "strict" && row.mode !== "emergent") {
1341
+ throw new Error(`Invalid ontology mode for entity ${entityId}: ${JSON.stringify(row.mode)}`);
1342
+ }
1343
+ let manifest;
1344
+ try {
1345
+ manifest = JSON.parse(row.manifest_json);
1346
+ } catch (error) {
1347
+ throw new Error(`Invalid manifest_json for entity ${entityId}: ${error.message}`);
1348
+ }
1349
+ validateManifest(manifest);
1350
+ return {
1351
+ mode: row.mode,
1352
+ manifest
1353
+ };
1354
+ }
1355
+ async setManifest(entityId, data, tx) {
1356
+ validateManifest(data.manifest);
1357
+ const executor = this.getExecutor(tx);
1358
+ await executor.runAsync(
1359
+ `INSERT INTO ${this.prefix}entity_manifests (entity_id, mode, manifest_json, updated_at)
1360
+ VALUES (?, ?, ?, ?)
1361
+ ON CONFLICT(entity_id) DO UPDATE SET mode = excluded.mode, manifest_json = excluded.manifest_json, updated_at = excluded.updated_at`,
1362
+ [entityId, data.mode, JSON.stringify(data.manifest), Date.now()]
1363
+ );
1364
+ }
1365
+ async mergeManifestUpdates(entityId, updates, tx) {
1366
+ const current = await this.getManifest(entityId, tx) ?? {
1367
+ mode: "emergent",
1368
+ manifest: emptyManifest()
1369
+ };
1370
+ const merged = mergeOntologyUpdates(current.manifest, updates);
1371
+ await this.setManifest(entityId, { mode: current.mode, manifest: merged }, tx);
1372
+ return merged;
1373
+ }
1374
+ };
1375
+
1159
1376
  // src/services/MaintenanceService.ts
1160
1377
  var FUZZY_THRESHOLD = 0.5;
1161
1378
  var MIN_TOKENS_TO_QUALIFY = 3;
@@ -1331,6 +1548,7 @@ var MaintenanceService = class {
1331
1548
  deletedEntries = await this.entryRepo.bulkSoftDeleteByEntityId(entityId, tx);
1332
1549
  deletedTasks = await this.taskRepo.bulkSoftDeleteByEntityId(entityId, tx);
1333
1550
  await this.metadataRepo.updateCheckpoint(entityId, { memory: 0, heal: 0 }, tx);
1551
+ await this.metadataRepo.deleteMeta(entitySummaryMetaKey(entityId), tx);
1334
1552
  } else {
1335
1553
  const hasIdSelectors = params.entryId !== void 0 || params.taskId !== void 0;
1336
1554
  const hasSourceSelectors = params.sourceRef !== void 0 || params.sourceHash !== void 0;
@@ -1679,11 +1897,12 @@ var ImportExportService = class {
1679
1897
  }
1680
1898
  }
1681
1899
  async getFullBundle(entityId, opts) {
1682
- const [factsRaw, tasks, events, edges] = await Promise.all([
1900
+ const [factsRaw, tasks, events, edges, summaryValue] = await Promise.all([
1683
1901
  opts?.includeBlobs ? this.entryRepo.findAllByEntityIdWithBlobs(entityId) : this.entryRepo.findAllByEntityId(entityId),
1684
1902
  this.taskRepo.findAllByEntityId(entityId),
1685
1903
  this.eventRepo.getByEntityId(entityId, opts?.maxEvents),
1686
- this.edgeRepo.getByEntityId(entityId)
1904
+ this.edgeRepo.getByEntityId(entityId),
1905
+ this.metadataRepo.getMeta(entitySummaryMetaKey(entityId))
1687
1906
  ]);
1688
1907
  const facts = factsRaw.map((f) => {
1689
1908
  const {
@@ -1702,7 +1921,13 @@ var ImportExportService = class {
1702
1921
  tags: typeof factBase.tags === "string" ? JSON.parse(factBase.tags) : factBase.tags
1703
1922
  };
1704
1923
  });
1705
- return { facts, tasks, events, edges };
1924
+ return {
1925
+ facts,
1926
+ tasks,
1927
+ events,
1928
+ edges,
1929
+ ...summaryValue != null ? { summary: summaryValue } : {}
1930
+ };
1706
1931
  }
1707
1932
  /** Single-entity import transaction + post-processing; package-internal hook for tests. */
1708
1933
  async doImportEntity(entityId, bundle, merge) {
@@ -1727,6 +1952,11 @@ var ImportExportService = class {
1727
1952
  await this.edgeRepo.bulkDeleteByEntityId(entityId, tx);
1728
1953
  await this.metadataRepo.deleteCheckpoint(entityId, tx);
1729
1954
  }
1955
+ if (bundle.summary !== void 0) {
1956
+ await this.metadataRepo.setMeta(entitySummaryMetaKey(entityId), bundle.summary, tx);
1957
+ } else if (!merge) {
1958
+ await this.metadataRepo.deleteMeta(entitySummaryMetaKey(entityId), tx);
1959
+ }
1730
1960
  const factIds = bundle.facts.map((fact) => fact.id);
1731
1961
  const existingFactsById = /* @__PURE__ */ new Map();
1732
1962
  const existingFacts = await this.entryRepo.findExistingMetadataByIds(
@@ -2815,6 +3045,6 @@ var WriteService = class {
2815
3045
  }
2816
3046
  };
2817
3047
 
2818
- export { EmbeddingService, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WriteService, __privateAdd, __privateGet, __privateSet, configureRandomSource, emptyManifest, generateId, mergeOntologyUpdates, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinition, resolveNodeType, validateInlineEdges, validateManifest };
2819
- //# sourceMappingURL=chunk-V7QNRQFK.mjs.map
2820
- //# sourceMappingURL=chunk-V7QNRQFK.mjs.map
3048
+ export { BaseRepository, EmbeddingService, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinition, resolveNodeType, validateInlineEdges };
3049
+ //# sourceMappingURL=chunk-HKB5FEJG.mjs.map
3050
+ //# sourceMappingURL=chunk-HKB5FEJG.mjs.map