noteconnection 1.8.0 → 1.9.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 (195) hide show
  1. package/README.md +257 -1803
  2. package/dist/src/agent_workspace.contract.parity.test.js +105 -421
  3. package/dist/src/agent_workspace.frontend.test.js +410 -11
  4. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  5. package/dist/src/appimage.patchelf.test.js +113 -0
  6. package/dist/src/appimage.portability.test.js +156 -0
  7. package/dist/src/backend/FileLoader.js +18 -4
  8. package/dist/src/backend/FileLoader.test.js +125 -0
  9. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  10. package/dist/src/backend/GraphBuilder.js +66 -18
  11. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  12. package/dist/src/backend/GraphMetrics.js +0 -3
  13. package/dist/src/backend/ResourceIdentity.js +155 -0
  14. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  15. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  16. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  17. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  18. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  19. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  20. package/dist/src/backend/build_dag.js +0 -1
  21. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  22. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  23. package/dist/src/core/Graph.js +138 -42
  24. package/dist/src/core/Graph.test.js +74 -0
  25. package/dist/src/core/NoteConnection.js +2 -2
  26. package/dist/src/core/PathBridge.js +265 -2
  27. package/dist/src/core/ResourceReference.js +17 -0
  28. package/dist/src/desktop.godot.pack.test.js +142 -0
  29. package/dist/src/desktop.runtime.graph.test.js +66 -0
  30. package/dist/src/export/WorkspaceExportBundle.js +44 -0
  31. package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
  32. package/dist/src/foundation.release.evidence.contract.test.js +127 -9
  33. package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
  34. package/dist/src/frontend/README.md +257 -1803
  35. package/dist/src/frontend/agent_workspace.js +665 -7
  36. package/dist/src/frontend/index.html +31 -2
  37. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  38. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  39. package/dist/src/frontend/libs/path_core.js +158 -42
  40. package/dist/src/frontend/locales/en.json +32 -0
  41. package/dist/src/frontend/locales/zh.json +32 -0
  42. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  43. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  44. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  45. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  46. package/dist/src/frontend/path.html +6 -2
  47. package/dist/src/frontend/simulationWorker.js +1 -1
  48. package/dist/src/frontend/source_manager.js +110 -4
  49. package/dist/src/frontend/storage_provider.js +661 -71
  50. package/dist/src/frontend/styles.css +107 -13
  51. package/dist/src/frontend/workspace_panes.js +111 -7
  52. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  53. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  54. package/dist/src/index.js +6 -2
  55. package/dist/src/index.knowledge-root.test.js +118 -0
  56. package/dist/src/indexing/IndexLifecycle.js +22 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +24 -0
  58. package/dist/src/knowledge.api.contract.test.js +4 -2
  59. package/dist/src/knowledge_projection.contract.test.js +134 -0
  60. package/dist/src/knowledge_projection_store.test.js +200 -0
  61. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  62. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
  64. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
  68. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  69. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  70. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  71. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  72. package/dist/src/learning/agentConversationExecution.js +211 -0
  73. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  74. package/dist/src/learning/agentConversationSerialization.js +266 -0
  75. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  76. package/dist/src/learning/agentResponseBudget.js +179 -0
  77. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  78. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  79. package/dist/src/learning/answerReleaseReview.js +1191 -250
  80. package/dist/src/learning/answerReleaseReview.test.js +1293 -7
  81. package/dist/src/learning/answerTaskPlan.js +473 -0
  82. package/dist/src/learning/conversationComposer.js +627 -386
  83. package/dist/src/learning/conversationComposer.test.js +1403 -0
  84. package/dist/src/learning/evidenceContextAssembler.js +312 -120
  85. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  86. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  87. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  88. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  89. package/dist/src/learning/graphAnswerFacts.js +94 -0
  90. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  91. package/dist/src/learning/graphAnswerPlan.js +908 -0
  92. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  93. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  94. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  95. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  96. package/dist/src/learning/graphClaimMatcher.js +104 -0
  97. package/dist/src/learning/graphConditionedContext.js +109 -0
  98. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  99. package/dist/src/learning/graphContextAssembler.js +3 -13
  100. package/dist/src/learning/graphContextAssembler.test.js +2 -2
  101. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  102. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  103. package/dist/src/learning/identityMetadata.test.js +26 -0
  104. package/dist/src/learning/index.js +1 -0
  105. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  106. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  107. package/dist/src/learning/queryBackend.js +34 -10
  108. package/dist/src/learning/queryBackend.test.js +21 -0
  109. package/dist/src/learning/ragContextPack.js +115 -16
  110. package/dist/src/learning/ragContextPack.test.js +151 -0
  111. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  112. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  113. package/dist/src/learning/ragPublicText.js +67 -4
  114. package/dist/src/learning/ragPublicText.test.js +57 -0
  115. package/dist/src/learning/ragSufficiencyJudge.js +11 -1
  116. package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
  117. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
  118. package/dist/src/learning/requestNormalization.js +106 -0
  119. package/dist/src/learning/requestNormalization.test.js +101 -0
  120. package/dist/src/learning/store.js +116 -12
  121. package/dist/src/learning/store.test.js +299 -0
  122. package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
  123. package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
  124. package/dist/src/learning/workspaceHydration.js +168 -0
  125. package/dist/src/learning/workspaceHydration.test.js +30 -0
  126. package/dist/src/license.policy.contract.test.js +9 -4
  127. package/dist/src/markdown/MarkdownGateway.js +4 -0
  128. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  129. package/dist/src/middleware/SseResponseWriter.js +122 -0
  130. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  131. package/dist/src/middleware/auth.js +28 -7
  132. package/dist/src/middleware/auth.test.js +33 -0
  133. package/dist/src/middleware/index.js +2 -1
  134. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  135. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  136. package/dist/src/mobile.artifact.contract.test.js +144 -0
  137. package/dist/src/mobile.budget.contract.test.js +90 -0
  138. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  139. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  140. package/dist/src/mobile.identity.contract.test.js +66 -0
  141. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  142. package/dist/src/mobile.identity.corpus.test.js +40 -0
  143. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  144. package/dist/src/mobile.pipeline.test.js +11 -1
  145. package/dist/src/mobile.profile.contract.test.js +225 -0
  146. package/dist/src/mobile.semantic.parity.test.js +92 -0
  147. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  148. package/dist/src/notemd/ContentGenerator.js +4 -9
  149. package/dist/src/notemd/FileProcessor.js +4 -9
  150. package/dist/src/notemd/NotemdService.js +12 -17
  151. package/dist/src/notemd/Translator.js +4 -9
  152. package/dist/src/notemd/progressReporter.js +9 -0
  153. package/dist/src/notemd.server.integration.test.js +119 -4
  154. package/dist/src/notemd.workflow.pipeline.test.js +1 -1
  155. package/dist/src/pathbridge.handshake.contract.test.js +29 -0
  156. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  157. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  158. package/dist/src/platform/ExportProfile.js +28 -0
  159. package/dist/src/platform/MobileBudget.js +11 -0
  160. package/dist/src/platform/PlatformCapabilities.js +14 -0
  161. package/dist/src/platform/PlatformCapabilities.test.js +6 -0
  162. package/dist/src/query_backend.external_http.integration.test.js +2 -2
  163. package/dist/src/release.godot.mirror.contract.test.js +28 -4
  164. package/dist/src/resources/ResourceRegistry.js +40 -0
  165. package/dist/src/resources/ResourceRegistry.test.js +36 -0
  166. package/dist/src/routes/index.js +0 -4
  167. package/dist/src/routes/knowledge.js +46 -33
  168. package/dist/src/routes/knowledgePayload.js +185 -0
  169. package/dist/src/routes/knowledgePayload.test.js +53 -0
  170. package/dist/src/routes/markdown.js +8 -0
  171. package/dist/src/routes/notemd.js +990 -234
  172. package/dist/src/routes/registry.contract.test.js +5 -2
  173. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  174. package/dist/src/routes/render.js +14 -51
  175. package/dist/src/runtime.capabilities.test.js +18 -2
  176. package/dist/src/server.js +566 -1090
  177. package/dist/src/server.migration.test.js +417 -15
  178. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  179. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  180. package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
  181. package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
  182. package/dist/src/simulation.worker.offline.test.js +104 -0
  183. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  184. package/dist/src/storage.provider.contract.test.js +152 -1
  185. package/dist/src/tauri.frontend.build.contract.test.js +8 -0
  186. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  187. package/dist/src/utils/RuntimePaths.js +6 -0
  188. package/dist/src/utils/RuntimePaths.test.js +27 -0
  189. package/dist/src/windows.installer.build.test.js +109 -0
  190. package/dist/src/windows.installer.payload.test.js +76 -0
  191. package/dist/src/windows.installer.runtime.test.js +69 -0
  192. package/dist/src/workspace/WorkspaceRegistry.js +13 -0
  193. package/package.json +32 -10
  194. package/dist/src/routes/data.js +0 -267
  195. package/dist/src/routes/diagnostics.js +0 -51
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNoopProgressReporter = createNoopProgressReporter;
4
+ function createNoopProgressReporter() {
5
+ return {
6
+ report: () => undefined,
7
+ isCancelled: () => false,
8
+ };
9
+ }
@@ -39,7 +39,7 @@ const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  class TempDir {
41
41
  constructor(prefix) {
42
- this.path = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), `${prefix}-`));
42
+ this.path = fs.realpathSync.native(fs.mkdtempSync(path.join(os.tmpdir(), `${prefix}-`)));
43
43
  }
44
44
  mkdir(relativePath) {
45
45
  const target = path.join(this.path, relativePath);
@@ -94,7 +94,7 @@ function getFreePort() {
94
94
  });
95
95
  });
96
96
  }
97
- function requestJson(port, method, requestPath, body) {
97
+ function requestJson(port, method, requestPath, body, headers = {}) {
98
98
  return new Promise((resolve, reject) => {
99
99
  const payload = typeof body === 'undefined' ? null : Buffer.from(JSON.stringify(body), 'utf8');
100
100
  const req = http.request({
@@ -106,8 +106,9 @@ function requestJson(port, method, requestPath, body) {
106
106
  ? {
107
107
  'Content-Type': 'application/json',
108
108
  'Content-Length': String(payload.length),
109
+ ...headers,
109
110
  }
110
- : undefined,
111
+ : headers,
111
112
  }, (res) => {
112
113
  let text = '';
113
114
  res.setEncoding('utf8');
@@ -213,7 +214,7 @@ describe('NoteMD server integration', () => {
213
214
  originalArgv = [...process.argv];
214
215
  serverModule = loadFreshServerModule();
215
216
  server = await serverModule.startServer({ port });
216
- });
217
+ }, 20000);
217
218
  async function shutdownServerInstance(instance) {
218
219
  if (!instance) {
219
220
  return;
@@ -452,6 +453,93 @@ describe('NoteMD server integration', () => {
452
453
  expect(workspaceResponse.body.workspace.filePath).toBe(kbFilePath);
453
454
  expect(workspaceResponse.body.workspace.folderPath).toBe(path.dirname(kbFilePath));
454
455
  });
456
+ test('NoteMD protocol rejects wrong content type, oversized input and missing cancellation targets', async () => {
457
+ const wrongType = await requestJson(port, 'POST', '/api/notemd/settings', {}, { 'Content-Type': 'text/plain' });
458
+ expect(wrongType.status).toBe(415);
459
+ const oversized = await requestJson(port, 'POST', '/api/notemd/settings', { pad: 'x'.repeat(600 * 1024) });
460
+ expect(oversized.status).toBe(413);
461
+ const cancel = await requestJson(port, 'POST', '/api/notemd/cancel', { operationId: 'missing-operation' });
462
+ expect(cancel.status).toBe(404);
463
+ });
464
+ test('NoteMD protocol rejects files outside the configured workspace without mutation', async () => {
465
+ const outside = temp.file('outside/rejected.md', '# Outside\n$\nunsafe\n$');
466
+ const response = await requestJson(port, 'POST', '/api/notemd/fix-formulas', { filePath: outside, inPlace: true });
467
+ expect(response.status).toBe(403);
468
+ expect(fs.readFileSync(outside, 'utf8')).toBe('# Outside\n$\nunsafe\n$');
469
+ });
470
+ test('NoteMD protocol emits one operation and one completion while processing a real file', async () => {
471
+ const response = await requestJson(port, 'POST', '/api/notemd/process-file', {
472
+ filePath: kbFilePath, operationId: 'notemd-stream-contract',
473
+ }, { accept: 'text/event-stream' });
474
+ expect(response.status).toBe(200);
475
+ const frames = String(response.body).split('\n\n').filter(Boolean);
476
+ const operations = frames.filter(frame => frame.startsWith('event: operation\n'));
477
+ const completed = frames.filter(frame => frame.startsWith('event: done\n'));
478
+ expect(operations).toHaveLength(1);
479
+ expect(completed).toHaveLength(1);
480
+ expect(JSON.parse(completed[0].slice(completed[0].indexOf('data: ') + 6))).toEqual(expect.objectContaining({ success: true, operationId: 'notemd-stream-contract' }));
481
+ });
482
+ test('NoteMD registry owns aliases and unknown routes without an inline fallback', async () => {
483
+ const alias = await requestJson(port, 'PUT', '/api/notemd/fix-formulas', { filePath: kbFilePath, inPlace: true });
484
+ expect(alias.status).toBe(200);
485
+ expect((await requestJson(port, 'POST', '/api/notemd/unknown', {})).status).toBe(404);
486
+ const diagnostics = await requestJson(port, 'GET', '/api/runtime-diagnostics');
487
+ expect(diagnostics.body.routeMigration.registryHits).toBeGreaterThan(0);
488
+ expect(diagnostics.body.routeMigration.inlineFallbacksByFamily.notemd || 0).toBe(0);
489
+ });
490
+ test('NoteMD operation IDs cannot replace an active operation and cancellation reaches its owner', async () => {
491
+ const { NotemdService } = require('./notemd/NotemdService');
492
+ let entered;
493
+ let release;
494
+ const started = new Promise(resolve => { entered = resolve; });
495
+ const held = new Promise(resolve => { release = resolve; });
496
+ let signal;
497
+ const processFile = jest.spyOn(NotemdService.prototype, 'processFile')
498
+ .mockImplementationOnce(async (...args) => {
499
+ signal = args[3];
500
+ entered();
501
+ await held;
502
+ signal.throwIfAborted();
503
+ return { outputPath: kbFilePath };
504
+ }).mockResolvedValueOnce({ outputPath: kbFilePath });
505
+ const payload = { filePath: kbFilePath, operationId: 'notemd-admission-contract' };
506
+ const first = requestJson(port, 'POST', '/api/notemd/process-file', payload);
507
+ try {
508
+ await started;
509
+ const duplicate = await requestJson(port, 'POST', '/api/notemd/process-file', payload);
510
+ expect(duplicate.status).toBe(409);
511
+ expect(processFile).toHaveBeenCalledTimes(1);
512
+ expect((await requestJson(port, 'POST', '/api/notemd/cancel', { operationId: payload.operationId })).status).toBe(200);
513
+ expect(signal?.aborted).toBe(true);
514
+ release();
515
+ expect((await first).status).toBe(499);
516
+ }
517
+ finally {
518
+ release();
519
+ await first;
520
+ processFile.mockRestore();
521
+ }
522
+ });
523
+ test('NoteMD authentication is enforced before route dispatch', async () => {
524
+ const restoreToken = setEnv('NOTE_CONNECTION_AUTH_TOKEN', 'notemd-fixture-token');
525
+ let authenticated;
526
+ try {
527
+ const authPort = await getFreePort();
528
+ authenticated = await loadFreshServerModule().startServer({ port: authPort });
529
+ expect((await requestJson(authPort, 'GET', '/api/notemd/settings')).status).toBe(401);
530
+ expect((await requestJson(authPort, 'GET', '/api/notemd/settings', undefined, { 'X-NoteConnection-Token': 'notemd-fixture-token' })).status).toBe(200);
531
+ }
532
+ finally {
533
+ await shutdownServerInstance(authenticated);
534
+ restoreToken();
535
+ }
536
+ });
537
+ test.each(['/batch-fix-formulas', '/batch-generate-content', '/workflow', '/batch-workflow', '/extract-original-text'])('NoteMD registry validates the %s filesystem boundary', async (endpoint) => {
538
+ const outside = temp.file('outside/extended.md', '# Outside');
539
+ const response = await requestJson(port, 'POST', '/api/notemd' + endpoint, { filePath: outside, folderPath: path.dirname(outside), skipGenerate: true });
540
+ expect(response.status).toBe(403);
541
+ expect(fs.readFileSync(outside, 'utf8')).toBe('# Outside');
542
+ });
455
543
  test('extract-concepts and duplicate endpoints return structured results', async () => {
456
544
  const conceptsResponse = await requestJson(port, 'POST', '/api/notemd/extract-concepts', {
457
545
  filePath: kbFilePath,
@@ -494,6 +582,9 @@ describe('NoteMD server integration', () => {
494
582
  }));
495
583
  expect(readinessResponse.body.readiness.baseline).toEqual(expect.objectContaining({
496
584
  storeType: 'sqlite',
585
+ storageRequestedProvider: 'sqlite',
586
+ storageResolvedProvider: 'sqlite',
587
+ storageSupportsSqlite: true,
497
588
  graphBackendStatus: 'independent',
498
589
  graphBackendSignalKind: 'embedded_graphdb',
499
590
  graphBackendIndependent: true,
@@ -520,6 +611,28 @@ describe('NoteMD server integration', () => {
520
611
  reason: 'embedding_ann',
521
612
  }));
522
613
  });
614
+ test('turn-cache alert trend restart durability', async () => {
615
+ await requestJson(port, 'POST', '/api/knowledge/conversation', { userId: 'durability-user', message: 'Graph theory', persistMemory: false });
616
+ const diagnostics = await requestJson(port, 'GET', '/api/knowledge/conversation/turn-cache/diagnostics');
617
+ expect(diagnostics.body.result.counters.executionSuccessCount).toBeGreaterThan(0);
618
+ const trend = await requestJson(port, 'GET', '/api/knowledge/conversation/turn-cache/diagnostics/trend');
619
+ expect(trend.body.result.records.length).toBeGreaterThan(0);
620
+ const storagePath = trend.body.result.storage.filePath;
621
+ const deadline = Date.now() + 3000;
622
+ while (!fs.existsSync(storagePath) && Date.now() < deadline)
623
+ await new Promise(resolve => setTimeout(resolve, 10));
624
+ expect(fs.existsSync(storagePath)).toBe(true);
625
+ const persisted = JSON.parse(fs.readFileSync(storagePath, 'utf8'));
626
+ expect(persisted.records.length).toBeGreaterThan(0);
627
+ await shutdownServerInstance(server);
628
+ port = await getFreePort();
629
+ serverModule = loadFreshServerModule();
630
+ server = await serverModule.startServer({ port });
631
+ const restored = await requestJson(port, 'GET', '/api/knowledge/conversation/turn-cache/diagnostics/trend/export');
632
+ expect(restored.status).toBe(200);
633
+ expect(restored.body.result.storage.lastLoadedRecordCount).toBeGreaterThan(0);
634
+ expect(restored.body.result.records).toEqual(expect.arrayContaining([expect.objectContaining(persisted.records[0])]));
635
+ });
523
636
  test('embedded sqlite graph runtime survives server restart and preserves query/store diagnostics', async () => {
524
637
  const sqliteStorePath = path.join(runtimeDataDir, 'knowledge_graph_store.graphdb.v1.sqlite');
525
638
  const ingestResponse = await requestJson(port, 'POST', '/api/knowledge/ingest', {
@@ -546,6 +659,8 @@ describe('NoteMD server integration', () => {
546
659
  expect(diagnosticsResponse.body.store).toEqual(expect.objectContaining({
547
660
  storeType: 'graphdb',
548
661
  storageEngine: 'sqlite',
662
+ requestedProvider: 'sqlite',
663
+ resolvedProvider: 'sqlite',
549
664
  backendReady: true,
550
665
  usingFallback: false,
551
666
  }));
@@ -104,7 +104,7 @@ describe('Notemd Workflow Pipeline', () => {
104
104
  // Summary should have valid numbers
105
105
  expect(typeof result.summary.conceptsExtracted).toBe('number');
106
106
  expect(typeof result.summary.totalElapsedMs).toBe('number');
107
- expect(result.summary.totalElapsedMs).toBeGreaterThan(0);
107
+ expect(result.summary.totalElapsedMs).toBeGreaterThanOrEqual(0);
108
108
  });
109
109
  test('runWorkflow with addWikiLinks injects wiki-links and writes _wikified file', async () => {
110
110
  const src = temp.file('notes/test.md', '# Machine Learning\n\nMachine learning uses neural networks and deep learning.');
@@ -91,6 +91,35 @@ describe('path bridge handshake and transport verification contracts', () => {
91
91
  });
92
92
  expect(validIdentify.ok).toBe(true);
93
93
  expect(validIdentify.envelope?.type).toBe('identify');
94
+ const validQuery = (0, PathBridge_1.parseBridgeInboundEnvelope)({
95
+ type: 'query',
96
+ version: PathBridge_1.PATH_BRIDGE_PROTOCOL_VERSION,
97
+ payload: {
98
+ requestId: 'query-1',
99
+ workspaceId: 'mobile-workspace',
100
+ sourceUri: 'note://workspace/v1/algebra/index.md',
101
+ },
102
+ });
103
+ expect(validQuery.ok).toBe(true);
104
+ const invalidQuery = (0, PathBridge_1.parseBridgeInboundEnvelope)({
105
+ type: 'query',
106
+ payload: { requestId: '' },
107
+ });
108
+ expect(invalidQuery.ok).toBe(false);
109
+ expect(invalidQuery.reason).toContain('requestId');
110
+ const invalidCapabilities = (0, PathBridge_1.parseBridgeInboundEnvelope)({
111
+ type: 'capabilities',
112
+ payload: { protocolVersion: PathBridge_1.PATH_BRIDGE_PROTOCOL_VERSION, capabilities: [''] },
113
+ });
114
+ expect(invalidCapabilities.ok).toBe(false);
115
+ expect(invalidCapabilities.reason).toContain('non-empty strings');
116
+ expect(PathBridge_1.PATH_BRIDGE_CAPABILITIES).toEqual(expect.arrayContaining([
117
+ 'knowledge.analyze',
118
+ 'knowledge.query',
119
+ 'knowledge.readEvidence',
120
+ 'knowledge.exportBundle',
121
+ 'request.cancel',
122
+ ]));
94
123
  const invalidSwitchCenter = (0, PathBridge_1.parseBridgeInboundEnvelope)({
95
124
  type: 'switchCenter',
96
125
  payload: {
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ws_1 = require("ws");
4
+ const PathBridge_1 = require("./core/PathBridge");
5
+ function waitForMessage(socket, predicate) {
6
+ return new Promise((resolve, reject) => {
7
+ const timer = setTimeout(() => {
8
+ socket.off('message', onMessage);
9
+ reject(new Error('Timed out waiting for PathBridge message.'));
10
+ }, 3000);
11
+ const onMessage = (raw) => {
12
+ let payload;
13
+ try {
14
+ payload = JSON.parse(raw.toString('utf8'));
15
+ }
16
+ catch {
17
+ return;
18
+ }
19
+ if (!predicate(payload)) {
20
+ return;
21
+ }
22
+ clearTimeout(timer);
23
+ socket.off('message', onMessage);
24
+ resolve(payload);
25
+ };
26
+ socket.on('message', onMessage);
27
+ });
28
+ }
29
+ function connectClient(port) {
30
+ return new Promise((resolve, reject) => {
31
+ const socket = new ws_1.WebSocket(`ws://127.0.0.1:${port}`);
32
+ socket.once('open', () => resolve(socket));
33
+ socket.once('error', reject);
34
+ });
35
+ }
36
+ async function closeClient(socket) {
37
+ if (!socket || socket.readyState === ws_1.WebSocket.CLOSED) {
38
+ return;
39
+ }
40
+ await new Promise((resolve) => {
41
+ socket.once('close', () => resolve());
42
+ socket.close();
43
+ });
44
+ }
45
+ describe('PathBridge host adapter contract', () => {
46
+ test('executes knowledge requests in the host and returns correlated results', async () => {
47
+ const operations = [];
48
+ const bridge = new PathBridge_1.PathBridge({
49
+ port: 0,
50
+ hostAdapter: {
51
+ execute: async (operation) => {
52
+ operations.push(operation);
53
+ return { nodeCount: 2, execution: 'host' };
54
+ },
55
+ },
56
+ });
57
+ let client = null;
58
+ try {
59
+ await bridge.waitUntilReady();
60
+ client = await connectClient(bridge.getPort());
61
+ const capabilitiesPromise = waitForMessage(client, (message) => message.type === 'capabilities');
62
+ client.send(JSON.stringify({ type: 'identify', payload: { client: 'test-client' } }));
63
+ await capabilitiesPromise;
64
+ client.send(JSON.stringify({
65
+ type: 'query',
66
+ correlationId: 'corr-1',
67
+ payload: { requestId: 'query-1', workspaceId: 'mobile-workspace' },
68
+ }));
69
+ const result = await waitForMessage(client, (message) => message.type === 'operationResult');
70
+ expect(result.payload).toEqual(expect.objectContaining({
71
+ type: 'query',
72
+ requestId: 'query-1',
73
+ correlationId: 'corr-1',
74
+ ok: true,
75
+ result: { nodeCount: 2, execution: 'host' },
76
+ }));
77
+ expect(operations[0]).toEqual(expect.objectContaining({
78
+ type: 'query',
79
+ requestId: 'query-1',
80
+ correlationId: 'corr-1',
81
+ clientTag: 'test-client',
82
+ }));
83
+ expect(operations[0].signal).toBeInstanceOf(AbortSignal);
84
+ }
85
+ finally {
86
+ await closeClient(client);
87
+ bridge.close();
88
+ await new Promise((resolve) => setTimeout(resolve, 25));
89
+ }
90
+ });
91
+ test('propagates cancellation to the host adapter and does not leak pending work', async () => {
92
+ let cancelledRequestId = '';
93
+ let operationSignal = null;
94
+ const bridge = new PathBridge_1.PathBridge({
95
+ port: 0,
96
+ hostAdapter: {
97
+ execute: (operation) => {
98
+ operationSignal = operation.signal;
99
+ return new Promise((_resolve, reject) => {
100
+ operation.signal.addEventListener('abort', () => reject(new Error('aborted')));
101
+ });
102
+ },
103
+ cancel: (requestId) => {
104
+ cancelledRequestId = requestId;
105
+ },
106
+ },
107
+ });
108
+ let client = null;
109
+ try {
110
+ await bridge.waitUntilReady();
111
+ client = await connectClient(bridge.getPort());
112
+ const capabilitiesPromise = waitForMessage(client, (message) => message.type === 'capabilities');
113
+ client.send(JSON.stringify({ type: 'identify', payload: { client: 'cancel-client' } }));
114
+ await capabilitiesPromise;
115
+ client.send(JSON.stringify({ type: 'query', payload: { requestId: 'query-cancel' } }));
116
+ await new Promise((resolve) => setTimeout(resolve, 20));
117
+ client.send(JSON.stringify({ type: 'cancel', payload: { requestId: 'query-cancel' } }));
118
+ const result = await waitForMessage(client, (message) => message.type === 'operationResult' && message.payload.type === 'cancel');
119
+ expect(result.payload).toEqual(expect.objectContaining({
120
+ requestId: 'query-cancel',
121
+ cancelled: true,
122
+ ok: false,
123
+ }));
124
+ expect(cancelledRequestId).toBe('query-cancel');
125
+ expect(operationSignal?.aborted).toBe(true);
126
+ }
127
+ finally {
128
+ await closeClient(client);
129
+ bridge.close();
130
+ await new Promise((resolve) => setTimeout(resolve, 25));
131
+ }
132
+ });
133
+ });
@@ -80,6 +80,14 @@ describe('path core bundle contract', () => {
80
80
  ['A', 'B', 'C', 'D'].forEach((id) => {
81
81
  graph.addNode({ id, label: id, inDegree: 0, outDegree: 0 });
82
82
  });
83
+ graph.addNode({
84
+ id: 'Alias',
85
+ label: 'Alias',
86
+ inDegree: 0,
87
+ outDegree: 0,
88
+ identityAliases: ['folder/alias.md'],
89
+ });
90
+ expect(graph.hasNode('folder\\alias.md')).toBe(true);
83
91
  graph.addEdge('A', 'B');
84
92
  graph.addEdge('A', 'C');
85
93
  graph.addEdge('B', 'D');
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOBILE_SLIM_MAX_RESIDENT_BYTES = exports.MOBILE_SLIM_ASSET_BUDGET_BYTES = void 0;
3
4
  exports.getExportProfile = getExportProfile;
4
5
  exports.listExportProfiles = listExportProfiles;
6
+ const MobileBudget_1 = require("./MobileBudget");
7
+ exports.MOBILE_SLIM_ASSET_BUDGET_BYTES = MobileBudget_1.MOBILE_LOW_ARTIFACT_BUDGET_BYTES;
8
+ exports.MOBILE_SLIM_MAX_RESIDENT_BYTES = MobileBudget_1.MOBILE_LOW_MAX_RESIDENT_BYTES;
5
9
  const EXPORT_PROFILES = {
6
10
  'desktop-full': {
7
11
  id: 'desktop-full',
@@ -12,6 +16,12 @@ const EXPORT_PROFILES = {
12
16
  supportsConversationStreaming: true,
13
17
  supportsSvgRenderArtifacts: true,
14
18
  supportsPngRenderArtifacts: true,
19
+ supportsLocalIngest: true,
20
+ supportsLocalExactQuery: true,
21
+ supportsRemoteInference: true,
22
+ requiresRemoteInference: false,
23
+ assetBudgetBytes: null,
24
+ maxResidentBytes: null,
15
25
  preferredMermaidRenderer: 'auto',
16
26
  },
17
27
  'desktop-reader': {
@@ -23,6 +33,12 @@ const EXPORT_PROFILES = {
23
33
  supportsConversationStreaming: true,
24
34
  supportsSvgRenderArtifacts: true,
25
35
  supportsPngRenderArtifacts: true,
36
+ supportsLocalIngest: false,
37
+ supportsLocalExactQuery: true,
38
+ supportsRemoteInference: true,
39
+ requiresRemoteInference: false,
40
+ assetBudgetBytes: null,
41
+ maxResidentBytes: null,
26
42
  preferredMermaidRenderer: 'auto',
27
43
  },
28
44
  'godot-path-mode': {
@@ -34,6 +50,12 @@ const EXPORT_PROFILES = {
34
50
  supportsConversationStreaming: false,
35
51
  supportsSvgRenderArtifacts: false,
36
52
  supportsPngRenderArtifacts: true,
53
+ supportsLocalIngest: false,
54
+ supportsLocalExactQuery: false,
55
+ supportsRemoteInference: false,
56
+ requiresRemoteInference: false,
57
+ assetBudgetBytes: null,
58
+ maxResidentBytes: null,
37
59
  preferredMermaidRenderer: 'auto',
38
60
  },
39
61
  'mobile-slim': {
@@ -45,6 +67,12 @@ const EXPORT_PROFILES = {
45
67
  supportsConversationStreaming: false,
46
68
  supportsSvgRenderArtifacts: false,
47
69
  supportsPngRenderArtifacts: true,
70
+ supportsLocalIngest: true,
71
+ supportsLocalExactQuery: true,
72
+ supportsRemoteInference: true,
73
+ requiresRemoteInference: false,
74
+ assetBudgetBytes: exports.MOBILE_SLIM_ASSET_BUDGET_BYTES,
75
+ maxResidentBytes: exports.MOBILE_SLIM_MAX_RESIDENT_BYTES,
48
76
  preferredMermaidRenderer: 'local',
49
77
  },
50
78
  };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOBILE_LOW_MAX_DEVICE_RAM_BYTES = exports.MOBILE_LOW_MAX_RESIDENT_BYTES = exports.MOBILE_LOW_ARTIFACT_BUDGET_BYTES = exports.MOBILE_BUDGET_SCHEMA_VERSION = void 0;
4
+ /**
5
+ * TypeScript projection of config/mobile-budget.v1.json for server-side export metadata.
6
+ * Keep this small and compare it with the checked-in contract in mobile budget tests.
7
+ */
8
+ exports.MOBILE_BUDGET_SCHEMA_VERSION = 1;
9
+ exports.MOBILE_LOW_ARTIFACT_BUDGET_BYTES = 25 * 1024 * 1024;
10
+ exports.MOBILE_LOW_MAX_RESIDENT_BYTES = 256 * 1024 * 1024;
11
+ exports.MOBILE_LOW_MAX_DEVICE_RAM_BYTES = 4 * 1024 * 1024 * 1024;
@@ -27,6 +27,8 @@ function resolvePlatformCapabilities(input = {}) {
27
27
  deterministicBundles: true,
28
28
  bundlePackagingMode: profile.supportsSidecar ? 'full' : 'slim',
29
29
  requiresIndexedReadiness: true,
30
+ assetBudgetBytes: profile.assetBudgetBytes,
31
+ maxResidentBytes: profile.maxResidentBytes,
30
32
  },
31
33
  render: {
32
34
  supportsSvgArtifacts: profile.supportsSvgRenderArtifacts,
@@ -37,6 +39,18 @@ function resolvePlatformCapabilities(input = {}) {
37
39
  retrieval: {
38
40
  supportsSidecar: profile.supportsSidecar,
39
41
  supportsBackgroundIndexing: profile.supportsBackgroundIndexing,
42
+ supportsLocalIngest: profile.supportsLocalIngest,
43
+ supportsLocalExactQuery: profile.supportsLocalExactQuery,
44
+ supportsRemoteInference: profile.supportsRemoteInference,
45
+ requiresRemoteInference: profile.requiresRemoteInference,
46
+ },
47
+ storage: {
48
+ preferredProvider: profile.platformTarget === 'mobile' ? 'projection' : 'sqlite',
49
+ supportedProviders: profile.platformTarget === 'mobile'
50
+ ? ['projection']
51
+ : ['sqlite', 'file', 'remote'],
52
+ supportsSqlite: profile.platformTarget !== 'mobile',
53
+ supportsProjection: profile.platformTarget === 'mobile',
40
54
  },
41
55
  conversation: {
42
56
  supportsStreaming: profile.supportsConversationStreaming,
@@ -10,6 +10,9 @@ describe('PlatformCapabilities', () => {
10
10
  expect(capabilities.render.supportsSvgArtifacts).toBe(false);
11
11
  expect(capabilities.render.preferredArtifact).toBe('png');
12
12
  expect(capabilities.conversation.supportsStreaming).toBe(false);
13
+ expect(capabilities.storage.preferredProvider).toBe('sqlite');
14
+ expect(capabilities.storage.supportsSqlite).toBe(true);
15
+ expect(capabilities.storage.supportsProjection).toBe(false);
13
16
  });
14
17
  test('resolves mobile slim profile as sidecar-free and PNG-only', () => {
15
18
  const capabilities = (0, PlatformCapabilities_1.resolvePlatformCapabilities)({ exportProfileId: 'mobile-slim' });
@@ -18,6 +21,9 @@ describe('PlatformCapabilities', () => {
18
21
  expect(capabilities.export.requiresIndexedReadiness).toBe(true);
19
22
  expect(capabilities.retrieval.supportsSidecar).toBe(false);
20
23
  expect(capabilities.render.supportsSvgArtifacts).toBe(false);
24
+ expect(capabilities.storage.preferredProvider).toBe('projection');
25
+ expect(capabilities.storage.supportsSqlite).toBe(false);
26
+ expect(capabilities.storage.supportsProjection).toBe(true);
21
27
  });
22
28
  test('platform target override selects the compatible profile family', () => {
23
29
  const capabilities = (0, PlatformCapabilities_1.resolvePlatformCapabilities)({
@@ -346,7 +346,7 @@ describe('external_http vector acceleration server integration', () => {
346
346
  originalArgv = [...process.argv];
347
347
  const serverModule = loadFreshServerModule();
348
348
  server = await serverModule.startServer({ port });
349
- });
349
+ }, 30000);
350
350
  afterAll(async () => {
351
351
  await shutdownServerInstance(server);
352
352
  await shutdownServerInstance(annService);
@@ -356,7 +356,7 @@ describe('external_http vector acceleration server integration', () => {
356
356
  jest.dontMock('./core/PathBridge');
357
357
  jest.dontMock('./reader_renderer');
358
358
  temp.cleanup();
359
- });
359
+ }, 30000);
360
360
  test('local_vector runtime can sync a remote external_http ANN index and use it for live queries', async () => {
361
361
  const documents = Array.from({ length: 140 }, (_value, index) => ({
362
362
  documentId: `doc_external_ann_${index}`,
@@ -44,10 +44,8 @@ describe('release workflow godot mirror contract', () => {
44
44
  expect(workflow).toContain('description: "Allow desktop release jobs to fall back to the upstream Godot release when the project mirror is unavailable."');
45
45
  expect(workflow).toContain('default: true');
46
46
  expect(workflow).toContain('type: boolean');
47
- expect(workflow).toContain('GODOT_MIRROR_TAG: "godot-mirror-v4.3-stable"');
48
- expect(workflow).toContain('GODOT_WINDOWS_ARCHIVE_SHA256: "8f2c75b734bd956027ae3ca92c41f78b5d5a255dacc0f20e4e3c523c545ad410"');
49
- expect(workflow).toContain('GODOT_LINUX_ARCHIVE_SHA256: "7de56444b130b10af84d19c7e0cf63cf9e9937ee4ba94364c3b7dd114253ca21"');
50
- expect(workflow).toContain('GODOT_MACOS_ARCHIVE_SHA256: "d17940b913b3f3bf54c941eeb09042099d93865c6e2638e09e20f7c649aa474a"');
47
+ expect(workflow.match(/node scripts\/write-godot-desktop-ci-env\.js/g)).toHaveLength(2);
48
+ expect(workflow).toContain('${GODOT_LINUX_ARCHIVE_NAME%.zip}');
51
49
  expect(workflow).toContain("GODOT_ALLOW_UPSTREAM_FALLBACK: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.allow_godot_upstream_fallback != 'false' }}");
52
50
  expect(workflow).toContain('ensure-godot-mirror-assets:');
53
51
  expect(workflow).toContain('gh release view "$GODOT_MIRROR_TAG" --repo "$GITHUB_REPOSITORY"');
@@ -70,4 +68,30 @@ describe('release workflow godot mirror contract', () => {
70
68
  expect(workflow).toContain('ACTUAL_SHA256="$(sha256sum build/godot/godot-macos.zip | awk \'{print $1}\')"');
71
69
  expect(workflow).toContain('needs: [ensure-release, ensure-godot-mirror-assets]');
72
70
  });
71
+ test('exports checksummed engine archives matching the declared Godot project version', () => {
72
+ const runtime = require('../config/godot-desktop-runtime.json');
73
+ const { writeGodotDesktopCiEnvironment } = require('../scripts/write-godot-desktop-ci-env');
74
+ const project = fs.readFileSync(path.join(repoRoot, 'path_mode/project.godot'), 'utf8');
75
+ expect(project).toContain(`PackedStringArray("${runtime.version}"`);
76
+ const directory = fs.mkdtempSync(path.join(fs.realpathSync(require('os').tmpdir()), 'godot-ci-env-'));
77
+ const destination = path.join(directory, 'env');
78
+ try {
79
+ writeGodotDesktopCiEnvironment(destination);
80
+ const environment = Object.fromEntries(fs.readFileSync(destination, 'utf8').trim().split('\n').map(line => {
81
+ const separator = line.indexOf('=');
82
+ return [line.slice(0, separator), line.slice(separator + 1)];
83
+ }));
84
+ expect(environment.GODOT_MIRROR_TAG).toBe(runtime.mirrorTag);
85
+ for (const platform of ['windows', 'linux', 'macos']) {
86
+ const archive = runtime.archives[platform];
87
+ expect(archive.name).toContain(`Godot_v${runtime.version}-stable`);
88
+ expect(archive.sha256).toMatch(/^[a-f0-9]{64}$/);
89
+ expect(environment[`GODOT_${platform.toUpperCase()}_ARCHIVE_NAME`]).toBe(archive.name);
90
+ expect(environment[`GODOT_${platform.toUpperCase()}_ARCHIVE_SHA256`]).toBe(archive.sha256);
91
+ }
92
+ }
93
+ finally {
94
+ fs.rmSync(directory, { recursive: true, force: true });
95
+ }
96
+ });
73
97
  });
@@ -121,6 +121,46 @@ class ResourceRegistry {
121
121
  this.projectionIdsByStableKey.set(input.stableKey, projectionId);
122
122
  return { resource, projection };
123
123
  }
124
+ /**
125
+ * Keeps the resource/projection owners aligned with a document move without
126
+ * allocating a new resource or changing the stable document projection key.
127
+ * The document store owns the transition journal; this adapter only mirrors
128
+ * the committed identity metadata for export and diagnostics consumers.
129
+ */
130
+ updateKnowledgeDocumentIdentity(input) {
131
+ const projection = this.getProjectionByDocumentId(input.documentId);
132
+ if (!projection) {
133
+ return false;
134
+ }
135
+ const resource = this.resources.get(projection.resourceId);
136
+ const identityMetadata = {
137
+ ...(input.sourceUri !== undefined ? { sourceUri: input.sourceUri } : {}),
138
+ ...(input.revision !== undefined ? { revision: input.revision } : {}),
139
+ ...(input.identityAliases !== undefined ? { identityAliases: [...input.identityAliases] } : {}),
140
+ };
141
+ this.projections.set(projection.projectionId, {
142
+ ...projection,
143
+ sourcePath: input.sourcePath,
144
+ metadata: {
145
+ ...projection.metadata,
146
+ ...identityMetadata,
147
+ },
148
+ updatedAt: input.updatedAt,
149
+ });
150
+ if (resource) {
151
+ this.resources.set(resource.resourceId, {
152
+ ...resource,
153
+ sourcePath: input.sourcePath,
154
+ title: input.title || resource.title,
155
+ metadata: {
156
+ ...resource.metadata,
157
+ ...identityMetadata,
158
+ },
159
+ updatedAt: input.updatedAt,
160
+ });
161
+ }
162
+ return true;
163
+ }
124
164
  markDocumentProjectionDeleted(documentId, deletedAt) {
125
165
  const stableKey = `knowledge_document:${String(documentId || '').trim()}`;
126
166
  const projectionId = this.projectionIdsByStableKey.get(stableKey);