noteconnection 1.6.8 → 1.8.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 (212) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +258 -64
  3. package/dist/src/agent_workspace.contract.parity.test.js +475 -0
  4. package/dist/src/agent_workspace.frontend.test.js +9989 -0
  5. package/dist/src/agent_workspace.locale.contract.test.js +95 -0
  6. package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
  7. package/dist/src/copy.assets.contract.test.js +130 -0
  8. package/dist/src/core/PathBridge.js +28 -3
  9. package/dist/src/export/WorkspaceExportBundle.js +1130 -0
  10. package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
  11. package/dist/src/export/index.js +18 -0
  12. package/dist/src/export/types.js +2 -0
  13. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  14. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  15. package/dist/src/foundation.release.evidence.contract.test.js +406 -0
  16. package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
  17. package/dist/src/frontend/README.md +258 -64
  18. package/dist/src/frontend/agent_workspace.js +5353 -0
  19. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  20. package/dist/src/frontend/analysis.js +31 -4
  21. package/dist/src/frontend/app.js +3597 -141
  22. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  23. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  24. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  25. package/dist/src/frontend/graph_state.mjs +105 -0
  26. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  27. package/dist/src/frontend/i18n.mjs +186 -0
  28. package/dist/src/frontend/index.html +641 -266
  29. package/dist/src/frontend/layout_gpu.js +12 -4
  30. package/dist/src/frontend/locales/en.json +721 -5
  31. package/dist/src/frontend/locales/zh.json +721 -5
  32. package/dist/src/frontend/main.mjs +60 -0
  33. package/dist/src/frontend/markdown_runtime.js +827 -0
  34. package/dist/src/frontend/notemd.css +49 -0
  35. package/dist/src/frontend/notemd.html +7 -1
  36. package/dist/src/frontend/notemd.js +64 -0
  37. package/dist/src/frontend/path.html +107 -0
  38. package/dist/src/frontend/path_app.js +2189 -150
  39. package/dist/src/frontend/path_layout.mjs +143 -0
  40. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  41. package/dist/src/frontend/path_modules_bridge.js +486 -0
  42. package/dist/src/frontend/path_state.mjs +118 -0
  43. package/dist/src/frontend/path_styles.css +146 -0
  44. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  45. package/dist/src/frontend/reader.js +522 -27
  46. package/dist/src/frontend/runtime_bridge.js +67 -54
  47. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  48. package/dist/src/frontend/settings.js +130 -12
  49. package/dist/src/frontend/simulationWorker.js +241 -6
  50. package/dist/src/frontend/source_manager.js +190 -21
  51. package/dist/src/frontend/styles.css +2853 -72
  52. package/dist/src/frontend/workbench_state.mjs +101 -0
  53. package/dist/src/frontend/workspace_panes.js +10168 -0
  54. package/dist/src/frontend.locale.contract.test.js +62 -0
  55. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  56. package/dist/src/indexing/IndexLifecycle.js +195 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  58. package/dist/src/indexing/SegmentBuilder.js +64 -0
  59. package/dist/src/indexing/UnitBuilder.js +48 -0
  60. package/dist/src/indexing/types.js +2 -0
  61. package/dist/src/knowledge.api.contract.test.js +170 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  68. package/dist/src/learning/answerReleaseReview.js +4319 -0
  69. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  70. package/dist/src/learning/api.js +2 -0
  71. package/dist/src/learning/conversationComposer.js +1480 -0
  72. package/dist/src/learning/conversationComposer.test.js +1817 -0
  73. package/dist/src/learning/domains/ConversationManager.js +53 -0
  74. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  75. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  76. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  77. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  78. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  79. package/dist/src/learning/domains/TutorRouter.js +313 -0
  80. package/dist/src/learning/domains/index.js +33 -0
  81. package/dist/src/learning/domains/types.js +7 -0
  82. package/dist/src/learning/errors.js +29 -0
  83. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  84. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  85. package/dist/src/learning/graphContextAssembler.js +870 -0
  86. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  87. package/dist/src/learning/index.js +28 -0
  88. package/dist/src/learning/queryBackend.js +1898 -0
  89. package/dist/src/learning/queryBackend.test.js +955 -0
  90. package/dist/src/learning/ragContextPack.js +257 -0
  91. package/dist/src/learning/ragContextPack.test.js +160 -0
  92. package/dist/src/learning/ragPublicText.js +38 -0
  93. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  94. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  96. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  97. package/dist/src/learning/requestNormalization.js +198 -0
  98. package/dist/src/learning/runtimeCapability.js +4677 -0
  99. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  100. package/dist/src/learning/store.js +1240 -0
  101. package/dist/src/learning/store.test.js +1126 -0
  102. package/dist/src/learning/tutorAdapter.js +2 -0
  103. package/dist/src/learning/types.js +2 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  105. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  106. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  107. package/dist/src/license.policy.contract.test.js +66 -0
  108. package/dist/src/memory/MemoryGovernance.js +74 -0
  109. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  110. package/dist/src/memory/types.js +2 -0
  111. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  112. package/dist/src/middleware/auth.js +17 -0
  113. package/dist/src/middleware/body-parser.js +45 -0
  114. package/dist/src/middleware/cors.js +44 -0
  115. package/dist/src/middleware/index.js +21 -0
  116. package/dist/src/middleware/request-trace.js +96 -0
  117. package/dist/src/notemd/AppConfigToml.js +6 -4
  118. package/dist/src/notemd/MermaidProcessor.js +400 -50
  119. package/dist/src/notemd/NotemdService.js +498 -13
  120. package/dist/src/notemd/PromptManager.js +15 -0
  121. package/dist/src/notemd/cli/commands.js +357 -0
  122. package/dist/src/notemd/cli/dispatcher.js +225 -0
  123. package/dist/src/notemd/cli/index.js +169 -0
  124. package/dist/src/notemd/cli/parser.js +68 -0
  125. package/dist/src/notemd/cli/types.js +2 -0
  126. package/dist/src/notemd/constants.js +43 -0
  127. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  128. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  129. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  130. package/dist/src/notemd/diagram/intent.js +95 -0
  131. package/dist/src/notemd/diagram/planner.js +71 -0
  132. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  133. package/dist/src/notemd/diagram/types.js +18 -0
  134. package/dist/src/notemd/index.js +26 -0
  135. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  136. package/dist/src/notemd/operations/cliContracts.js +17 -0
  137. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  138. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  139. package/dist/src/notemd/operations/registry.js +991 -0
  140. package/dist/src/notemd/operations/types.js +2 -0
  141. package/dist/src/notemd/providerDiagnostics.js +220 -0
  142. package/dist/src/notemd/providerProfiles.js +42 -0
  143. package/dist/src/notemd/providerTemplates.js +231 -0
  144. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  145. package/dist/src/notemd/search/SearchManager.js +13 -0
  146. package/dist/src/notemd/search/SearchProvider.js +2 -0
  147. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  148. package/dist/src/notemd.agent.manifest.test.js +85 -0
  149. package/dist/src/notemd.api.contract.test.js +14 -1
  150. package/dist/src/notemd.app_config_toml.test.js +2 -1
  151. package/dist/src/notemd.batch.workflow.test.js +117 -0
  152. package/dist/src/notemd.cli.e2e.test.js +136 -0
  153. package/dist/src/notemd.core.test.js +51 -0
  154. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  155. package/dist/src/notemd.providerTemplates.test.js +34 -0
  156. package/dist/src/notemd.server.integration.test.js +143 -35
  157. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  158. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  159. package/dist/src/pathmode.background.contract.test.js +69 -0
  160. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  161. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  162. package/dist/src/platform/ExportProfile.js +58 -0
  163. package/dist/src/platform/PlatformCapabilities.js +45 -0
  164. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  165. package/dist/src/platform/RenderMaterializer.js +33 -0
  166. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  167. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  168. package/dist/src/reader_renderer.js +404 -3
  169. package/dist/src/reader_renderer.test.js +87 -0
  170. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  171. package/dist/src/resources/ResourceRegistry.js +223 -0
  172. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  173. package/dist/src/resources/types.js +2 -0
  174. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  175. package/dist/src/routes/data.js +267 -0
  176. package/dist/src/routes/diagnostics.js +51 -0
  177. package/dist/src/routes/index.js +23 -0
  178. package/dist/src/routes/knowledge.js +968 -0
  179. package/dist/src/routes/markdown.js +287 -0
  180. package/dist/src/routes/notemd.js +565 -0
  181. package/dist/src/routes/registry.contract.test.js +130 -0
  182. package/dist/src/routes/render.js +285 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  184. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  185. package/dist/src/routes/settings.js +6 -0
  186. package/dist/src/routes/staticFiles.js +94 -0
  187. package/dist/src/routes/types.js +2 -0
  188. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  189. package/dist/src/server.js +11090 -1549
  190. package/dist/src/server.migration.test.js +193 -21
  191. package/dist/src/server.port.fallback.contract.test.js +63 -0
  192. package/dist/src/session/SessionStateStore.js +81 -0
  193. package/dist/src/session/SessionStateStore.test.js +58 -0
  194. package/dist/src/session/types.js +2 -0
  195. package/dist/src/settings.runtime.contract.test.js +50 -0
  196. package/dist/src/shared/types.contract.test.js +107 -0
  197. package/dist/src/shared/types.js +22 -0
  198. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  199. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  200. package/dist/src/source_manager.loadflow.test.js +46 -0
  201. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  202. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  203. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  204. package/dist/src/utils/RuntimePaths.js +4 -13
  205. package/dist/src/utils/platform.js +153 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  207. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  208. package/dist/src/workflows/types.js +2 -0
  209. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  210. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  211. package/dist/src/workspace/types.js +2 -0
  212. package/package.json +61 -10
@@ -0,0 +1,1126 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const http = __importStar(require("http"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const store_1 = require("./store");
41
+ function createSnapshot(seed) {
42
+ return {
43
+ schemaVersion: 1,
44
+ savedAt: new Date('2026-01-01T00:00:00.000Z').toISOString(),
45
+ idCounter: 1,
46
+ atoms: [],
47
+ evidenceSpans: [],
48
+ relationEdges: [],
49
+ temporalEdges: [],
50
+ documents: [],
51
+ activeStableKeyToAtomId: [],
52
+ activeAtomIds: [],
53
+ learnerStates: [],
54
+ tutorTraces: [],
55
+ ingestLatencyHistoryMs: [],
56
+ recomputeLatencyHistoryMs: [],
57
+ queryLatencyHistoryMs: [],
58
+ latestIngestSummary: null,
59
+ sessionActionTelemetry: {
60
+ executionCount: 0,
61
+ analyzedAnswerCount: 0,
62
+ inferredMasteryUpdateCount: 0,
63
+ explicitMasteryUpdateCount: 0,
64
+ memoryPersistedCount: 0,
65
+ memoryPromotionAppliedCount: 0,
66
+ memoryPromotionCount: 0,
67
+ verifiedTutorCount: 0,
68
+ pendingVerificationCount: 0,
69
+ outcomeCounts: {
70
+ correct: 0,
71
+ partial: 0,
72
+ incorrect: 0,
73
+ skipped: 0,
74
+ },
75
+ },
76
+ sessionExecutionHistory: [],
77
+ userMemory: {
78
+ [seed]: {
79
+ session: [],
80
+ unit: [],
81
+ long_term: [],
82
+ },
83
+ },
84
+ relationEdgeSignatures: [],
85
+ };
86
+ }
87
+ function createAtom(id, title, content) {
88
+ return {
89
+ id,
90
+ stableKey: `${id}_stable`,
91
+ documentId: 'doc_ops',
92
+ sourcePath: '/ops/doc.md',
93
+ title,
94
+ content: content || `${title} content`,
95
+ representationType: 'text',
96
+ keywords: [title.toLowerCase()],
97
+ evidenceSpanIds: [],
98
+ createdAt: new Date('2026-01-01T00:00:00.000Z').toISOString(),
99
+ updatedAt: new Date('2026-01-01T00:00:00.000Z').toISOString(),
100
+ metadata: {
101
+ sectionPath: ['root'],
102
+ version: 1,
103
+ sourceHash: `${id}_hash`,
104
+ language: 'en',
105
+ },
106
+ };
107
+ }
108
+ function createRelation(id, sourceAtomId, targetAtomId, relationKind = 'sequence') {
109
+ return {
110
+ id,
111
+ sourceAtomId,
112
+ targetAtomId,
113
+ relationKind,
114
+ provenance: 'fact',
115
+ confidence: 0.95,
116
+ evidenceSpanIds: [],
117
+ temporal: {
118
+ validFrom: new Date('2026-01-01T00:00:00.000Z').toISOString(),
119
+ },
120
+ };
121
+ }
122
+ function buildProbeMetadataFromSnapshot(snapshot) {
123
+ if (!snapshot) {
124
+ return null;
125
+ }
126
+ return {
127
+ schemaVersion: snapshot.schemaVersion,
128
+ savedAt: snapshot.savedAt,
129
+ atomCount: snapshot.atoms.length,
130
+ relationEdgeCount: snapshot.relationEdges.length,
131
+ temporalEdgeCount: snapshot.temporalEdges.length,
132
+ documentCount: snapshot.documents.length,
133
+ };
134
+ }
135
+ async function startMockGraphDbSnapshotServer() {
136
+ let persistedSnapshot = null;
137
+ const requests = [];
138
+ const server = http.createServer((req, res) => {
139
+ const method = String(req.method || 'GET').toUpperCase();
140
+ const requestPath = String(req.url || '');
141
+ let rawBody = '';
142
+ req.setEncoding('utf8');
143
+ req.on('data', (chunk) => {
144
+ rawBody += chunk;
145
+ });
146
+ req.on('end', () => {
147
+ let parsedBody = null;
148
+ if (rawBody.trim().length > 0) {
149
+ try {
150
+ parsedBody = JSON.parse(rawBody);
151
+ }
152
+ catch {
153
+ parsedBody = rawBody;
154
+ }
155
+ }
156
+ requests.push({
157
+ method,
158
+ path: requestPath,
159
+ body: parsedBody,
160
+ });
161
+ const nodeMatch = requestPath.match(/^\/graphdb\/ops\/node\/(.+)$/);
162
+ if (nodeMatch && method === 'GET') {
163
+ if (!persistedSnapshot) {
164
+ res.statusCode = 404;
165
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
166
+ res.setHeader('X-Error-Code', 'snapshot_not_found');
167
+ res.end(JSON.stringify({ error: 'snapshot_not_found' }));
168
+ return;
169
+ }
170
+ const rawId = decodeURIComponent(nodeMatch[1] || '').trim();
171
+ const node = persistedSnapshot.atoms.find((atom) => atom.id === rawId || atom.stableKey === rawId) ?? null;
172
+ if (!node) {
173
+ res.statusCode = 404;
174
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
175
+ res.setHeader('X-Error-Code', 'node_not_found');
176
+ res.end(JSON.stringify({ error: 'node_not_found' }));
177
+ return;
178
+ }
179
+ res.statusCode = 200;
180
+ res.setHeader('Content-Type', 'application/json');
181
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
182
+ res.end(JSON.stringify({ success: true, node }));
183
+ return;
184
+ }
185
+ if (requestPath === '/graphdb/ops/nodes' && method === 'POST') {
186
+ if (!persistedSnapshot) {
187
+ res.statusCode = 404;
188
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
189
+ res.setHeader('X-Error-Code', 'snapshot_not_found');
190
+ res.end(JSON.stringify({ error: 'snapshot_not_found' }));
191
+ return;
192
+ }
193
+ const filter = (parsedBody
194
+ && typeof parsedBody === 'object'
195
+ && !Array.isArray(parsedBody)
196
+ && parsedBody.filter
197
+ && typeof parsedBody.filter === 'object') ? parsedBody.filter : {};
198
+ let nodes = persistedSnapshot.atoms;
199
+ if (Array.isArray(filter.nodeIds) && filter.nodeIds.length > 0) {
200
+ const idSet = new Set(filter.nodeIds.map((item) => String(item || '')));
201
+ nodes = nodes.filter((atom) => idSet.has(atom.id) || idSet.has(atom.stableKey ?? ''));
202
+ }
203
+ if (String(filter.stableKey || '').trim()) {
204
+ nodes = nodes.filter((atom) => atom.stableKey === String(filter.stableKey));
205
+ }
206
+ if (Number.isFinite(Number(filter.limit)) && Number(filter.limit) > 0) {
207
+ nodes = nodes.slice(0, Math.max(1, Math.floor(Number(filter.limit))));
208
+ }
209
+ res.statusCode = 200;
210
+ res.setHeader('Content-Type', 'application/json');
211
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
212
+ res.end(JSON.stringify({ success: true, nodes }));
213
+ return;
214
+ }
215
+ if (requestPath === '/graphdb/ops/edges' && method === 'POST') {
216
+ if (!persistedSnapshot) {
217
+ res.statusCode = 404;
218
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
219
+ res.setHeader('X-Error-Code', 'snapshot_not_found');
220
+ res.end(JSON.stringify({ error: 'snapshot_not_found' }));
221
+ return;
222
+ }
223
+ const filter = (parsedBody
224
+ && typeof parsedBody === 'object'
225
+ && !Array.isArray(parsedBody)
226
+ && parsedBody.filter
227
+ && typeof parsedBody.filter === 'object') ? parsedBody.filter : {};
228
+ let edges = persistedSnapshot.relationEdges;
229
+ if (String(filter.fromNodeId || '').trim()) {
230
+ edges = edges.filter((edge) => edge.sourceAtomId === String(filter.fromNodeId));
231
+ }
232
+ if (String(filter.toNodeId || '').trim()) {
233
+ edges = edges.filter((edge) => edge.targetAtomId === String(filter.toNodeId));
234
+ }
235
+ if (String(filter.relationKind || '').trim()) {
236
+ edges = edges.filter((edge) => edge.relationKind === String(filter.relationKind));
237
+ }
238
+ if (Number.isFinite(Number(filter.limit)) && Number(filter.limit) > 0) {
239
+ edges = edges.slice(0, Math.max(1, Math.floor(Number(filter.limit))));
240
+ }
241
+ res.statusCode = 200;
242
+ res.setHeader('Content-Type', 'application/json');
243
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
244
+ res.end(JSON.stringify({ success: true, edges }));
245
+ return;
246
+ }
247
+ if (requestPath === '/graphdb/ops/path' && method === 'POST') {
248
+ if (!persistedSnapshot) {
249
+ res.statusCode = 404;
250
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
251
+ res.setHeader('X-Error-Code', 'snapshot_not_found');
252
+ res.end(JSON.stringify({ error: 'snapshot_not_found' }));
253
+ return;
254
+ }
255
+ const sourceId = String(parsedBody?.sourceId || '').trim();
256
+ const targetId = String(parsedBody?.targetId || '').trim();
257
+ const maxDepth = Number.isFinite(Number(parsedBody?.maxDepth))
258
+ ? Math.max(1, Math.floor(Number(parsedBody?.maxDepth)))
259
+ : 10;
260
+ const adjacency = new Map();
261
+ for (const edge of persistedSnapshot.relationEdges) {
262
+ if (!adjacency.has(edge.sourceAtomId))
263
+ adjacency.set(edge.sourceAtomId, []);
264
+ adjacency.get(edge.sourceAtomId).push({
265
+ to: edge.targetAtomId,
266
+ relation: edge.relationKind,
267
+ });
268
+ }
269
+ const visited = new Set();
270
+ const queue = [
271
+ { nodeId: sourceId, path: [sourceId], edges: [] },
272
+ ];
273
+ visited.add(sourceId);
274
+ let pathResult = { path: [], length: 0, edges: [], found: false };
275
+ while (queue.length > 0) {
276
+ const current = queue.shift();
277
+ if (current.path.length > maxDepth)
278
+ continue;
279
+ if (current.nodeId === targetId) {
280
+ pathResult = {
281
+ path: current.path,
282
+ length: current.path.length - 1,
283
+ edges: current.edges,
284
+ found: true,
285
+ };
286
+ break;
287
+ }
288
+ const neighbors = adjacency.get(current.nodeId) ?? [];
289
+ for (const neighbor of neighbors) {
290
+ if (!visited.has(neighbor.to)) {
291
+ visited.add(neighbor.to);
292
+ queue.push({
293
+ nodeId: neighbor.to,
294
+ path: [...current.path, neighbor.to],
295
+ edges: [...current.edges, { from: current.nodeId, to: neighbor.to, relation: neighbor.relation }],
296
+ });
297
+ }
298
+ }
299
+ }
300
+ res.statusCode = 200;
301
+ res.setHeader('Content-Type', 'application/json');
302
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
303
+ res.end(JSON.stringify({ success: true, pathResult }));
304
+ return;
305
+ }
306
+ if (requestPath !== '/graphdb/snapshot') {
307
+ res.statusCode = 404;
308
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
309
+ res.setHeader('X-Error-Code', 'not_found');
310
+ res.end(JSON.stringify({ error: 'not_found' }));
311
+ return;
312
+ }
313
+ if (method === 'GET') {
314
+ if (!persistedSnapshot) {
315
+ res.statusCode = 404;
316
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
317
+ res.setHeader('X-Error-Code', 'snapshot_not_found');
318
+ res.end(JSON.stringify({ error: 'snapshot_not_found' }));
319
+ return;
320
+ }
321
+ res.statusCode = 200;
322
+ res.setHeader('Content-Type', 'application/json');
323
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
324
+ res.end(JSON.stringify({ success: true, snapshot: persistedSnapshot }));
325
+ return;
326
+ }
327
+ if (method === 'POST') {
328
+ const nextSnapshot = (parsedBody
329
+ && typeof parsedBody === 'object'
330
+ && !Array.isArray(parsedBody)
331
+ && Object.prototype.hasOwnProperty.call(parsedBody, 'snapshot'))
332
+ ? parsedBody.snapshot
333
+ : null;
334
+ if (!nextSnapshot) {
335
+ res.statusCode = 400;
336
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
337
+ res.setHeader('X-Error-Code', 'snapshot_required');
338
+ res.end(JSON.stringify({ error: 'snapshot_required' }));
339
+ return;
340
+ }
341
+ persistedSnapshot = nextSnapshot;
342
+ res.statusCode = 200;
343
+ res.setHeader('Content-Type', 'application/json');
344
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
345
+ res.end(JSON.stringify({ success: true }));
346
+ return;
347
+ }
348
+ res.statusCode = 405;
349
+ res.setHeader('X-Request-Id', `graphdb-mock-${requests.length}`);
350
+ res.setHeader('X-Error-Code', 'method_not_allowed');
351
+ res.end(JSON.stringify({ error: 'method_not_allowed' }));
352
+ });
353
+ });
354
+ const port = await new Promise((resolve, reject) => {
355
+ server.once('error', reject);
356
+ server.listen(0, '127.0.0.1', () => {
357
+ const address = server.address();
358
+ if (!address || typeof address !== 'object') {
359
+ reject(new Error('Failed to allocate mock graphdb port.'));
360
+ return;
361
+ }
362
+ resolve(address.port);
363
+ });
364
+ });
365
+ return {
366
+ endpoint: `http://127.0.0.1:${port}/graphdb`,
367
+ requests,
368
+ close: () => new Promise((resolve, reject) => {
369
+ server.close((error) => {
370
+ if (error) {
371
+ reject(error);
372
+ return;
373
+ }
374
+ resolve();
375
+ });
376
+ }),
377
+ };
378
+ }
379
+ describe('Knowledge graph store backend factory', () => {
380
+ test('memory backend stores and restores snapshots in-process', async () => {
381
+ const store = (0, store_1.createKnowledgeGraphStore)({
382
+ backend: 'memory',
383
+ filePath: '/tmp/unused.v1.json',
384
+ });
385
+ expect(store.getDiagnostics().storeType).toBe('memory');
386
+ expect(await store.loadSnapshot()).toBeNull();
387
+ const snapshot = createSnapshot('memory_user');
388
+ await store.saveSnapshot(snapshot);
389
+ const restored = await store.loadSnapshot();
390
+ expect(restored).toEqual(snapshot);
391
+ expect(store.getDiagnostics().exists).toBe(true);
392
+ expect(store.getDiagnostics().loaded).toBe(true);
393
+ });
394
+ test('unknown backend value is normalized to file backend', async () => {
395
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-test-'));
396
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
397
+ try {
398
+ expect((0, store_1.normalizeKnowledgeGraphStoreBackend)('unknown-backend')).toBe('file');
399
+ const store = (0, store_1.createKnowledgeGraphStore)({
400
+ backend: 'unknown-backend',
401
+ filePath,
402
+ });
403
+ const snapshot = createSnapshot('file_user');
404
+ await store.saveSnapshot(snapshot);
405
+ const restored = await store.loadSnapshot();
406
+ expect(restored).toEqual(snapshot);
407
+ const diagnostics = store.getDiagnostics();
408
+ expect(diagnostics.storeType).toBe('file');
409
+ expect(diagnostics.exists).toBe(true);
410
+ expect(fs.existsSync(filePath)).toBe(true);
411
+ }
412
+ finally {
413
+ fs.rmSync(tempRoot, { recursive: true, force: true });
414
+ }
415
+ });
416
+ test('normalizes graphdb adapter provider aliases', () => {
417
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('file')).toBe('file');
418
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('local-file')).toBe('file');
419
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('external_http')).toBe('http');
420
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('remote-http')).toBe('http');
421
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('service')).toBe('http');
422
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('sqlite')).toBe('sqlite');
423
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('embedded_sqlite')).toBe('sqlite');
424
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('none')).toBe('none');
425
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('disabled')).toBe('none');
426
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('fallback_only')).toBe('none');
427
+ expect((0, store_1.normalizeGraphDbSnapshotAdapterProvider)('unknown')).toBe('file');
428
+ });
429
+ test('normalizes graphdb operation mode aliases', () => {
430
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('snapshot')).toBe('snapshot_only');
431
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('snapshot_only')).toBe('snapshot_only');
432
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('ops')).toBe('ops_preferred');
433
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('ops_preferred')).toBe('ops_preferred');
434
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('operations')).toBe('ops_preferred');
435
+ expect((0, store_1.normalizeGraphDbStoreOperationMode)('unknown')).toBe('snapshot_only');
436
+ });
437
+ test('graphdb adapter factory supports provider-based rollout selection', async () => {
438
+ const disabledAdapter = (0, store_1.createGraphDbSnapshotAdapter)({
439
+ provider: 'none',
440
+ filePath: '/tmp/unused.graphdb.v1.json',
441
+ });
442
+ expect(disabledAdapter).toBeNull();
443
+ const fileAdapter = (0, store_1.createGraphDbSnapshotAdapter)({
444
+ provider: 'file',
445
+ filePath: '/tmp/knowledge_graph_store.graphdb.v1.json',
446
+ fileAdapterId: 'file-adapter-from-factory',
447
+ });
448
+ expect(fileAdapter?.id).toBe('file-adapter-from-factory');
449
+ const mockServer = await startMockGraphDbSnapshotServer();
450
+ try {
451
+ const httpAdapter = (0, store_1.createGraphDbSnapshotAdapter)({
452
+ provider: 'external_http',
453
+ filePath: '/tmp/unused.graphdb.v1.json',
454
+ adapterId: 'http-adapter-from-factory',
455
+ httpEndpoint: mockServer.endpoint,
456
+ httpTimeoutMs: 800,
457
+ httpMaxRetries: 0,
458
+ });
459
+ expect(httpAdapter?.id).toBe('http-adapter-from-factory');
460
+ }
461
+ finally {
462
+ await mockServer.close();
463
+ }
464
+ });
465
+ test('graphdb backend falls back to file store when adapter is not configured', async () => {
466
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-fallback-'));
467
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
468
+ try {
469
+ expect((0, store_1.normalizeKnowledgeGraphStoreBackend)('graphdb')).toBe('graphdb');
470
+ const store = (0, store_1.createKnowledgeGraphStore)({
471
+ backend: 'graphdb',
472
+ filePath,
473
+ });
474
+ const snapshot = createSnapshot('graphdb_fallback_user');
475
+ await store.saveSnapshot(snapshot);
476
+ const restored = await store.loadSnapshot();
477
+ expect(restored).toEqual(snapshot);
478
+ expect(fs.existsSync(filePath)).toBe(true);
479
+ const diagnostics = store.getDiagnostics();
480
+ expect(diagnostics.storeType).toBe('graphdb');
481
+ expect(diagnostics.backendReady).toBe(false);
482
+ expect(diagnostics.usingFallback).toBe(true);
483
+ expect(diagnostics.fallbackStoreType).toBe('file');
484
+ expect(diagnostics.graphDbOperationMode).toBe('snapshot_only');
485
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('unknown');
486
+ expect(diagnostics.graphDbReadPath).toBe('fallback');
487
+ expect(diagnostics.graphDbWritePath).toBe('fallback');
488
+ }
489
+ finally {
490
+ fs.rmSync(tempRoot, { recursive: true, force: true });
491
+ }
492
+ });
493
+ test('graphdb backend uses configured adapter and bypasses file fallback', async () => {
494
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-adapter-'));
495
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
496
+ let persisted = null;
497
+ const adapter = {
498
+ id: 'mock-graphdb',
499
+ async loadSnapshot() {
500
+ return persisted;
501
+ },
502
+ async saveSnapshot(snapshot) {
503
+ persisted = snapshot;
504
+ },
505
+ getDiagnostics() {
506
+ return {
507
+ location: 'graphdb://mock',
508
+ exists: persisted !== null,
509
+ loaded: true,
510
+ };
511
+ },
512
+ };
513
+ try {
514
+ const store = (0, store_1.createKnowledgeGraphStore)({
515
+ backend: 'graphdb',
516
+ filePath,
517
+ graphdb: { adapter },
518
+ });
519
+ const snapshot = createSnapshot('graphdb_adapter_user');
520
+ await store.saveSnapshot(snapshot);
521
+ const restored = await store.loadSnapshot();
522
+ expect(restored).toEqual(snapshot);
523
+ expect(fs.existsSync(filePath)).toBe(false);
524
+ const diagnostics = store.getDiagnostics();
525
+ expect(diagnostics.storeType).toBe('graphdb');
526
+ expect(diagnostics.location).toBe('graphdb://mock');
527
+ expect(diagnostics.backendReady).toBe(true);
528
+ expect(diagnostics.usingFallback).toBe(false);
529
+ expect(diagnostics.adapterId).toBe('mock-graphdb');
530
+ expect(diagnostics.graphDbOperationMode).toBe('snapshot_only');
531
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('snapshot_only');
532
+ expect(diagnostics.graphDbReadPath).toBe('snapshot');
533
+ expect(diagnostics.graphDbWritePath).toBe('snapshot');
534
+ }
535
+ finally {
536
+ fs.rmSync(tempRoot, { recursive: true, force: true });
537
+ }
538
+ });
539
+ test('graphdb backend negotiates ops-capable adapter path when operation mode is ops_preferred', async () => {
540
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-ops-capable-'));
541
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
542
+ let persisted = null;
543
+ let probeCount = 0;
544
+ let loadOpsCount = 0;
545
+ let saveOpsCount = 0;
546
+ const adapter = {
547
+ id: 'mock-graphdb-ops-capable',
548
+ getCapabilities() {
549
+ return {
550
+ mode: 'ops_capable',
551
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata'],
552
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
553
+ };
554
+ },
555
+ async loadSnapshot() {
556
+ throw new Error('snapshot_path_should_not_be_used_when_ops_is_available');
557
+ },
558
+ async saveSnapshot() {
559
+ throw new Error('snapshot_path_should_not_be_used_when_ops_is_available');
560
+ },
561
+ async probeSnapshotMetadata() {
562
+ probeCount += 1;
563
+ if (!persisted) {
564
+ return null;
565
+ }
566
+ return {
567
+ schemaVersion: persisted.schemaVersion,
568
+ savedAt: persisted.savedAt,
569
+ atomCount: persisted.atoms.length,
570
+ relationEdgeCount: persisted.relationEdges.length,
571
+ temporalEdgeCount: persisted.temporalEdges.length,
572
+ documentCount: persisted.documents.length,
573
+ };
574
+ },
575
+ async loadSnapshotByOps() {
576
+ loadOpsCount += 1;
577
+ return persisted;
578
+ },
579
+ async saveSnapshotByOps(snapshot) {
580
+ saveOpsCount += 1;
581
+ persisted = snapshot;
582
+ },
583
+ getDiagnostics() {
584
+ return {
585
+ location: 'graphdb://mock-ops-capable',
586
+ exists: persisted !== null,
587
+ loaded: true,
588
+ capabilityMode: 'ops_capable',
589
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata'],
590
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
591
+ lastReadPath: 'ops',
592
+ lastWritePath: 'ops',
593
+ };
594
+ },
595
+ };
596
+ try {
597
+ const store = (0, store_1.createKnowledgeGraphStore)({
598
+ backend: 'graphdb',
599
+ filePath,
600
+ graphdb: { adapter },
601
+ graphDbOperationMode: 'ops_preferred',
602
+ });
603
+ const snapshot = createSnapshot('graphdb_ops_capable_user');
604
+ await store.saveSnapshot(snapshot);
605
+ const restored = await store.loadSnapshot();
606
+ expect(restored).toEqual(snapshot);
607
+ expect(probeCount).toBeGreaterThanOrEqual(1);
608
+ expect(loadOpsCount).toBeGreaterThanOrEqual(1);
609
+ expect(saveOpsCount).toBeGreaterThanOrEqual(1);
610
+ const diagnostics = store.getDiagnostics();
611
+ expect(diagnostics.storeType).toBe('graphdb');
612
+ expect(diagnostics.graphDbOperationMode).toBe('ops_preferred');
613
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('ops_capable');
614
+ expect(diagnostics.graphDbReadPath).toBe('ops');
615
+ expect(diagnostics.graphDbWritePath).toBe('ops');
616
+ expect(diagnostics.graphDbSupportedReadOperations).toEqual(expect.arrayContaining(['load_snapshot_by_ops', 'probe_snapshot_metadata']));
617
+ expect(diagnostics.graphDbSupportedWriteOperations).toEqual(expect.arrayContaining(['save_snapshot_by_ops']));
618
+ expect(diagnostics.graphDbLastSnapshotMetadata?.schemaVersion).toBe(1);
619
+ }
620
+ finally {
621
+ fs.rmSync(tempRoot, { recursive: true, force: true });
622
+ }
623
+ });
624
+ test('graphdb backend requires loadSnapshotByOps before classifying read path as ops', async () => {
625
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-probe-only-'));
626
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
627
+ let persisted = null;
628
+ let probeCount = 0;
629
+ let loadCount = 0;
630
+ let saveCount = 0;
631
+ const adapter = {
632
+ id: 'mock-graphdb-probe-only',
633
+ getCapabilities() {
634
+ return {
635
+ mode: 'ops_capable',
636
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata'],
637
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
638
+ };
639
+ },
640
+ async loadSnapshot() {
641
+ loadCount += 1;
642
+ return persisted;
643
+ },
644
+ async saveSnapshot(snapshot) {
645
+ saveCount += 1;
646
+ persisted = snapshot;
647
+ },
648
+ async probeSnapshotMetadata() {
649
+ probeCount += 1;
650
+ return buildProbeMetadataFromSnapshot(persisted);
651
+ },
652
+ };
653
+ try {
654
+ const store = (0, store_1.createKnowledgeGraphStore)({
655
+ backend: 'graphdb',
656
+ filePath,
657
+ graphdb: { adapter },
658
+ graphDbFallbackEnabled: false,
659
+ graphDbOperationMode: 'ops_preferred',
660
+ });
661
+ const snapshot = createSnapshot('graphdb_probe_only_user');
662
+ await store.saveSnapshot(snapshot);
663
+ const restored = await store.loadSnapshot();
664
+ expect(restored).toEqual(snapshot);
665
+ expect(probeCount).toBeGreaterThanOrEqual(1);
666
+ expect(loadCount).toBeGreaterThanOrEqual(1);
667
+ expect(saveCount).toBeGreaterThanOrEqual(1);
668
+ const diagnostics = store.getDiagnostics();
669
+ expect(diagnostics.graphDbOperationMode).toBe('ops_preferred');
670
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('ops_capable');
671
+ expect(diagnostics.graphDbReadPath).toBe('snapshot');
672
+ expect(diagnostics.graphDbWritePath).toBe('snapshot');
673
+ expect(diagnostics.usingFallback).toBe(false);
674
+ }
675
+ finally {
676
+ fs.rmSync(tempRoot, { recursive: true, force: true });
677
+ }
678
+ });
679
+ test('graphdb backend downgrades ops_preferred path to snapshot within adapter before fallback store', async () => {
680
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-ops-downgrade-'));
681
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
682
+ let persisted = null;
683
+ let loadOpsCount = 0;
684
+ let saveOpsCount = 0;
685
+ let loadSnapshotCount = 0;
686
+ let saveSnapshotCount = 0;
687
+ const adapter = {
688
+ id: 'mock-graphdb-ops-downgrade',
689
+ getCapabilities() {
690
+ return {
691
+ mode: 'ops_capable',
692
+ supportedReadOperations: ['load_snapshot', 'load_snapshot_by_ops'],
693
+ supportedWriteOperations: ['save_snapshot', 'save_snapshot_by_ops'],
694
+ };
695
+ },
696
+ async loadSnapshotByOps() {
697
+ loadOpsCount += 1;
698
+ throw new Error('ops_read_path_unavailable');
699
+ },
700
+ async saveSnapshotByOps() {
701
+ saveOpsCount += 1;
702
+ throw new Error('ops_write_path_unavailable');
703
+ },
704
+ async loadSnapshot() {
705
+ loadSnapshotCount += 1;
706
+ return persisted;
707
+ },
708
+ async saveSnapshot(snapshot) {
709
+ saveSnapshotCount += 1;
710
+ persisted = snapshot;
711
+ },
712
+ };
713
+ try {
714
+ const store = (0, store_1.createKnowledgeGraphStore)({
715
+ backend: 'graphdb',
716
+ filePath,
717
+ graphdb: { adapter },
718
+ graphDbFallbackEnabled: false,
719
+ graphDbOperationMode: 'ops_preferred',
720
+ });
721
+ const snapshot = createSnapshot('graphdb_ops_downgrade_user');
722
+ await store.saveSnapshot(snapshot);
723
+ const restored = await store.loadSnapshot();
724
+ expect(restored).toEqual(snapshot);
725
+ expect(saveOpsCount).toBeGreaterThanOrEqual(1);
726
+ expect(loadOpsCount).toBeGreaterThanOrEqual(1);
727
+ expect(saveSnapshotCount).toBeGreaterThanOrEqual(1);
728
+ expect(loadSnapshotCount).toBeGreaterThanOrEqual(1);
729
+ const diagnostics = store.getDiagnostics();
730
+ expect(diagnostics.graphDbOperationMode).toBe('ops_preferred');
731
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('ops_capable');
732
+ expect(diagnostics.graphDbReadPath).toBe('snapshot');
733
+ expect(diagnostics.graphDbWritePath).toBe('snapshot');
734
+ expect(diagnostics.usingFallback).toBe(false);
735
+ expect(diagnostics.lastError).toBeUndefined();
736
+ }
737
+ finally {
738
+ fs.rmSync(tempRoot, { recursive: true, force: true });
739
+ }
740
+ });
741
+ test('graphdb backend keeps snapshot path when adapter is snapshot-only even if operation mode requests ops', async () => {
742
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-ops-fallback-'));
743
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
744
+ let persisted = null;
745
+ let loadCount = 0;
746
+ let saveCount = 0;
747
+ const adapter = {
748
+ id: 'mock-graphdb-snapshot-only',
749
+ getCapabilities() {
750
+ return {
751
+ mode: 'snapshot_only',
752
+ supportedReadOperations: ['load_snapshot'],
753
+ supportedWriteOperations: ['save_snapshot'],
754
+ };
755
+ },
756
+ async loadSnapshot() {
757
+ loadCount += 1;
758
+ return persisted;
759
+ },
760
+ async saveSnapshot(snapshot) {
761
+ saveCount += 1;
762
+ persisted = snapshot;
763
+ },
764
+ getDiagnostics() {
765
+ return {
766
+ location: 'graphdb://mock-snapshot-only',
767
+ exists: persisted !== null,
768
+ loaded: true,
769
+ capabilityMode: 'snapshot_only',
770
+ supportedReadOperations: ['load_snapshot'],
771
+ supportedWriteOperations: ['save_snapshot'],
772
+ lastReadPath: 'snapshot',
773
+ lastWritePath: 'snapshot',
774
+ };
775
+ },
776
+ };
777
+ try {
778
+ const store = (0, store_1.createKnowledgeGraphStore)({
779
+ backend: 'graphdb',
780
+ filePath,
781
+ graphdb: { adapter },
782
+ graphDbOperationMode: 'ops_preferred',
783
+ });
784
+ const snapshot = createSnapshot('graphdb_snapshot_only_user');
785
+ await store.saveSnapshot(snapshot);
786
+ const restored = await store.loadSnapshot();
787
+ expect(restored).toEqual(snapshot);
788
+ expect(loadCount).toBeGreaterThanOrEqual(1);
789
+ expect(saveCount).toBeGreaterThanOrEqual(1);
790
+ const diagnostics = store.getDiagnostics();
791
+ expect(diagnostics.graphDbOperationMode).toBe('ops_preferred');
792
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('snapshot_only');
793
+ expect(diagnostics.graphDbReadPath).toBe('snapshot');
794
+ expect(diagnostics.graphDbWritePath).toBe('snapshot');
795
+ expect(diagnostics.graphDbSupportedReadOperations).toEqual(['load_snapshot']);
796
+ expect(diagnostics.graphDbSupportedWriteOperations).toEqual(['save_snapshot']);
797
+ }
798
+ finally {
799
+ fs.rmSync(tempRoot, { recursive: true, force: true });
800
+ }
801
+ });
802
+ test('graphdb backend fails closed when fallback is disabled and adapter is unavailable', async () => {
803
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-graphdb-strict-no-adapter-'));
804
+ const filePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
805
+ try {
806
+ const store = (0, store_1.createKnowledgeGraphStore)({
807
+ backend: 'graphdb',
808
+ filePath,
809
+ graphdb: { adapter: null },
810
+ graphDbFallbackEnabled: false,
811
+ });
812
+ const snapshot = createSnapshot('graphdb_strict_user');
813
+ await expect(store.saveSnapshot(snapshot)).rejects.toThrow('graphdb_adapter_unavailable_no_fallback');
814
+ await expect(store.loadSnapshot()).rejects.toThrow('graphdb_adapter_unavailable_no_fallback');
815
+ const diagnostics = store.getDiagnostics();
816
+ expect(diagnostics.storeType).toBe('graphdb');
817
+ expect(diagnostics.fallbackEnabled).toBe(false);
818
+ expect(diagnostics.usingFallback).toBe(false);
819
+ expect(diagnostics.graphDbOperationMode).toBe('snapshot_only');
820
+ expect(diagnostics.graphDbAdapterCapabilityMode).toBe('unknown');
821
+ expect(String(diagnostics.lastError || '')).toContain('graphdb_adapter_unavailable_no_fallback');
822
+ }
823
+ finally {
824
+ fs.rmSync(tempRoot, { recursive: true, force: true });
825
+ }
826
+ });
827
+ test('built-in file graphdb adapter persists snapshots to dedicated path', async () => {
828
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-file-graphdb-'));
829
+ const graphDbPath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.graphdb.v1.json');
830
+ try {
831
+ const adapter = (0, store_1.createFileGraphDbSnapshotAdapter)({
832
+ provider: 'file',
833
+ filePath: graphDbPath,
834
+ id: 'file-graphdb-test',
835
+ });
836
+ expect(adapter).not.toBeNull();
837
+ const snapshot = createSnapshot('file_graphdb_user');
838
+ await adapter.saveSnapshot(snapshot);
839
+ const restored = await adapter.loadSnapshot();
840
+ expect(restored).toEqual(snapshot);
841
+ expect(fs.existsSync(graphDbPath)).toBe(true);
842
+ const diagnostics = adapter?.getDiagnostics ? adapter.getDiagnostics() : {};
843
+ expect(diagnostics.location).toContain('knowledge_graph_store.graphdb.v1.json');
844
+ expect(diagnostics.exists).toBe(true);
845
+ expect(diagnostics.capabilityMode).toBe('ops_capable');
846
+ expect(diagnostics.supportedReadOperations).toEqual(expect.arrayContaining(['load_snapshot', 'load_snapshot_by_ops', 'probe_snapshot_metadata']));
847
+ expect(diagnostics.supportedWriteOperations).toEqual(expect.arrayContaining(['save_snapshot', 'save_snapshot_by_ops']));
848
+ }
849
+ finally {
850
+ fs.rmSync(tempRoot, { recursive: true, force: true });
851
+ }
852
+ });
853
+ test('built-in http graphdb adapter persists snapshots through remote endpoint', async () => {
854
+ const mockServer = await startMockGraphDbSnapshotServer();
855
+ try {
856
+ const adapter = (0, store_1.createGraphDbSnapshotAdapter)({
857
+ provider: 'http',
858
+ filePath: '/tmp/unused.graphdb.v1.json',
859
+ adapterId: 'http-graphdb-test',
860
+ httpEndpoint: mockServer.endpoint,
861
+ httpTimeoutMs: 1200,
862
+ httpMaxRetries: 0,
863
+ });
864
+ expect(adapter).not.toBeNull();
865
+ expect(adapter?.id).toBe('http-graphdb-test');
866
+ const snapshot = createSnapshot('http_graphdb_user');
867
+ await adapter.saveSnapshot(snapshot);
868
+ const restored = await adapter.loadSnapshot();
869
+ expect(restored).toEqual(snapshot);
870
+ const diagnostics = adapter.getDiagnostics ? adapter.getDiagnostics() : {};
871
+ expect(String(diagnostics.location || '')).toContain('/graphdb/snapshot');
872
+ expect(diagnostics.exists).toBe(true);
873
+ expect(diagnostics.capabilityMode).toBe('ops_capable');
874
+ expect(diagnostics.supportedReadOperations).toEqual(expect.arrayContaining(['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path']));
875
+ expect(diagnostics.supportedWriteOperations).toEqual(['save_snapshot']);
876
+ expect(String(diagnostics.connector?.healthStatus || '')).toBe('ready');
877
+ expect(String(diagnostics.connector?.circuitState || '')).toBe('closed');
878
+ expect(Number(diagnostics.connector?.requestCount || 0)).toBeGreaterThanOrEqual(2);
879
+ expect(Number(diagnostics.connector?.successCount || 0)).toBeGreaterThanOrEqual(2);
880
+ expect(Number(diagnostics.connector?.failureCount || 0)).toBe(0);
881
+ expect(String(diagnostics.connector?.lastRequestId || '')).toContain('graphdb-mock-');
882
+ expect(mockServer.requests.some((item) => item.method === 'POST' && item.path === '/graphdb/snapshot')).toBe(true);
883
+ expect(mockServer.requests.some((item) => item.method === 'GET' && item.path === '/graphdb/snapshot')).toBe(true);
884
+ }
885
+ finally {
886
+ await mockServer.close();
887
+ }
888
+ });
889
+ test('graphdb store proxies node/edge/path operations through http ops adapter when ops mode is preferred', async () => {
890
+ const mockServer = await startMockGraphDbSnapshotServer();
891
+ try {
892
+ const adapter = (0, store_1.createGraphDbSnapshotAdapter)({
893
+ provider: 'http',
894
+ filePath: '/tmp/unused.graphdb.v1.json',
895
+ adapterId: 'http-graphdb-ops-test',
896
+ httpEndpoint: mockServer.endpoint,
897
+ httpTimeoutMs: 1200,
898
+ httpMaxRetries: 0,
899
+ });
900
+ expect(adapter).not.toBeNull();
901
+ const store = (0, store_1.createKnowledgeGraphStore)({
902
+ backend: 'graphdb',
903
+ graphdb: { adapter },
904
+ graphDbFallbackEnabled: false,
905
+ graphDbOperationMode: 'ops_preferred',
906
+ });
907
+ const snapshot = createSnapshot('http_graphdb_ops_user');
908
+ snapshot.atoms = [
909
+ createAtom('atom_a', 'Alpha'),
910
+ createAtom('atom_b', 'Beta'),
911
+ createAtom('atom_c', 'Gamma'),
912
+ ];
913
+ snapshot.relationEdges = [
914
+ createRelation('edge_a_b', 'atom_a', 'atom_b'),
915
+ createRelation('edge_b_c', 'atom_b', 'atom_c'),
916
+ ];
917
+ await store.saveSnapshot(snapshot);
918
+ expect((0, store_1.isOpsAdapter)(store)).toBe(true);
919
+ if (!(0, store_1.isOpsAdapter)(store)) {
920
+ throw new Error('Expected ops adapter interface on graphdb store.');
921
+ }
922
+ const opsStore = store;
923
+ const node = await opsStore.getNode('atom_b');
924
+ expect(node?.id).toBe('atom_b');
925
+ const nodes = await opsStore.queryNodes({ nodeIds: ['atom_c', 'missing'] });
926
+ expect(nodes.map((item) => item.id)).toEqual(['atom_c']);
927
+ const edges = await opsStore.queryEdges({ fromNodeId: 'atom_a' });
928
+ expect(edges.map((item) => item.id)).toEqual(['edge_a_b']);
929
+ const pathResult = await opsStore.findPath('atom_a', 'atom_c', 4);
930
+ expect(pathResult.found).toBe(true);
931
+ expect(pathResult.path).toEqual(['atom_a', 'atom_b', 'atom_c']);
932
+ const diagnostics = store.getDiagnostics();
933
+ expect(diagnostics.graphDbQueryPath).toBe('ops');
934
+ expect(Number(diagnostics.graphDbQueryOpsReadCount || 0)).toBeGreaterThanOrEqual(4);
935
+ expect(String(diagnostics.graphDbAdapterCapabilityMode || '')).toBe('ops_capable');
936
+ expect(diagnostics.graphDbSupportedReadOperations).toEqual(expect.arrayContaining(['get_node', 'query_nodes', 'query_edges', 'find_path']));
937
+ expect(mockServer.requests.some((item) => item.path === '/graphdb/ops/node/atom_b')).toBe(true);
938
+ expect(mockServer.requests.some((item) => item.path === '/graphdb/ops/nodes')).toBe(true);
939
+ expect(mockServer.requests.some((item) => item.path === '/graphdb/ops/edges')).toBe(true);
940
+ expect(mockServer.requests.some((item) => item.path === '/graphdb/ops/path')).toBe(true);
941
+ }
942
+ finally {
943
+ await mockServer.close();
944
+ }
945
+ });
946
+ test('built-in sqlite graphdb adapter persists snapshots and serves ops locally', async () => {
947
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-sqlite-graphdb-'));
948
+ const sqlitePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.graphdb.v1.sqlite');
949
+ let adapter = null;
950
+ try {
951
+ adapter = (0, store_1.createGraphDbSnapshotAdapter)({
952
+ provider: 'sqlite',
953
+ sqlitePath,
954
+ adapterId: 'embedded-sqlite-graphdb-test',
955
+ });
956
+ expect(adapter).not.toBeNull();
957
+ const snapshot = createSnapshot('sqlite_graphdb_user');
958
+ snapshot.atoms = [
959
+ createAtom('atom_a', 'Alpha'),
960
+ createAtom('atom_b', 'Beta'),
961
+ createAtom('atom_c', 'Gamma'),
962
+ ];
963
+ snapshot.relationEdges = [
964
+ createRelation('edge_a_b', 'atom_a', 'atom_b'),
965
+ createRelation('edge_b_c', 'atom_b', 'atom_c'),
966
+ ];
967
+ await adapter.saveSnapshot(snapshot);
968
+ const restored = await adapter.loadSnapshot();
969
+ expect(restored).toEqual(snapshot);
970
+ const node = await adapter.getNodeByOps?.('atom_b');
971
+ expect(node?.id).toBe('atom_b');
972
+ const nodes = await adapter.queryNodesByOps?.({ nodeIds: ['atom_c', 'missing'] });
973
+ expect(nodes?.map((item) => item.id)).toEqual(['atom_c']);
974
+ const edges = await adapter.queryEdgesByOps?.({ fromNodeId: 'atom_a' });
975
+ expect(edges?.map((item) => item.id)).toEqual(['edge_a_b']);
976
+ const pathResult = await adapter.findPathByOps?.('atom_a', 'atom_c', 4);
977
+ expect(pathResult?.found).toBe(true);
978
+ expect(pathResult?.path).toEqual(['atom_a', 'atom_b', 'atom_c']);
979
+ const diagnostics = adapter.getDiagnostics ? adapter.getDiagnostics() : {};
980
+ expect(String(diagnostics.location || '')).toContain('knowledge_graph_store.graphdb.v1.sqlite');
981
+ expect(diagnostics.storeType).toBe('graphdb');
982
+ expect(String(diagnostics.storageEngine || '')).toBe('sqlite');
983
+ expect(diagnostics.capabilityMode).toBe('ops_capable');
984
+ expect(diagnostics.supportedReadOperations).toEqual(expect.arrayContaining(['load_snapshot', 'get_node', 'query_nodes', 'query_edges', 'find_path']));
985
+ expect(diagnostics.supportedWriteOperations).toEqual(['save_snapshot']);
986
+ }
987
+ finally {
988
+ try {
989
+ adapter?.close?.();
990
+ }
991
+ catch {
992
+ }
993
+ fs.rmSync(tempRoot, { recursive: true, force: true });
994
+ }
995
+ });
996
+ test('graphdb sqlite store diagnostics refresh snapshot metadata after snapshot writes', async () => {
997
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'knowledge-store-sqlite-store-diag-'));
998
+ const sqlitePath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.graphdb.v1.sqlite');
999
+ const fallbackPath = path.join(tempRoot, 'runtime_data', 'knowledge_graph_store.v1.json');
1000
+ let adapter = null;
1001
+ try {
1002
+ adapter = (0, store_1.createGraphDbSnapshotAdapter)({
1003
+ provider: 'sqlite',
1004
+ sqlitePath,
1005
+ adapterId: 'embedded-sqlite-graphdb-diagnostics-test',
1006
+ });
1007
+ expect(adapter).not.toBeNull();
1008
+ const store = (0, store_1.createKnowledgeGraphStore)({
1009
+ backend: 'graphdb',
1010
+ filePath: fallbackPath,
1011
+ graphdb: {
1012
+ adapter,
1013
+ },
1014
+ graphDbFallbackEnabled: false,
1015
+ graphDbOperationMode: 'ops_preferred',
1016
+ });
1017
+ const snapshot = createSnapshot('sqlite_store_diag_user');
1018
+ snapshot.atoms = [
1019
+ createAtom('atom_diag_a', 'Diag Alpha'),
1020
+ createAtom('atom_diag_b', 'Diag Beta'),
1021
+ ];
1022
+ snapshot.relationEdges = [
1023
+ createRelation('edge_diag_a_b', 'atom_diag_a', 'atom_diag_b'),
1024
+ ];
1025
+ snapshot.documents = [
1026
+ {
1027
+ documentId: 'doc_diag',
1028
+ sourcePath: '/diag/doc.md',
1029
+ sourceHash: 'doc_diag_hash',
1030
+ version: 1,
1031
+ updatedAt: new Date('2026-01-01T00:00:00.000Z').toISOString(),
1032
+ atomStableKeyToId: [
1033
+ ['atom_diag_a_stable', 'atom_diag_a'],
1034
+ ['atom_diag_b_stable', 'atom_diag_b'],
1035
+ ],
1036
+ atomIds: ['atom_diag_a', 'atom_diag_b'],
1037
+ evidenceSpanIds: [],
1038
+ relationEdgeIds: ['edge_diag_a_b'],
1039
+ temporalEdgeIds: [],
1040
+ },
1041
+ ];
1042
+ await store.saveSnapshot(snapshot);
1043
+ const diagnostics = store.getDiagnostics();
1044
+ expect(diagnostics.storeType).toBe('graphdb');
1045
+ expect(diagnostics.storageEngine).toBe('sqlite');
1046
+ expect(diagnostics.graphDbLastSnapshotMetadata).toEqual(expect.objectContaining({
1047
+ schemaVersion: 1,
1048
+ atomCount: 2,
1049
+ relationEdgeCount: 1,
1050
+ documentCount: 1,
1051
+ }));
1052
+ }
1053
+ finally {
1054
+ try {
1055
+ adapter?.close?.();
1056
+ }
1057
+ catch {
1058
+ }
1059
+ fs.rmSync(tempRoot, { recursive: true, force: true });
1060
+ }
1061
+ });
1062
+ test('built-in http graphdb adapter surfaces circuit-open telemetry after repeated transient failures', async () => {
1063
+ const server = http.createServer((req, res) => {
1064
+ const method = String(req.method || 'GET').toUpperCase();
1065
+ const requestPath = String(req.url || '');
1066
+ if (requestPath !== '/graphdb/snapshot' || method !== 'GET') {
1067
+ res.statusCode = 404;
1068
+ res.setHeader('Content-Type', 'application/json');
1069
+ res.setHeader('X-Request-Id', 'graphdb-flaky-nf');
1070
+ res.setHeader('X-Error-Code', 'not_found');
1071
+ res.end(JSON.stringify({ error: 'not_found' }));
1072
+ return;
1073
+ }
1074
+ res.statusCode = 503;
1075
+ res.setHeader('Content-Type', 'application/json');
1076
+ res.setHeader('X-Request-Id', 'graphdb-flaky-503');
1077
+ res.setHeader('X-Error-Code', 'overloaded');
1078
+ res.end(JSON.stringify({ error: 'overloaded' }));
1079
+ });
1080
+ const port = await new Promise((resolve, reject) => {
1081
+ server.once('error', reject);
1082
+ server.listen(0, '127.0.0.1', () => {
1083
+ const address = server.address();
1084
+ if (!address || typeof address !== 'object') {
1085
+ reject(new Error('Failed to allocate graphdb flaky mock port.'));
1086
+ return;
1087
+ }
1088
+ resolve(address.port);
1089
+ });
1090
+ });
1091
+ try {
1092
+ const adapter = (0, store_1.createGraphDbSnapshotAdapter)({
1093
+ provider: 'http',
1094
+ filePath: '/tmp/unused.graphdb.v1.json',
1095
+ adapterId: 'http-graphdb-circuit',
1096
+ httpEndpoint: `http://127.0.0.1:${port}/graphdb`,
1097
+ httpTimeoutMs: 1200,
1098
+ httpMaxRetries: 0,
1099
+ httpRetryDelayMs: 0,
1100
+ httpCircuitFailureThreshold: 2,
1101
+ httpCircuitCooldownMs: 60000,
1102
+ });
1103
+ expect(adapter).not.toBeNull();
1104
+ await expect(adapter.loadSnapshot()).rejects.toThrow('graphdb_http_request_failed:503');
1105
+ await expect(adapter.loadSnapshot()).rejects.toThrow('graphdb_http_request_failed:503');
1106
+ await expect(adapter.loadSnapshot()).rejects.toThrow('graphdb_http_circuit_open');
1107
+ const diagnostics = adapter.getDiagnostics ? adapter.getDiagnostics() : {};
1108
+ expect(String(diagnostics.connector?.healthStatus || '')).toBe('unavailable');
1109
+ expect(String(diagnostics.connector?.circuitState || '')).toBe('open');
1110
+ expect(Number(diagnostics.connector?.shortCircuitCount || 0)).toBeGreaterThanOrEqual(1);
1111
+ expect(Number(diagnostics.connector?.failureCount || 0)).toBeGreaterThanOrEqual(2);
1112
+ expect(String(diagnostics.connector?.lastErrorCode || '')).toBe('circuit_open');
1113
+ }
1114
+ finally {
1115
+ await new Promise((resolve, reject) => {
1116
+ server.close((error) => {
1117
+ if (error) {
1118
+ reject(error);
1119
+ return;
1120
+ }
1121
+ resolve();
1122
+ });
1123
+ });
1124
+ }
1125
+ });
1126
+ });