@pellux/goodvibes-sdk 1.1.0 → 1.3.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.
Files changed (139) hide show
  1. package/dist/contracts/artifacts/operator-contract.json +1416 -88
  2. package/dist/platform/companion/companion-chat-manager.d.ts +6 -12
  3. package/dist/platform/companion/companion-chat-manager.d.ts.map +1 -1
  4. package/dist/platform/companion/companion-chat-manager.js +8 -2
  5. package/dist/platform/config/index.d.ts +2 -1
  6. package/dist/platform/config/index.d.ts.map +1 -1
  7. package/dist/platform/config/index.js +1 -0
  8. package/dist/platform/config/manager.d.ts +35 -0
  9. package/dist/platform/config/manager.d.ts.map +1 -1
  10. package/dist/platform/config/manager.js +98 -2
  11. package/dist/platform/config/shared-config-tier.d.ts +51 -0
  12. package/dist/platform/config/shared-config-tier.d.ts.map +1 -0
  13. package/dist/platform/config/shared-config-tier.js +127 -0
  14. package/dist/platform/control-plane/method-catalog-runtime.d.ts.map +1 -1
  15. package/dist/platform/control-plane/method-catalog-runtime.js +95 -1
  16. package/dist/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
  17. package/dist/platform/control-plane/operator-contract-schemas-knowledge.js +6 -1
  18. package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts +7 -0
  19. package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts.map +1 -1
  20. package/dist/platform/control-plane/operator-contract-schemas-runtime.js +40 -1
  21. package/dist/platform/daemon/facade-composition.d.ts.map +1 -1
  22. package/dist/platform/daemon/facade-composition.js +1 -0
  23. package/dist/platform/daemon/homeassistant-chat.d.ts +4 -1
  24. package/dist/platform/daemon/homeassistant-chat.d.ts.map +1 -1
  25. package/dist/platform/daemon/homeassistant-chat.js +1 -1
  26. package/dist/platform/daemon/http/homeassistant-routes.d.ts.map +1 -1
  27. package/dist/platform/daemon/http/homeassistant-routes.js +28 -2
  28. package/dist/platform/integrations/webhooks.d.ts +15 -0
  29. package/dist/platform/integrations/webhooks.d.ts.map +1 -1
  30. package/dist/platform/integrations/webhooks.js +18 -0
  31. package/dist/platform/knowledge/extraction-policy.d.ts +12 -1
  32. package/dist/platform/knowledge/extraction-policy.d.ts.map +1 -1
  33. package/dist/platform/knowledge/extraction-policy.js +19 -1
  34. package/dist/platform/knowledge/graphql-schema.d.ts +1 -1
  35. package/dist/platform/knowledge/graphql-schema.d.ts.map +1 -1
  36. package/dist/platform/knowledge/graphql-schema.js +5 -0
  37. package/dist/platform/knowledge/home-graph/index.d.ts.map +1 -1
  38. package/dist/platform/knowledge/home-graph/index.js +4 -0
  39. package/dist/platform/knowledge/home-graph/link.d.ts +2 -2
  40. package/dist/platform/knowledge/home-graph/link.d.ts.map +1 -1
  41. package/dist/platform/knowledge/home-graph/link.js +60 -9
  42. package/dist/platform/knowledge/home-graph/refinement.d.ts +14 -9
  43. package/dist/platform/knowledge/home-graph/refinement.d.ts.map +1 -1
  44. package/dist/platform/knowledge/home-graph/refinement.js +18 -3
  45. package/dist/platform/knowledge/home-graph/reset.js +21 -4
  46. package/dist/platform/knowledge/home-graph/service.d.ts +10 -11
  47. package/dist/platform/knowledge/home-graph/service.d.ts.map +1 -1
  48. package/dist/platform/knowledge/home-graph/triage.d.ts +103 -0
  49. package/dist/platform/knowledge/home-graph/triage.d.ts.map +1 -0
  50. package/dist/platform/knowledge/home-graph/triage.js +362 -0
  51. package/dist/platform/knowledge/home-graph/types.d.ts +15 -0
  52. package/dist/platform/knowledge/home-graph/types.d.ts.map +1 -1
  53. package/dist/platform/knowledge/knowledge-api.d.ts +6 -0
  54. package/dist/platform/knowledge/knowledge-api.d.ts.map +1 -1
  55. package/dist/platform/knowledge/knowledge-api.js +5 -0
  56. package/dist/platform/knowledge/knowledge-history-types.d.ts +56 -0
  57. package/dist/platform/knowledge/knowledge-history-types.d.ts.map +1 -0
  58. package/dist/platform/knowledge/knowledge-history-types.js +1 -0
  59. package/dist/platform/knowledge/packet.d.ts.map +1 -1
  60. package/dist/platform/knowledge/packet.js +27 -3
  61. package/dist/platform/knowledge/scope-records.js +4 -2
  62. package/dist/platform/knowledge/semantic/answer-gaps.d.ts +8 -0
  63. package/dist/platform/knowledge/semantic/answer-gaps.d.ts.map +1 -1
  64. package/dist/platform/knowledge/semantic/answer-gaps.js +26 -6
  65. package/dist/platform/knowledge/semantic/enrichment.d.ts.map +1 -1
  66. package/dist/platform/knowledge/semantic/enrichment.js +15 -13
  67. package/dist/platform/knowledge/semantic/graph-index.d.ts.map +1 -1
  68. package/dist/platform/knowledge/semantic/graph-index.js +3 -1
  69. package/dist/platform/knowledge/semantic/service.d.ts +7 -0
  70. package/dist/platform/knowledge/semantic/service.d.ts.map +1 -1
  71. package/dist/platform/knowledge/semantic/service.js +10 -1
  72. package/dist/platform/knowledge/service-node-admin.d.ts +46 -0
  73. package/dist/platform/knowledge/service-node-admin.d.ts.map +1 -0
  74. package/dist/platform/knowledge/service-node-admin.js +72 -0
  75. package/dist/platform/knowledge/service.d.ts +24 -20
  76. package/dist/platform/knowledge/service.d.ts.map +1 -1
  77. package/dist/platform/knowledge/service.js +23 -47
  78. package/dist/platform/knowledge/store-config.d.ts +34 -0
  79. package/dist/platform/knowledge/store-config.d.ts.map +1 -1
  80. package/dist/platform/knowledge/store-config.js +23 -0
  81. package/dist/platform/knowledge/store-load.d.ts +3 -1
  82. package/dist/platform/knowledge/store-load.d.ts.map +1 -1
  83. package/dist/platform/knowledge/store-load.js +3 -1
  84. package/dist/platform/knowledge/store-node-history.d.ts +49 -0
  85. package/dist/platform/knowledge/store-node-history.d.ts.map +1 -0
  86. package/dist/platform/knowledge/store-node-history.js +331 -0
  87. package/dist/platform/knowledge/store-record-delete.d.ts +25 -0
  88. package/dist/platform/knowledge/store-record-delete.d.ts.map +1 -0
  89. package/dist/platform/knowledge/store-record-delete.js +84 -0
  90. package/dist/platform/knowledge/store-schema.d.ts +3 -1
  91. package/dist/platform/knowledge/store-schema.d.ts.map +1 -1
  92. package/dist/platform/knowledge/store-schema.js +66 -0
  93. package/dist/platform/knowledge/store.d.ts +19 -1
  94. package/dist/platform/knowledge/store.d.ts.map +1 -1
  95. package/dist/platform/knowledge/store.js +69 -108
  96. package/dist/platform/knowledge/types.d.ts +18 -0
  97. package/dist/platform/knowledge/types.d.ts.map +1 -1
  98. package/dist/platform/providers/registry.d.ts +18 -0
  99. package/dist/platform/providers/registry.d.ts.map +1 -1
  100. package/dist/platform/providers/registry.js +49 -3
  101. package/dist/platform/providers/runtime-snapshot.d.ts.map +1 -1
  102. package/dist/platform/providers/runtime-snapshot.js +14 -2
  103. package/dist/platform/runtime/memory-spine/client.d.ts +137 -12
  104. package/dist/platform/runtime/memory-spine/client.d.ts.map +1 -1
  105. package/dist/platform/runtime/memory-spine/client.js +119 -1
  106. package/dist/platform/runtime/memory-spine/index.d.ts +8 -1
  107. package/dist/platform/runtime/memory-spine/index.d.ts.map +1 -1
  108. package/dist/platform/runtime/memory-spine/index.js +7 -0
  109. package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts +74 -0
  110. package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts.map +1 -0
  111. package/dist/platform/runtime/memory-spine/recall-snapshot.js +87 -0
  112. package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts +59 -0
  113. package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts.map +1 -0
  114. package/dist/platform/runtime/memory-spine/wire-verb-availability.js +114 -0
  115. package/dist/platform/runtime/services.d.ts.map +1 -1
  116. package/dist/platform/runtime/services.js +3 -12
  117. package/dist/platform/state/memory-recall-contract.d.ts +7 -0
  118. package/dist/platform/state/memory-recall-contract.d.ts.map +1 -1
  119. package/dist/platform/state/memory-recall-contract.js +2 -0
  120. package/dist/platform/utils/notify.d.ts +25 -1
  121. package/dist/platform/utils/notify.d.ts.map +1 -1
  122. package/dist/platform/utils/notify.js +29 -1
  123. package/dist/platform/version.js +1 -1
  124. package/dist/platform/voice/index.d.ts +2 -0
  125. package/dist/platform/voice/index.d.ts.map +1 -1
  126. package/dist/platform/voice/index.js +3 -0
  127. package/dist/platform/voice/spoken-turn/audio-sink.d.ts +111 -0
  128. package/dist/platform/voice/spoken-turn/audio-sink.d.ts.map +1 -0
  129. package/dist/platform/voice/spoken-turn/audio-sink.js +1 -0
  130. package/dist/platform/voice/spoken-turn/controller.d.ts +117 -0
  131. package/dist/platform/voice/spoken-turn/controller.d.ts.map +1 -0
  132. package/dist/platform/voice/spoken-turn/controller.js +448 -0
  133. package/dist/platform/voice/spoken-turn/index.d.ts +16 -0
  134. package/dist/platform/voice/spoken-turn/index.d.ts.map +1 -0
  135. package/dist/platform/voice/spoken-turn/index.js +12 -0
  136. package/dist/platform/voice/spoken-turn/text-chunker.d.ts +33 -0
  137. package/dist/platform/voice/spoken-turn/text-chunker.d.ts.map +1 -0
  138. package/dist/platform/voice/spoken-turn/text-chunker.js +97 -0
  139. package/package.json +9 -9
@@ -8,6 +8,7 @@ import { renderKnowledgeMap } from './map.js';
8
8
  import { KnowledgeProjectionService } from './projections.js';
9
9
  import { KnowledgeSemanticService } from './semantic/index.js';
10
10
  import { KnowledgeStore } from './store.js';
11
+ import { reviewKnowledgeNodeRecord, queryKnowledgeIssues, queryKnowledgeNodes, } from './service-node-admin.js';
11
12
  import { ingestBrowserKnowledge } from './browser-history/index.js';
12
13
  import { buildKnowledgePacket, buildKnowledgePacketSync, buildKnowledgePromptPacket, buildKnowledgePromptPacketSync, searchKnowledge, } from './packet.js';
13
14
  import { ingestKnowledgeArtifact, ingestKnowledgeBookmarkSeeds, ingestKnowledgeConnectorInput, ingestKnowledgeUrl, ingestKnowledgeWithConnector, importKnowledgeBookmarksFromFile, importKnowledgeUrlsFromFile, refreshKnowledgeSources, pickKnowledgeRefreshCandidates, recompileKnowledgeSource, } from './ingest.js';
@@ -175,59 +176,34 @@ export class KnowledgeService {
175
176
  listNodes(limit = 100) {
176
177
  return this.queryNodes({ limit }).items;
177
178
  }
179
+ /** The append-only revision history of a node, oldest first. (Defect 1.) */
180
+ listNodeRevisions(nodeId) {
181
+ return this.store.listNodeRevisions(nodeId);
182
+ }
183
+ /** Honest hard delete of a single node — removes the row and its history. (Defect 6.) */
184
+ async deleteNode(id) {
185
+ return { deleted: await this.store.deleteNode(id) };
186
+ }
187
+ /** Honest hard delete of a single source and its derived records. (Defect 6.) */
188
+ async deleteSource(id) {
189
+ return { deleted: await this.store.deleteSource(id) };
190
+ }
191
+ /** Merge one node into another, re-pointing cross-reference edges. (Defect 5.) */
192
+ async mergeNodes(loserId, winnerId) {
193
+ return this.store.mergeNodes(loserId, winnerId);
194
+ }
195
+ /** Accept/reject a pending node — the decide step that governs activation. (Defect 2.) */
196
+ async reviewNode(input) {
197
+ return reviewKnowledgeNodeRecord(this.store, input);
198
+ }
178
199
  queryNodes(input = {}) {
179
- const limit = Math.max(1, input.limit ?? 100);
180
- const offset = Math.max(0, input.offset ?? 0);
181
- const queryTokens = tokenize(input.query ?? '');
182
- const scopeLookup = this.getScopeLookup();
183
- const items = this.store.listNodes(Number.MAX_SAFE_INTEGER).filter((node) => {
184
- if (!knowledgeNodeMatchesScope(node, input, scopeLookup))
185
- return false;
186
- if (input.kind && node.kind !== input.kind)
187
- return false;
188
- if (input.status && node.status !== input.status)
189
- return false;
190
- if (queryTokens.length === 0)
191
- return true;
192
- const haystack = [
193
- node.title,
194
- node.summary ?? '',
195
- node.aliases.join(' '),
196
- JSON.stringify(node.metadata),
197
- ].join(' ').toLowerCase();
198
- return queryTokens.every((token) => haystack.includes(token));
199
- });
200
- return {
201
- total: items.length,
202
- items: items.slice(offset, offset + limit),
203
- };
200
+ return queryKnowledgeNodes(this.store, this.getScopeLookup(), input);
204
201
  }
205
202
  listIssues(limit = 100) {
206
203
  return this.queryIssues({ limit }).items;
207
204
  }
208
205
  queryIssues(input = {}) {
209
- const limit = Math.max(1, input.limit ?? 100);
210
- const offset = Math.max(0, input.offset ?? 0);
211
- const queryTokens = tokenize(input.query ?? '');
212
- const scopeLookup = this.getScopeLookup();
213
- const items = this.store.listIssues(Number.MAX_SAFE_INTEGER).filter((issue) => {
214
- if (!knowledgeIssueMatchesScope(issue, input, scopeLookup))
215
- return false;
216
- if (input.severity && issue.severity !== input.severity)
217
- return false;
218
- if (input.status && issue.status !== input.status)
219
- return false;
220
- if (input.code && issue.code !== input.code)
221
- return false;
222
- if (queryTokens.length === 0)
223
- return true;
224
- const haystack = [issue.message, issue.code, JSON.stringify(issue.metadata)].join(' ').toLowerCase();
225
- return queryTokens.every((token) => haystack.includes(token));
226
- });
227
- return {
228
- total: items.length,
229
- items: items.slice(offset, offset + limit),
230
- };
206
+ return queryKnowledgeIssues(this.store, this.getScopeLookup(), input);
231
207
  }
232
208
  async reviewIssue(input) {
233
209
  return reviewKnowledgeIssue(this.store, input);
@@ -1,12 +1,46 @@
1
1
  export declare const REGULAR_KNOWLEDGE_DB_FILE = "knowledge-wiki.sqlite";
2
2
  export declare const HOME_GRAPH_KNOWLEDGE_DB_FILE = "knowledge-home-graph.sqlite";
3
3
  export declare const GOODVIBES_AGENT_KNOWLEDGE_DB_FILE = "knowledge-agent.sqlite";
4
+ /**
5
+ * The three physically separate knowledge families. There is one KnowledgeStore
6
+ * class and one schema; the wall between the general wiki, the home-graph, and the
7
+ * agent personal-ops knowledge is held by construction — separate SQLite files
8
+ * opened by separate store instances. When a caller declares which family it is
9
+ * constructing, the store asserts the resolved file name matches that family so a
10
+ * mis-wired construction site fails loudly instead of silently sharing a file.
11
+ */
12
+ export type KnowledgeStoreFamily = 'wiki' | 'home-graph' | 'agent';
13
+ export declare const KNOWLEDGE_FAMILY_DB_FILE: Readonly<Record<KnowledgeStoreFamily, string>>;
4
14
  export interface KnowledgeStoreConfig {
5
15
  readonly dbPath?: string | undefined;
6
16
  readonly dbFileName?: string | undefined;
17
+ /**
18
+ * The knowledge family this store belongs to. When provided, the resolved db
19
+ * file name must match {@link KNOWLEDGE_FAMILY_DB_FILE} for that family, or
20
+ * construction throws — the cheap guard that turns a silent cross-family
21
+ * mis-wire into a loud failure.
22
+ */
23
+ readonly family?: KnowledgeStoreFamily | undefined;
24
+ /**
25
+ * Auto-accept confidence threshold for the node review gate. A synthesized node
26
+ * whose confidence is at or above this value is activated with honest
27
+ * 'auto-accepted' provenance; below it, the node is held as 'draft'
28
+ * (pending review) and is not served by search/ask until reviewed. Defaults to
29
+ * {@link DEFAULT_NODE_AUTO_ACCEPT_CONFIDENCE}.
30
+ */
31
+ readonly nodeAutoAcceptConfidence?: number | undefined;
7
32
  readonly configManager?: {
8
33
  getControlPlaneConfigDir?: (() => string) | undefined;
9
34
  };
10
35
  }
36
+ /**
37
+ * Default auto-accept threshold. Chosen just below the lowest confidence the
38
+ * existing synthesis producers emit (deterministic facts at 45, deterministic
39
+ * wiki pages at 55) so those flows keep activating — now with honest,
40
+ * numeric provenance rather than silently — while genuinely low-confidence
41
+ * synthesized content (below 40) is held as 'draft' for review. The mechanism is
42
+ * the deliverable; consumers raise this threshold to hold more for review.
43
+ */
44
+ export declare const DEFAULT_NODE_AUTO_ACCEPT_CONFIDENCE = 40;
11
45
  export declare function resolveKnowledgeDbPath(config: KnowledgeStoreConfig): string;
12
46
  //# sourceMappingURL=store-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-config.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-config.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,yBAAyB,0BAA4B,CAAC;AACnE,eAAO,MAAM,4BAA4B,gCAAgC,CAAC;AAC1E,eAAO,MAAM,iCAAiC,2BAA2B,CAAC;AAE1E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,wBAAwB,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;KACvD,CAAC;CACH;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAS3E"}
1
+ {"version":3,"file":"store-config.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-config.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,yBAAyB,0BAA4B,CAAC;AACnE,eAAO,MAAM,4BAA4B,gCAAgC,CAAC;AAC1E,eAAO,MAAM,iCAAiC,2BAA2B,CAAC;AAE1E;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC;AAEnE,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAIlF,CAAC;AAEH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACnD;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,wBAAwB,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;KACvD,CAAC;CACH;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,KAAK,CAAC;AAEtD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAmB3E"}
@@ -1,7 +1,22 @@
1
+ import { basename } from 'node:path';
1
2
  import { DEFAULT_KNOWLEDGE_DB_FILE, resolveKnowledgeDbPathFromControlPlaneDir, } from './store-schema.js';
2
3
  export const REGULAR_KNOWLEDGE_DB_FILE = DEFAULT_KNOWLEDGE_DB_FILE;
3
4
  export const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
4
5
  export const GOODVIBES_AGENT_KNOWLEDGE_DB_FILE = 'knowledge-agent.sqlite';
6
+ export const KNOWLEDGE_FAMILY_DB_FILE = Object.freeze({
7
+ wiki: REGULAR_KNOWLEDGE_DB_FILE,
8
+ 'home-graph': HOME_GRAPH_KNOWLEDGE_DB_FILE,
9
+ agent: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
10
+ });
11
+ /**
12
+ * Default auto-accept threshold. Chosen just below the lowest confidence the
13
+ * existing synthesis producers emit (deterministic facts at 45, deterministic
14
+ * wiki pages at 55) so those flows keep activating — now with honest,
15
+ * numeric provenance rather than silently — while genuinely low-confidence
16
+ * synthesized content (below 40) is held as 'draft' for review. The mechanism is
17
+ * the deliverable; consumers raise this threshold to hold more for review.
18
+ */
19
+ export const DEFAULT_NODE_AUTO_ACCEPT_CONFIDENCE = 40;
5
20
  export function resolveKnowledgeDbPath(config) {
6
21
  const controlPlaneDir = typeof config.configManager?.getControlPlaneConfigDir === 'function'
7
22
  ? config.configManager.getControlPlaneConfigDir()
@@ -11,5 +26,13 @@ export function resolveKnowledgeDbPath(config) {
11
26
  : undefined);
12
27
  if (!dbPath)
13
28
  throw new Error('KnowledgeStore requires an explicit dbPath or configManager.getControlPlaneConfigDir().');
29
+ if (config.family) {
30
+ const expected = KNOWLEDGE_FAMILY_DB_FILE[config.family];
31
+ const actual = basename(dbPath);
32
+ if (actual !== expected) {
33
+ throw new Error(`KnowledgeStore family mismatch: constructed for '${config.family}' (expected file '${expected}') but resolved db file is '${actual}'. `
34
+ + 'This guards the wall between the wiki, home-graph, and agent knowledge families — the construction site is wired to the wrong file.');
35
+ }
36
+ }
14
37
  return dbPath;
15
38
  }
@@ -1,5 +1,5 @@
1
1
  import type { SQLiteStore } from '../state/sqlite-store.js';
2
- import type { KnowledgeConsolidationCandidateRecord, KnowledgeConsolidationReportRecord, KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeIssueRecord, KnowledgeJobRunRecord, KnowledgeNodeRecord, KnowledgeRefinementTaskRecord, KnowledgeScheduleRecord, KnowledgeSourceRecord, KnowledgeUsageRecord } from './types.js';
2
+ import type { KnowledgeConsolidationCandidateRecord, KnowledgeConsolidationReportRecord, KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeIssueRecord, KnowledgeJobRunRecord, KnowledgeNodeRecord, KnowledgeNodeRevisionRecord, KnowledgeRefinementTaskRecord, KnowledgeScheduleRecord, KnowledgeSemanticEnrichmentStateRecord, KnowledgeSourceRecord, KnowledgeUsageRecord } from './types.js';
3
3
  export interface KnowledgeStoreSnapshot {
4
4
  readonly sources: KnowledgeSourceRecord[];
5
5
  readonly nodes: KnowledgeNodeRecord[];
@@ -12,6 +12,8 @@ export interface KnowledgeStoreSnapshot {
12
12
  readonly consolidationCandidates: KnowledgeConsolidationCandidateRecord[];
13
13
  readonly consolidationReports: KnowledgeConsolidationReportRecord[];
14
14
  readonly schedules: KnowledgeScheduleRecord[];
15
+ readonly nodeRevisions: KnowledgeNodeRevisionRecord[];
16
+ readonly semanticEnrichmentStates: KnowledgeSemanticEnrichmentStateRecord[];
15
17
  }
16
18
  export declare function loadKnowledgeStoreSnapshot(sqlite: SQLiteStore): KnowledgeStoreSnapshot;
17
19
  //# sourceMappingURL=store-load.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-load.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-load.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAc5D,OAAO,KAAK,EACV,qCAAqC,EACrC,kCAAkC,EAClC,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,EAC7B,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,yBAAyB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,6BAA6B,EAAE,CAAC;IAC1D,QAAQ,CAAC,YAAY,EAAE,oBAAoB,EAAE,CAAC;IAC9C,QAAQ,CAAC,uBAAuB,EAAE,qCAAqC,EAAE,CAAC;IAC1E,QAAQ,CAAC,oBAAoB,EAAE,kCAAkC,EAAE,CAAC;IACpE,QAAQ,CAAC,SAAS,EAAE,uBAAuB,EAAE,CAAC;CAC/C;AAQD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,WAAW,GAAG,sBAAsB,CActF"}
1
+ {"version":3,"file":"store-load.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-load.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAgB5D,OAAO,KAAK,EACV,qCAAqC,EACrC,kCAAkC,EAClC,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,2BAA2B,EAC3B,6BAA6B,EAC7B,uBAAuB,EACvB,sCAAsC,EACtC,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,yBAAyB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,6BAA6B,EAAE,CAAC;IAC1D,QAAQ,CAAC,YAAY,EAAE,oBAAoB,EAAE,CAAC;IAC9C,QAAQ,CAAC,uBAAuB,EAAE,qCAAqC,EAAE,CAAC;IAC1E,QAAQ,CAAC,oBAAoB,EAAE,kCAAkC,EAAE,CAAC;IACpE,QAAQ,CAAC,SAAS,EAAE,uBAAuB,EAAE,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,2BAA2B,EAAE,CAAC;IACtD,QAAQ,CAAC,wBAAwB,EAAE,sCAAsC,EAAE,CAAC;CAC7E;AAQD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,WAAW,GAAG,sBAAsB,CAgBtF"}
@@ -1,4 +1,4 @@
1
- import { mapCandidateRow, mapEdgeRow, mapExtractionRow, mapIssueRow, mapJobRunRow, mapNodeRow, mapRefinementTaskRow, mapReportRow, mapScheduleRow, mapSourceRow, mapUsageRow, } from './store-schema.js';
1
+ import { mapCandidateRow, mapEdgeRow, mapExtractionRow, mapIssueRow, mapJobRunRow, mapNodeRevisionRow, mapNodeRow, mapRefinementTaskRow, mapReportRow, mapScheduleRow, mapSemanticEnrichmentStateRow, mapSourceRow, mapUsageRow, } from './store-schema.js';
2
2
  function loadRows(sqlite, sql, mapRow) {
3
3
  const rows = sqlite.exec(sql);
4
4
  if (!rows.length)
@@ -18,5 +18,7 @@ export function loadKnowledgeStoreSnapshot(sqlite) {
18
18
  consolidationCandidates: loadRows(sqlite, 'SELECT * FROM knowledge_consolidation_candidates', mapCandidateRow),
19
19
  consolidationReports: loadRows(sqlite, 'SELECT * FROM knowledge_consolidation_reports', mapReportRow),
20
20
  schedules: loadRows(sqlite, 'SELECT * FROM knowledge_schedules', mapScheduleRow),
21
+ nodeRevisions: loadRows(sqlite, 'SELECT * FROM knowledge_node_revisions ORDER BY node_id, revision', mapNodeRevisionRow),
22
+ semanticEnrichmentStates: loadRows(sqlite, 'SELECT * FROM knowledge_semantic_enrichment_state', mapSemanticEnrichmentStateRow),
21
23
  };
22
24
  }
@@ -0,0 +1,49 @@
1
+ import type { SQLiteStore } from '../state/sqlite-store.js';
2
+ import type { KnowledgeStore } from './store.js';
3
+ import type { KnowledgeNodeRecord, KnowledgeNodeRevisionRecord, KnowledgeNodeUpsertInput, KnowledgeSemanticEnrichmentStateRecord } from './types.js';
4
+ export declare function writeKnowledgeNodeRow(sqlite: SQLiteStore, record: KnowledgeNodeRecord): void;
5
+ export declare function clampConfidence(value: number): number;
6
+ /**
7
+ * Decide a node's effective status and stamp honest review provenance so a node is
8
+ * never silently active. (Invariants 2 & 4.)
9
+ * - An explicit producer status (or a review that applied facts) is honored and
10
+ * labelled 'explicit'/'reviewed'.
11
+ * - An already-active node stays active; if it predates the gate it is labelled
12
+ * 'pre-gate' (folds/migrations never get downgraded).
13
+ * - A new/draft node auto-accepts at/above the configured confidence threshold
14
+ * (labelled 'auto-accepted') or is held as 'draft' pending review otherwise.
15
+ */
16
+ export declare function resolveNodeActivation(args: {
17
+ readonly input: KnowledgeNodeUpsertInput;
18
+ readonly existing: KnowledgeNodeRecord | undefined;
19
+ readonly confidence: number;
20
+ readonly metadata: Record<string, unknown>;
21
+ readonly now: number;
22
+ readonly autoAcceptConfidence: number;
23
+ }): {
24
+ status: KnowledgeNodeRecord['status'];
25
+ metadata: Record<string, unknown>;
26
+ };
27
+ /**
28
+ * Record an append-only revision on a content-changing upsert: preserve the
29
+ * overwritten prior content and note what changed. (Invariant 8.)
30
+ */
31
+ export declare function recordKnowledgeNodeRevisions(sqlite: SQLiteStore, nodeRevisions: Map<string, KnowledgeNodeRevisionRecord[]>, record: KnowledgeNodeRecord, existing: KnowledgeNodeRecord | undefined, now: number): void;
32
+ export declare function listKnowledgeNodeRevisions(nodeRevisions: Map<string, KnowledgeNodeRevisionRecord[]>, nodeId: string): KnowledgeNodeRevisionRecord[];
33
+ /**
34
+ * Merge one node into another: re-point every edge that referenced the loser onto
35
+ * the winner (deduping and dropping self-loops), record a `merged_into` edge, and
36
+ * mark the loser 'stale' with a `mergedInto` stamp. Keeps cross-references live on
37
+ * the surviving node. (Invariant 7.)
38
+ */
39
+ export declare function mergeKnowledgeNodes(store: KnowledgeStore, loserId: string, winnerId: string): Promise<{
40
+ merged: boolean;
41
+ repointedEdges: number;
42
+ }>;
43
+ export declare function upsertKnowledgeSemanticEnrichmentState(sqlite: SQLiteStore, states: Map<string, KnowledgeSemanticEnrichmentStateRecord>, input: {
44
+ readonly sourceId: string;
45
+ readonly textHash?: string | undefined;
46
+ readonly enrichedAt?: number | undefined;
47
+ readonly metadata?: Record<string, unknown> | undefined;
48
+ }): KnowledgeSemanticEnrichmentStateRecord;
49
+ //# sourceMappingURL=store-node-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-node-history.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-node-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EACV,mBAAmB,EAInB,2BAA2B,EAC3B,wBAAwB,EACxB,sCAAsC,EACvC,MAAM,YAAY,CAAC;AAEpB,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAmB5F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUrD;AA0BD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACvC,GAAG;IAAE,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CA4D/E;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,WAAW,EACnB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,2BAA2B,EAAE,CAAC,EACzD,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,mBAAmB,GAAG,SAAS,EACzC,GAAG,EAAE,MAAM,GACV,IAAI,CAeN;AAED,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,2BAA2B,EAAE,CAAC,EACzD,MAAM,EAAE,MAAM,GACb,2BAA2B,EAAE,CAE/B;AAkGD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,CAiDtD;AAED,wBAAgB,sCAAsC,CACpD,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,sCAAsC,CAAC,EAC3D,KAAK,EAAE;IACL,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,GACA,sCAAsC,CAyBxC"}
@@ -0,0 +1,331 @@
1
+ import { nowMs } from './store-schema.js';
2
+ import { DEFAULT_NODE_AUTO_ACCEPT_CONFIDENCE } from './store-config.js';
3
+ export function writeKnowledgeNodeRow(sqlite, record) {
4
+ sqlite.run(`
5
+ INSERT OR REPLACE INTO knowledge_nodes (
6
+ id, kind, slug, title, summary, aliases, status, confidence, source_id, metadata, created_at, updated_at
7
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8
+ `, [
9
+ record.id,
10
+ record.kind,
11
+ record.slug,
12
+ record.title,
13
+ record.summary ?? null,
14
+ JSON.stringify([...record.aliases]),
15
+ record.status,
16
+ record.confidence,
17
+ record.sourceId ?? null,
18
+ JSON.stringify(record.metadata),
19
+ record.createdAt,
20
+ record.updatedAt,
21
+ ]);
22
+ }
23
+ export function clampConfidence(value) {
24
+ // A non-finite value (NaN/Infinity — slips past a `??` guard, which only catches
25
+ // null/undefined) resolves to the auto-accept default rather than poisoning the
26
+ // gate with `NaN >= threshold === false` and silently drafting the node forever.
27
+ if (!Number.isFinite(value))
28
+ return DEFAULT_NODE_AUTO_ACCEPT_CONFIDENCE;
29
+ // Confidence is a 0-100 score everywhere. A fractional value below 1 (e.g. 0.9) is
30
+ // almost certainly a 0-1 probability a producer/LLM emitted despite that contract —
31
+ // scale it up so 0.9 → 90 instead of Math.round truncating it to a draft-holding 1.
32
+ const scaled = value > 0 && value < 1 ? value * 100 : value;
33
+ return Math.max(0, Math.min(100, Math.round(scaled)));
34
+ }
35
+ function isPlainRecord(value) {
36
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
37
+ }
38
+ function readNodeReviewer(metadata) {
39
+ const review = metadata.review;
40
+ if (!isPlainRecord(review))
41
+ return undefined;
42
+ const reviewer = review.reviewer;
43
+ return typeof reviewer === 'string' && reviewer.trim().length > 0 ? reviewer.trim() : 'knowledge-review';
44
+ }
45
+ function readNodeProvenanceState(metadata) {
46
+ const provenance = metadata.reviewProvenance;
47
+ if (!isPlainRecord(provenance))
48
+ return undefined;
49
+ return typeof provenance.state === 'string' ? provenance.state : undefined;
50
+ }
51
+ function stampNodeProvenance(metadata, provenance) {
52
+ return { ...metadata, reviewProvenance: provenance };
53
+ }
54
+ /**
55
+ * Decide a node's effective status and stamp honest review provenance so a node is
56
+ * never silently active. (Invariants 2 & 4.)
57
+ * - An explicit producer status (or a review that applied facts) is honored and
58
+ * labelled 'explicit'/'reviewed'.
59
+ * - An already-active node stays active; if it predates the gate it is labelled
60
+ * 'pre-gate' (folds/migrations never get downgraded).
61
+ * - A new/draft node auto-accepts at/above the configured confidence threshold
62
+ * (labelled 'auto-accepted') or is held as 'draft' pending review otherwise.
63
+ */
64
+ export function resolveNodeActivation(args) {
65
+ const { input, existing, confidence, metadata, now, autoAcceptConfidence } = args;
66
+ const reviewer = readNodeReviewer(metadata);
67
+ const reviewedByFacts = reviewer !== undefined;
68
+ if (input.status) {
69
+ const state = reviewedByFacts ? 'reviewed' : 'explicit';
70
+ return {
71
+ status: input.status,
72
+ metadata: stampNodeProvenance(metadata, {
73
+ state,
74
+ reason: reviewedByFacts
75
+ ? `reviewed: status set to '${input.status}' by ${reviewer}`
76
+ : `explicit: producer set status '${input.status}'`,
77
+ decidedAt: now,
78
+ ...(reviewer !== undefined ? { reviewer } : {}),
79
+ }),
80
+ };
81
+ }
82
+ if (reviewedByFacts && existing) {
83
+ return {
84
+ status: existing.status,
85
+ metadata: stampNodeProvenance(metadata, {
86
+ state: 'reviewed',
87
+ reason: `reviewed by ${reviewer}`,
88
+ decidedAt: now,
89
+ reviewer,
90
+ }),
91
+ };
92
+ }
93
+ if (existing && existing.status === 'active') {
94
+ if (readNodeProvenanceState(metadata))
95
+ return { status: 'active', metadata };
96
+ return {
97
+ status: 'active',
98
+ metadata: stampNodeProvenance(metadata, {
99
+ state: 'pre-gate',
100
+ reason: 'pre-gate: node was active before the review gate; left active',
101
+ decidedAt: now,
102
+ }),
103
+ };
104
+ }
105
+ if (confidence >= autoAcceptConfidence) {
106
+ return {
107
+ status: 'active',
108
+ metadata: stampNodeProvenance(metadata, {
109
+ state: 'auto-accepted',
110
+ reason: `auto-accepted: confidence ${confidence} >= auto-accept threshold ${autoAcceptConfidence}`,
111
+ decidedAt: now,
112
+ threshold: autoAcceptConfidence,
113
+ }),
114
+ };
115
+ }
116
+ return {
117
+ status: 'draft',
118
+ metadata: stampNodeProvenance(metadata, {
119
+ state: 'pending-review',
120
+ reason: `pending review: confidence ${confidence} < auto-accept threshold ${autoAcceptConfidence}`,
121
+ decidedAt: now,
122
+ threshold: autoAcceptConfidence,
123
+ }),
124
+ };
125
+ }
126
+ /**
127
+ * Record an append-only revision on a content-changing upsert: preserve the
128
+ * overwritten prior content and note what changed. (Invariant 8.)
129
+ */
130
+ export function recordKnowledgeNodeRevisions(sqlite, nodeRevisions, record, existing, now) {
131
+ const list = nodeRevisions.get(record.id) ?? [];
132
+ if (existing) {
133
+ const changedFields = diffKnowledgeNodeFields(existing, record);
134
+ if (changedFields.length === 0)
135
+ return; // idempotent re-upsert (e.g. a provenance-only restamp)
136
+ if (list.length === 0) {
137
+ // First tracked change to a pre-existing node: preserve the overwritten prior
138
+ // content as the baseline revision so it is never lost.
139
+ appendNodeRevision(sqlite, list, existing, 'create', [], now);
140
+ }
141
+ appendNodeRevision(sqlite, list, record, 'update', changedFields, now);
142
+ }
143
+ else {
144
+ appendNodeRevision(sqlite, list, record, 'create', [], now);
145
+ }
146
+ nodeRevisions.set(record.id, list);
147
+ }
148
+ export function listKnowledgeNodeRevisions(nodeRevisions, nodeId) {
149
+ return [...(nodeRevisions.get(nodeId) ?? [])].sort((a, b) => a.revision - b.revision);
150
+ }
151
+ function appendNodeRevision(sqlite, list, snapshot, changeKind, changedFields, now) {
152
+ const revision = (list[list.length - 1]?.revision ?? 0) + 1;
153
+ const rev = {
154
+ id: `noderev-${snapshot.id}-${revision}`,
155
+ nodeId: snapshot.id,
156
+ revision,
157
+ changeKind,
158
+ changedFields: [...changedFields],
159
+ kind: snapshot.kind,
160
+ slug: snapshot.slug,
161
+ title: snapshot.title,
162
+ ...(snapshot.summary ? { summary: snapshot.summary } : {}),
163
+ aliases: [...snapshot.aliases],
164
+ status: snapshot.status,
165
+ confidence: snapshot.confidence,
166
+ ...(snapshot.sourceId ? { sourceId: snapshot.sourceId } : {}),
167
+ metadata: snapshot.metadata,
168
+ nodeCreatedAt: snapshot.createdAt,
169
+ nodeUpdatedAt: snapshot.updatedAt,
170
+ recordedAt: now,
171
+ };
172
+ sqlite.run(`
173
+ INSERT OR REPLACE INTO knowledge_node_revisions (
174
+ id, node_id, revision, change_kind, changed_fields, kind, slug, title, summary,
175
+ aliases, status, confidence, source_id, metadata, node_created_at, node_updated_at, recorded_at
176
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
177
+ `, [
178
+ rev.id,
179
+ rev.nodeId,
180
+ rev.revision,
181
+ rev.changeKind,
182
+ JSON.stringify(rev.changedFields),
183
+ rev.kind,
184
+ rev.slug,
185
+ rev.title,
186
+ rev.summary ?? null,
187
+ JSON.stringify([...rev.aliases]),
188
+ rev.status,
189
+ rev.confidence,
190
+ rev.sourceId ?? null,
191
+ JSON.stringify(rev.metadata),
192
+ rev.nodeCreatedAt,
193
+ rev.nodeUpdatedAt,
194
+ rev.recordedAt,
195
+ ]);
196
+ list.push(rev);
197
+ }
198
+ function diffKnowledgeNodeFields(prev, next) {
199
+ const changed = [];
200
+ // Identity fields are tracked: an id-based upsert that changes ONLY the kind or
201
+ // slug (all other fields identical) is still a real content change and must
202
+ // record a revision — otherwise `changedFields` is empty, the early return in
203
+ // recordKnowledgeNodeRevisions fires, and the prior slug/kind is lost from
204
+ // history with no trace.
205
+ if (prev.kind !== next.kind)
206
+ changed.push('kind');
207
+ if (prev.slug !== next.slug)
208
+ changed.push('slug');
209
+ if (prev.title !== next.title)
210
+ changed.push('title');
211
+ if ((prev.summary ?? '') !== (next.summary ?? ''))
212
+ changed.push('summary');
213
+ if (prev.status !== next.status)
214
+ changed.push('status');
215
+ if (prev.confidence !== next.confidence)
216
+ changed.push('confidence');
217
+ if ((prev.sourceId ?? '') !== (next.sourceId ?? ''))
218
+ changed.push('sourceId');
219
+ if (!sameStringSet(prev.aliases, next.aliases))
220
+ changed.push('aliases');
221
+ if (stableMetadataForDiff(prev.metadata) !== stableMetadataForDiff(next.metadata))
222
+ changed.push('metadata');
223
+ return changed;
224
+ }
225
+ function sameStringSet(a, b) {
226
+ if (a.length !== b.length)
227
+ return false;
228
+ const set = new Set(a);
229
+ return b.every((entry) => set.has(entry));
230
+ }
231
+ // Compare metadata for revision purposes ignoring the volatile review-provenance
232
+ // stamp (its decidedAt changes on every write); a provenance-only restamp must not
233
+ // count as a content change.
234
+ function stableMetadataForDiff(metadata) {
235
+ const { reviewProvenance: _reviewProvenance, ...rest } = metadata;
236
+ return stableStringify(rest);
237
+ }
238
+ function stableStringify(value) {
239
+ if (Array.isArray(value))
240
+ return `[${value.map((entry) => stableStringify(entry)).join(',')}]`;
241
+ if (isPlainRecord(value)) {
242
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
243
+ }
244
+ return JSON.stringify(value ?? null);
245
+ }
246
+ /**
247
+ * Merge one node into another: re-point every edge that referenced the loser onto
248
+ * the winner (deduping and dropping self-loops), record a `merged_into` edge, and
249
+ * mark the loser 'stale' with a `mergedInto` stamp. Keeps cross-references live on
250
+ * the surviving node. (Invariant 7.)
251
+ */
252
+ export async function mergeKnowledgeNodes(store, loserId, winnerId) {
253
+ if (loserId === winnerId)
254
+ return { merged: false, repointedEdges: 0 };
255
+ const loser = store.getNode(loserId);
256
+ const winner = store.getNode(winnerId);
257
+ if (!loser || !winner)
258
+ return { merged: false, repointedEdges: 0 };
259
+ const mergedAt = nowMs();
260
+ let repointedEdges = 0;
261
+ await store.batch(async () => {
262
+ for (const edge of store.listEdges()) {
263
+ const fromMatch = edge.fromKind === 'node' && edge.fromId === loserId;
264
+ const toMatch = edge.toKind === 'node' && edge.toId === loserId;
265
+ if (!fromMatch && !toMatch)
266
+ continue;
267
+ const newFromId = fromMatch ? winnerId : edge.fromId;
268
+ const newToId = toMatch ? winnerId : edge.toId;
269
+ await store.deleteEdge(edge.id);
270
+ if (newFromId === newToId && edge.fromKind === edge.toKind)
271
+ continue;
272
+ await store.upsertEdge({
273
+ fromKind: edge.fromKind,
274
+ fromId: newFromId,
275
+ toKind: edge.toKind,
276
+ toId: newToId,
277
+ relation: edge.relation,
278
+ weight: edge.weight,
279
+ metadata: { ...edge.metadata, repointedFromNodeId: loserId, repointedAt: mergedAt },
280
+ });
281
+ repointedEdges += 1;
282
+ }
283
+ await store.upsertEdge({
284
+ fromKind: 'node',
285
+ fromId: loserId,
286
+ toKind: 'node',
287
+ toId: winnerId,
288
+ relation: 'merged_into',
289
+ metadata: { mergedAt },
290
+ });
291
+ await store.upsertNode({
292
+ id: loser.id,
293
+ kind: loser.kind,
294
+ slug: loser.slug,
295
+ title: loser.title,
296
+ ...(loser.summary ? { summary: loser.summary } : {}),
297
+ aliases: [...loser.aliases],
298
+ status: 'stale',
299
+ confidence: loser.confidence,
300
+ ...(loser.sourceId ? { sourceId: loser.sourceId } : {}),
301
+ metadata: { ...loser.metadata, mergedInto: winnerId, mergedAt },
302
+ });
303
+ });
304
+ return { merged: true, repointedEdges };
305
+ }
306
+ export function upsertKnowledgeSemanticEnrichmentState(sqlite, states, input) {
307
+ const existing = states.get(input.sourceId);
308
+ const now = nowMs();
309
+ const record = {
310
+ sourceId: input.sourceId,
311
+ ...(input.textHash ? { textHash: input.textHash } : {}),
312
+ ...(typeof input.enrichedAt === 'number' ? { enrichedAt: input.enrichedAt } : {}),
313
+ metadata: input.metadata ?? {},
314
+ createdAt: existing?.createdAt ?? now,
315
+ updatedAt: now,
316
+ };
317
+ sqlite.run(`
318
+ INSERT OR REPLACE INTO knowledge_semantic_enrichment_state (
319
+ source_id, text_hash, enriched_at, metadata, created_at, updated_at
320
+ ) VALUES (?, ?, ?, ?, ?, ?)
321
+ `, [
322
+ record.sourceId,
323
+ record.textHash ?? null,
324
+ record.enrichedAt ?? null,
325
+ JSON.stringify(record.metadata),
326
+ record.createdAt,
327
+ record.updatedAt,
328
+ ]);
329
+ states.set(record.sourceId, record);
330
+ return record;
331
+ }
@@ -0,0 +1,25 @@
1
+ import type { SQLiteStore } from '../state/sqlite-store.js';
2
+ import type { KnowledgeConsolidationCandidateRecord, KnowledgeEdgeRecord, KnowledgeExtractionRecord, KnowledgeIssueRecord, KnowledgeNodeRecord, KnowledgeNodeRevisionRecord, KnowledgeRefinementTaskRecord, KnowledgeSemanticEnrichmentStateRecord, KnowledgeSourceRecord, KnowledgeUsageRecord } from './types.js';
3
+ /**
4
+ * The store internals needed to cascade a single-record hard delete. Both node and
5
+ * source deletes cascade edges, issues, usage records, consolidation candidates and
6
+ * refinement tasks (Defect 7 closed the refinement-task gap); node delete also
7
+ * purges revision history and source delete purges derived extraction/enrichment
8
+ * rows. Honest hard delete — no soft-hide. (Defect 6.)
9
+ */
10
+ export interface KnowledgeRecordDeleteView {
11
+ readonly sqlite: SQLiteStore;
12
+ readonly sources: Map<string, KnowledgeSourceRecord>;
13
+ readonly nodes: Map<string, KnowledgeNodeRecord>;
14
+ readonly edges: Map<string, KnowledgeEdgeRecord>;
15
+ readonly issues: Map<string, KnowledgeIssueRecord>;
16
+ readonly extractions: Map<string, KnowledgeExtractionRecord>;
17
+ readonly usageRecords: Map<string, KnowledgeUsageRecord>;
18
+ readonly consolidationCandidates: Map<string, KnowledgeConsolidationCandidateRecord>;
19
+ readonly refinementTasks: Map<string, KnowledgeRefinementTaskRecord>;
20
+ readonly nodeRevisions: Map<string, KnowledgeNodeRevisionRecord[]>;
21
+ readonly semanticEnrichmentStates: Map<string, KnowledgeSemanticEnrichmentStateRecord>;
22
+ }
23
+ export declare function deleteKnowledgeNodeRecord(view: KnowledgeRecordDeleteView, id: string): boolean;
24
+ export declare function deleteKnowledgeSourceRecord(view: KnowledgeRecordDeleteView, id: string): boolean;
25
+ //# sourceMappingURL=store-record-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-record-delete.d.ts","sourceRoot":"","sources":["../../../src/platform/knowledge/store-record-delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EACV,qCAAqC,EACrC,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,6BAA6B,EAC7B,sCAAsC,EACtC,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;IAC7D,QAAQ,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACzD,QAAQ,CAAC,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,qCAAqC,CAAC,CAAC;IACrF,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC;IACrE,QAAQ,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,2BAA2B,EAAE,CAAC,CAAC;IACnE,QAAQ,CAAC,wBAAwB,EAAE,GAAG,CAAC,MAAM,EAAE,sCAAsC,CAAC,CAAC;CACxF;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,yBAAyB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAqC9F;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CA2ChG"}