@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.
- package/dist/contracts/artifacts/operator-contract.json +1416 -88
- package/dist/platform/companion/companion-chat-manager.d.ts +6 -12
- package/dist/platform/companion/companion-chat-manager.d.ts.map +1 -1
- package/dist/platform/companion/companion-chat-manager.js +8 -2
- package/dist/platform/config/index.d.ts +2 -1
- package/dist/platform/config/index.d.ts.map +1 -1
- package/dist/platform/config/index.js +1 -0
- package/dist/platform/config/manager.d.ts +35 -0
- package/dist/platform/config/manager.d.ts.map +1 -1
- package/dist/platform/config/manager.js +98 -2
- package/dist/platform/config/shared-config-tier.d.ts +51 -0
- package/dist/platform/config/shared-config-tier.d.ts.map +1 -0
- package/dist/platform/config/shared-config-tier.js +127 -0
- package/dist/platform/control-plane/method-catalog-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-runtime.js +95 -1
- package/dist/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-knowledge.js +6 -1
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts +7 -0
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-runtime.js +40 -1
- package/dist/platform/daemon/facade-composition.d.ts.map +1 -1
- package/dist/platform/daemon/facade-composition.js +1 -0
- package/dist/platform/daemon/homeassistant-chat.d.ts +4 -1
- package/dist/platform/daemon/homeassistant-chat.d.ts.map +1 -1
- package/dist/platform/daemon/homeassistant-chat.js +1 -1
- package/dist/platform/daemon/http/homeassistant-routes.d.ts.map +1 -1
- package/dist/platform/daemon/http/homeassistant-routes.js +28 -2
- package/dist/platform/integrations/webhooks.d.ts +15 -0
- package/dist/platform/integrations/webhooks.d.ts.map +1 -1
- package/dist/platform/integrations/webhooks.js +18 -0
- package/dist/platform/knowledge/extraction-policy.d.ts +12 -1
- package/dist/platform/knowledge/extraction-policy.d.ts.map +1 -1
- package/dist/platform/knowledge/extraction-policy.js +19 -1
- package/dist/platform/knowledge/graphql-schema.d.ts +1 -1
- package/dist/platform/knowledge/graphql-schema.d.ts.map +1 -1
- package/dist/platform/knowledge/graphql-schema.js +5 -0
- package/dist/platform/knowledge/home-graph/index.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/index.js +4 -0
- package/dist/platform/knowledge/home-graph/link.d.ts +2 -2
- package/dist/platform/knowledge/home-graph/link.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/link.js +60 -9
- package/dist/platform/knowledge/home-graph/refinement.d.ts +14 -9
- package/dist/platform/knowledge/home-graph/refinement.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/refinement.js +18 -3
- package/dist/platform/knowledge/home-graph/reset.js +21 -4
- package/dist/platform/knowledge/home-graph/service.d.ts +10 -11
- package/dist/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/triage.d.ts +103 -0
- package/dist/platform/knowledge/home-graph/triage.d.ts.map +1 -0
- package/dist/platform/knowledge/home-graph/triage.js +362 -0
- package/dist/platform/knowledge/home-graph/types.d.ts +15 -0
- package/dist/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/platform/knowledge/knowledge-api.d.ts +6 -0
- package/dist/platform/knowledge/knowledge-api.d.ts.map +1 -1
- package/dist/platform/knowledge/knowledge-api.js +5 -0
- package/dist/platform/knowledge/knowledge-history-types.d.ts +56 -0
- package/dist/platform/knowledge/knowledge-history-types.d.ts.map +1 -0
- package/dist/platform/knowledge/knowledge-history-types.js +1 -0
- package/dist/platform/knowledge/packet.d.ts.map +1 -1
- package/dist/platform/knowledge/packet.js +27 -3
- package/dist/platform/knowledge/scope-records.js +4 -2
- package/dist/platform/knowledge/semantic/answer-gaps.d.ts +8 -0
- package/dist/platform/knowledge/semantic/answer-gaps.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/answer-gaps.js +26 -6
- package/dist/platform/knowledge/semantic/enrichment.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/enrichment.js +15 -13
- package/dist/platform/knowledge/semantic/graph-index.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/graph-index.js +3 -1
- package/dist/platform/knowledge/semantic/service.d.ts +7 -0
- package/dist/platform/knowledge/semantic/service.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/service.js +10 -1
- package/dist/platform/knowledge/service-node-admin.d.ts +46 -0
- package/dist/platform/knowledge/service-node-admin.d.ts.map +1 -0
- package/dist/platform/knowledge/service-node-admin.js +72 -0
- package/dist/platform/knowledge/service.d.ts +24 -20
- package/dist/platform/knowledge/service.d.ts.map +1 -1
- package/dist/platform/knowledge/service.js +23 -47
- package/dist/platform/knowledge/store-config.d.ts +34 -0
- package/dist/platform/knowledge/store-config.d.ts.map +1 -1
- package/dist/platform/knowledge/store-config.js +23 -0
- package/dist/platform/knowledge/store-load.d.ts +3 -1
- package/dist/platform/knowledge/store-load.d.ts.map +1 -1
- package/dist/platform/knowledge/store-load.js +3 -1
- package/dist/platform/knowledge/store-node-history.d.ts +49 -0
- package/dist/platform/knowledge/store-node-history.d.ts.map +1 -0
- package/dist/platform/knowledge/store-node-history.js +331 -0
- package/dist/platform/knowledge/store-record-delete.d.ts +25 -0
- package/dist/platform/knowledge/store-record-delete.d.ts.map +1 -0
- package/dist/platform/knowledge/store-record-delete.js +84 -0
- package/dist/platform/knowledge/store-schema.d.ts +3 -1
- package/dist/platform/knowledge/store-schema.d.ts.map +1 -1
- package/dist/platform/knowledge/store-schema.js +66 -0
- package/dist/platform/knowledge/store.d.ts +19 -1
- package/dist/platform/knowledge/store.d.ts.map +1 -1
- package/dist/platform/knowledge/store.js +69 -108
- package/dist/platform/knowledge/types.d.ts +18 -0
- package/dist/platform/knowledge/types.d.ts.map +1 -1
- package/dist/platform/providers/registry.d.ts +18 -0
- package/dist/platform/providers/registry.d.ts.map +1 -1
- package/dist/platform/providers/registry.js +49 -3
- package/dist/platform/providers/runtime-snapshot.d.ts.map +1 -1
- package/dist/platform/providers/runtime-snapshot.js +14 -2
- package/dist/platform/runtime/memory-spine/client.d.ts +137 -12
- package/dist/platform/runtime/memory-spine/client.d.ts.map +1 -1
- package/dist/platform/runtime/memory-spine/client.js +119 -1
- package/dist/platform/runtime/memory-spine/index.d.ts +8 -1
- package/dist/platform/runtime/memory-spine/index.d.ts.map +1 -1
- package/dist/platform/runtime/memory-spine/index.js +7 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts +74 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.js +87 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts +59 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.js +114 -0
- package/dist/platform/runtime/services.d.ts.map +1 -1
- package/dist/platform/runtime/services.js +3 -12
- package/dist/platform/state/memory-recall-contract.d.ts +7 -0
- package/dist/platform/state/memory-recall-contract.d.ts.map +1 -1
- package/dist/platform/state/memory-recall-contract.js +2 -0
- package/dist/platform/utils/notify.d.ts +25 -1
- package/dist/platform/utils/notify.d.ts.map +1 -1
- package/dist/platform/utils/notify.js +29 -1
- package/dist/platform/version.js +1 -1
- package/dist/platform/voice/index.d.ts +2 -0
- package/dist/platform/voice/index.d.ts.map +1 -1
- package/dist/platform/voice/index.js +3 -0
- package/dist/platform/voice/spoken-turn/audio-sink.d.ts +111 -0
- package/dist/platform/voice/spoken-turn/audio-sink.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/audio-sink.js +1 -0
- package/dist/platform/voice/spoken-turn/controller.d.ts +117 -0
- package/dist/platform/voice/spoken-turn/controller.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/controller.js +448 -0
- package/dist/platform/voice/spoken-turn/index.d.ts +16 -0
- package/dist/platform/voice/spoken-turn/index.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/index.js +12 -0
- package/dist/platform/voice/spoken-turn/text-chunker.d.ts +33 -0
- package/dist/platform/voice/spoken-turn/text-chunker.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/text-chunker.js +97 -0
- 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
|
-
|
|
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
|
-
|
|
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":"
|
|
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;
|
|
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"}
|