@equationalapplications/core-llm-wiki 3.2.0 → 4.0.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/index.mjs CHANGED
@@ -10,7 +10,7 @@ async function setupDatabase(db, prefix) {
10
10
  body TEXT NOT NULL,
11
11
  tags TEXT NOT NULL DEFAULT '[]',
12
12
  confidence TEXT NOT NULL DEFAULT 'inferred',
13
- source_type TEXT NOT NULL DEFAULT 'agent_inferred',
13
+ source_type TEXT NOT NULL DEFAULT 'librarian_inferred',
14
14
  source_hash TEXT,
15
15
  source_ref TEXT,
16
16
  created_at INTEGER NOT NULL,
@@ -587,6 +587,44 @@ var _WikiMemory = class _WikiMemory {
587
587
  _warnCrossEntityCollision(type, id, existingEntityId, targetEntityId) {
588
588
  console.warn(`[WikiMemory] importDump: ${type} id "${id}" already belongs to entity "${existingEntityId}"; skipping for entity "${targetEntityId}"`);
589
589
  }
590
+ /** Maps pre-rename enum strings from older dumps to current source_type values. */
591
+ _normalizeImportedSourceType(raw, ctx) {
592
+ if (raw === "user_document") return "immutable_document";
593
+ if (raw === "agent_inferred") return "librarian_inferred";
594
+ const allowed = ["user_stated", "librarian_inferred", "user_confirmed", "immutable_document"];
595
+ if (allowed.includes(raw)) return raw;
596
+ const where = ctx !== void 0 ? ` for entity "${ctx.entityId}" fact "${ctx.factId}"` : "";
597
+ throw new Error(
598
+ `importDump: invalid source_type "${raw}"${where} (expected one of: ${allowed.join(", ")}, or legacy aliases user_document / agent_inferred)`
599
+ );
600
+ }
601
+ async assertNoLegacySourceTypes() {
602
+ const legacyProbe = await this.db.getFirstAsync(
603
+ `SELECT 1 AS one FROM ${this.prefix}entries
604
+ WHERE source_type IN ('user_document', 'agent_inferred')
605
+ LIMIT 1`,
606
+ []
607
+ );
608
+ if (!legacyProbe) return;
609
+ const legacyCount = await this.db.getFirstAsync(
610
+ `SELECT COUNT(*) as count FROM ${this.prefix}entries
611
+ WHERE source_type IN ('user_document', 'agent_inferred')`,
612
+ []
613
+ );
614
+ const count = legacyCount?.count ?? 0;
615
+ const migrationSQL = `
616
+ -- Migrate legacy source_type values (targets your WikiMemory prefix: ${this.prefix})
617
+ UPDATE ${this.prefix}entries SET source_type = 'immutable_document' WHERE source_type = 'user_document';
618
+ UPDATE ${this.prefix}entries SET source_type = 'librarian_inferred' WHERE source_type = 'agent_inferred';
619
+ `.trim();
620
+ throw new Error(
621
+ `Database contains ${count} entries with legacy source_type values ('user_document' or 'agent_inferred'). These enum values were renamed in this release. Running without migration would allow legacy 'user_document' facts to bypass immutability guards, causing data corruption.
622
+
623
+ ${migrationSQL}
624
+
625
+ After running the migration SQL, restart your application.`
626
+ );
627
+ }
590
628
  async _notifyEmbeddingPersisted(entityId, factId, vector) {
591
629
  if (!this.options.vectorRanker?.onEmbeddingPersisted) return;
592
630
  const vectorCopy = vector ? vector.slice() : null;
@@ -688,6 +726,9 @@ var _WikiMemory = class _WikiMemory {
688
726
  );
689
727
  }
690
728
  }
729
+ if (entriesExistedBeforeSetup) {
730
+ await this.assertNoLegacySourceTypes();
731
+ }
691
732
  const rows = await this.db.getAllAsync(`
692
733
  SELECT rowid, source_ref FROM ${this.prefix}entries
693
734
  WHERE source_ref IS NOT NULL
@@ -824,7 +865,7 @@ var _WikiMemory = class _WikiMemory {
824
865
  const cutoff = now - retainSoftDeletedFor * 864e5;
825
866
  const entriesToDelete = await this.db.getAllAsync(
826
867
  `SELECT id, entity_id FROM ${this.prefix}entries
827
- WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at < ?`,
868
+ WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at <= ?`,
828
869
  [entityId, cutoff]
829
870
  );
830
871
  const succeeded = [];
@@ -844,7 +885,7 @@ var _WikiMemory = class _WikiMemory {
844
885
  const chunk = succeeded.slice(i, i + chunkSize);
845
886
  const placeholders = chunk.map(() => "?").join(",");
846
887
  const entryResult = await this.db.runAsync(
847
- `DELETE FROM ${this.prefix}entries WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at < ? AND id IN (${placeholders})`,
888
+ `DELETE FROM ${this.prefix}entries WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at <= ? AND id IN (${placeholders})`,
848
889
  [entityId, cutoff, ...chunk.map((r) => r.id)]
849
890
  );
850
891
  deletedEntries += entryResult.changes;
@@ -852,7 +893,7 @@ var _WikiMemory = class _WikiMemory {
852
893
  }
853
894
  const taskResult = await this.db.runAsync(
854
895
  `DELETE FROM ${this.prefix}tasks
855
- WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at < ?`,
896
+ WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at <= ?`,
856
897
  [entityId, cutoff]
857
898
  );
858
899
  deletedTasks = taskResult.changes;
@@ -890,7 +931,7 @@ var _WikiMemory = class _WikiMemory {
890
931
  const cutoff = now - retainEventsFor * 864e5;
891
932
  const eventResult = await this.db.runAsync(
892
933
  `DELETE FROM ${this.prefix}events
893
- WHERE entity_id = ? AND created_at < ?`,
934
+ WHERE entity_id = ? AND created_at <= ?`,
894
935
  [entityId, cutoff]
895
936
  );
896
937
  deletedEvents = eventResult.changes;
@@ -1591,7 +1632,7 @@ ${JSON.stringify(currentFacts, null, 2)}`;
1591
1632
  let skip = false;
1592
1633
  if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
1593
1634
  for (const existing of currentFactsRows) {
1594
- if (existing.source_type !== "agent_inferred") continue;
1635
+ if (existing.source_type !== "librarian_inferred") continue;
1595
1636
  const existingTokens = titleTokens(existing.title);
1596
1637
  if (existingTokens.size >= MIN_TOKENS_TO_QUALIFY) {
1597
1638
  if (jaccardScore(newTokens, existingTokens) >= FUZZY_THRESHOLD) {
@@ -1606,7 +1647,7 @@ ${JSON.stringify(currentFacts, null, 2)}`;
1606
1647
  await this.db.runAsync(`
1607
1648
  INSERT INTO ${this.prefix}entries (id, entity_id, title, body, tags, confidence, source_type, created_at, updated_at)
1608
1649
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1609
- `, [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "agent_inferred", now, now]);
1650
+ `, [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "librarian_inferred", now, now]);
1610
1651
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
1611
1652
  }
1612
1653
  for (const task of validTasks) {
@@ -1639,25 +1680,25 @@ ${JSON.stringify(currentFacts, null, 2)}`;
1639
1680
  if (orphanAfterDays !== null) {
1640
1681
  const orphanThreshold = now - orphanAfterDays * MS_PER_DAY;
1641
1682
  await this.db.runAsync(`
1642
- UPDATE ${this.prefix}entries
1643
- SET deleted_at = ?, updated_at = ?
1644
- WHERE entity_id = ? AND access_count = 0 AND created_at < ? AND source_type != 'user_document' AND deleted_at IS NULL
1683
+ UPDATE ${this.prefix}entries
1684
+ SET deleted_at = ?, updated_at = ?
1685
+ WHERE entity_id = ? AND access_count = 0 AND created_at <= ? AND source_type != 'immutable_document' AND deleted_at IS NULL
1645
1686
  `, [now, now, entityId, orphanThreshold]);
1646
1687
  }
1647
1688
  if (staleInferredAfterDays !== null) {
1648
1689
  const staleThreshold = now - staleInferredAfterDays * MS_PER_DAY;
1649
1690
  await this.db.runAsync(`
1650
- UPDATE ${this.prefix}entries
1651
- SET confidence = 'tentative', updated_at = ?
1652
- WHERE entity_id = ? AND confidence = 'inferred' AND (last_accessed_at < ? OR (last_accessed_at IS NULL AND created_at < ?)) AND source_type != 'user_document' AND deleted_at IS NULL
1691
+ UPDATE ${this.prefix}entries
1692
+ SET confidence = 'tentative', updated_at = ?
1693
+ WHERE entity_id = ? AND confidence = 'inferred' AND (last_accessed_at <= ? OR (last_accessed_at IS NULL AND created_at <= ?)) AND source_type != 'immutable_document' AND deleted_at IS NULL
1653
1694
  `, [now, entityId, staleThreshold, staleThreshold]);
1654
1695
  }
1655
1696
  });
1656
1697
  const allFactsRows = await this.db.getAllAsync(`SELECT * FROM ${this.prefix}entries WHERE entity_id = ? AND deleted_at IS NULL`, [entityId]);
1657
1698
  const allTasks = await this.db.getAllAsync(`SELECT * FROM ${this.prefix}tasks WHERE entity_id = ? AND status IN ('pending', 'in_progress') AND deleted_at IS NULL`, [entityId]);
1658
1699
  const recentEvents = await this.db.getAllAsync(`SELECT * FROM ${this.prefix}events WHERE entity_id = ? ORDER BY created_at DESC LIMIT 20`, [entityId]);
1659
- const healCandidates = allFactsRows.filter((f) => f.source_type !== "user_document");
1660
- const documentAnchors = allFactsRows.filter((f) => f.source_type === "user_document").map(({ id, title, source_ref }) => ({ id, title, source_ref }));
1700
+ const healCandidates = allFactsRows.filter((f) => f.source_type !== "immutable_document");
1701
+ const documentAnchors = allFactsRows.filter((f) => f.source_type === "immutable_document").map(({ id, title, source_ref }) => ({ id, title, source_ref }));
1661
1702
  const userPrompt = `Heal Candidates:
1662
1703
  ${JSON.stringify(healCandidates.map((f) => {
1663
1704
  const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
@@ -1700,7 +1741,7 @@ The following document anchors are provided for contradiction detection only. Do
1700
1741
  await this.db.runAsync(`
1701
1742
  INSERT INTO ${this.prefix}entries (id, entity_id, title, body, tags, confidence, source_type, created_at, updated_at)
1702
1743
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1703
- `, [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "agent_inferred", now, now]);
1744
+ `, [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "librarian_inferred", now, now]);
1704
1745
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
1705
1746
  }
1706
1747
  });
@@ -2000,6 +2041,7 @@ The following document anchors are provided for contradiction detection only. Do
2000
2041
  this.activeMaintenanceJobs.add(this._importKey(entityId));
2001
2042
  }
2002
2043
  try {
2044
+ await this.assertNoLegacySourceTypes();
2003
2045
  for (const [entityId, bundle] of Object.entries(dump.entities)) {
2004
2046
  await this._doImportEntity(entityId, bundle, merge);
2005
2047
  }
@@ -2053,6 +2095,10 @@ The following document anchors are provided for contradiction detection only. Do
2053
2095
  }
2054
2096
  }
2055
2097
  for (const fact of bundle.facts) {
2098
+ const sourceType = this._normalizeImportedSourceType(String(fact.source_type), {
2099
+ entityId,
2100
+ factId: fact.id
2101
+ });
2056
2102
  const tagsJson = JSON.stringify(Array.isArray(fact.tags) ? fact.tags : []);
2057
2103
  const safeUpdatedAt = Number.isFinite(fact.updated_at) ? fact.updated_at : 0;
2058
2104
  const existing = existingFactsById.get(fact.id);
@@ -2101,14 +2147,14 @@ The following document anchors are provided for contradiction detection only. Do
2101
2147
  if (blobData != null) {
2102
2148
  await this.db.runAsync(
2103
2149
  `UPDATE ${this.prefix}entries SET entity_id = ?, title = ?, body = ?, tags = ?, confidence = ?, source_type = ?, source_hash = ?, source_ref = ?, created_at = ?, updated_at = ?, last_accessed_at = ?, access_count = ?, deleted_at = ?, embedding_blob = ?, embedding = NULL WHERE id = ?`,
2104
- [entityId, fact.title, fact.body, tagsJson, fact.confidence, fact.source_type, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, blobData, fact.id]
2150
+ [entityId, fact.title, fact.body, tagsJson, fact.confidence, sourceType, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, blobData, fact.id]
2105
2151
  );
2106
2152
  factsWithPreservedBlob.set(fact.id, blobData);
2107
2153
  if (!fact.deleted_at) preservedBlobDims.add(blobData.byteLength / 4);
2108
2154
  } else {
2109
2155
  await this.db.runAsync(
2110
2156
  `UPDATE ${this.prefix}entries SET entity_id = ?, title = ?, body = ?, tags = ?, confidence = ?, source_type = ?, source_hash = ?, source_ref = ?, created_at = ?, updated_at = ?, last_accessed_at = ?, access_count = ?, deleted_at = ?, embedding_blob = NULL, embedding = NULL WHERE id = ?`,
2111
- [entityId, fact.title, fact.body, tagsJson, fact.confidence, fact.source_type, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, fact.id]
2157
+ [entityId, fact.title, fact.body, tagsJson, fact.confidence, sourceType, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, fact.id]
2112
2158
  );
2113
2159
  }
2114
2160
  existingFactsById.set(fact.id, { id: fact.id, entity_id: entityId, updated_at: safeUpdatedAt });
@@ -2118,14 +2164,14 @@ The following document anchors are provided for contradiction detection only. Do
2118
2164
  if (blobData != null) {
2119
2165
  await this.db.runAsync(
2120
2166
  `INSERT INTO ${this.prefix}entries (id, entity_id, title, body, tags, confidence, source_type, source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count, deleted_at, embedding_blob) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2121
- [fact.id, entityId, fact.title, fact.body, tagsJson, fact.confidence, fact.source_type, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, blobData]
2167
+ [fact.id, entityId, fact.title, fact.body, tagsJson, fact.confidence, sourceType, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at, blobData]
2122
2168
  );
2123
2169
  factsWithPreservedBlob.set(fact.id, blobData);
2124
2170
  if (!fact.deleted_at) preservedBlobDims.add(blobData.byteLength / 4);
2125
2171
  } else {
2126
2172
  await this.db.runAsync(
2127
2173
  `INSERT INTO ${this.prefix}entries (id, entity_id, title, body, tags, confidence, source_type, source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count, deleted_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2128
- [fact.id, entityId, fact.title, fact.body, tagsJson, fact.confidence, fact.source_type, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at]
2174
+ [fact.id, entityId, fact.title, fact.body, tagsJson, fact.confidence, sourceType, fact.source_hash, fact.source_ref, fact.created_at, safeUpdatedAt, fact.last_accessed_at, fact.access_count, fact.deleted_at]
2129
2175
  );
2130
2176
  }
2131
2177
  existingFactsById.set(fact.id, { id: fact.id, entity_id: entityId, updated_at: safeUpdatedAt });
@@ -2472,7 +2518,7 @@ ${chunk}`;
2472
2518
  await this.db.runAsync(
2473
2519
  `INSERT INTO ${this.prefix}entries (id, entity_id, title, body, tags, confidence, source_type, source_hash, source_ref, created_at, updated_at)
2474
2520
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2475
- [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "user_document", sourceHash, sourceRef, now, now]
2521
+ [id, entityId, fact.title, fact.body, JSON.stringify(fact.tags), fact.confidence, "immutable_document", sourceHash, sourceRef, now, now]
2476
2522
  );
2477
2523
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2478
2524
  }
@@ -2731,6 +2777,6 @@ function createWiki(db, options) {
2731
2777
  return new WikiMemory(db, options);
2732
2778
  }
2733
2779
 
2734
- export { PrunePartialFailureError, WikiBusyError, WikiMemory, createWiki, formatContext, formatMemoryDump };
2780
+ export { PrunePartialFailureError, WikiBusyError, WikiMemory, createWiki, formatContext, formatMemoryDump, parseEmbedding };
2735
2781
  //# sourceMappingURL=index.mjs.map
2736
2782
  //# sourceMappingURL=index.mjs.map