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.
- package/README.md +257 -1803
- package/dist/src/agent_workspace.contract.parity.test.js +105 -421
- package/dist/src/agent_workspace.frontend.test.js +410 -11
- package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
- package/dist/src/appimage.patchelf.test.js +113 -0
- package/dist/src/appimage.portability.test.js +156 -0
- package/dist/src/backend/FileLoader.js +18 -4
- package/dist/src/backend/FileLoader.test.js +125 -0
- package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
- package/dist/src/backend/GraphBuilder.js +66 -18
- package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
- package/dist/src/backend/GraphMetrics.js +0 -3
- package/dist/src/backend/ResourceIdentity.js +155 -0
- package/dist/src/backend/ResourceIdentity.test.js +113 -0
- package/dist/src/backend/algorithms/CycleDetection.js +0 -1
- package/dist/src/backend/algorithms/HybridEngine.js +0 -3
- package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
- package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
- package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
- package/dist/src/backend/build_dag.js +0 -1
- package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
- package/dist/src/capacitor.runtime.contract.test.js +11 -2
- package/dist/src/core/Graph.js +138 -42
- package/dist/src/core/Graph.test.js +74 -0
- package/dist/src/core/NoteConnection.js +2 -2
- package/dist/src/core/PathBridge.js +265 -2
- package/dist/src/core/ResourceReference.js +17 -0
- package/dist/src/desktop.godot.pack.test.js +142 -0
- package/dist/src/desktop.runtime.graph.test.js +66 -0
- package/dist/src/export/WorkspaceExportBundle.js +44 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +61 -0
- package/dist/src/foundation.release.evidence.contract.test.js +127 -9
- package/dist/src/foundation.sqlite.runtime.contract.test.js +6 -0
- package/dist/src/frontend/README.md +257 -1803
- package/dist/src/frontend/agent_workspace.js +665 -7
- package/dist/src/frontend/index.html +31 -2
- package/dist/src/frontend/knowledge_projection_contract.js +230 -0
- package/dist/src/frontend/knowledge_projection_store.js +214 -0
- package/dist/src/frontend/libs/path_core.js +158 -42
- package/dist/src/frontend/locales/en.json +32 -0
- package/dist/src/frontend/locales/zh.json +32 -0
- package/dist/src/frontend/mobile_budget_runtime.js +33 -0
- package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
- package/dist/src/frontend/mobile_identity_contract.js +221 -0
- package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
- package/dist/src/frontend/path.html +6 -2
- package/dist/src/frontend/simulationWorker.js +1 -1
- package/dist/src/frontend/source_manager.js +110 -4
- package/dist/src/frontend/storage_provider.js +661 -71
- package/dist/src/frontend/styles.css +107 -13
- package/dist/src/frontend/workspace_panes.js +111 -7
- package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
- package/dist/src/graph.answer.operator.contract.test.js +72 -0
- package/dist/src/index.js +6 -2
- package/dist/src/index.knowledge-root.test.js +118 -0
- package/dist/src/indexing/IndexLifecycle.js +22 -0
- package/dist/src/indexing/IndexLifecycle.test.js +24 -0
- package/dist/src/knowledge.api.contract.test.js +4 -2
- package/dist/src/knowledge_projection.contract.test.js +134 -0
- package/dist/src/knowledge_projection_store.test.js +200 -0
- package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
- package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +4687 -3892
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +418 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +243 -6
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +25 -1
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +13 -5
- package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
- package/dist/src/learning/PublicAnswerContract.test.js +85 -0
- package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
- package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
- package/dist/src/learning/agentConversationExecution.js +211 -0
- package/dist/src/learning/agentConversationExecution.test.js +209 -0
- package/dist/src/learning/agentConversationSerialization.js +266 -0
- package/dist/src/learning/agentConversationSerialization.test.js +165 -0
- package/dist/src/learning/agentResponseBudget.js +179 -0
- package/dist/src/learning/agentResponseBudget.test.js +97 -0
- package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
- package/dist/src/learning/answerReleaseReview.js +1191 -250
- package/dist/src/learning/answerReleaseReview.test.js +1293 -7
- package/dist/src/learning/answerTaskPlan.js +473 -0
- package/dist/src/learning/conversationComposer.js +627 -386
- package/dist/src/learning/conversationComposer.test.js +1403 -0
- package/dist/src/learning/evidenceContextAssembler.js +312 -120
- package/dist/src/learning/graphAnswerCoverage.js +37 -0
- package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
- package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
- package/dist/src/learning/graphAnswerFacts.js +94 -0
- package/dist/src/learning/graphAnswerFacts.test.js +39 -0
- package/dist/src/learning/graphAnswerPlan.js +908 -0
- package/dist/src/learning/graphAnswerPlan.test.js +907 -0
- package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
- package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
- package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
- package/dist/src/learning/graphClaimMatcher.js +104 -0
- package/dist/src/learning/graphConditionedContext.js +109 -0
- package/dist/src/learning/graphConditionedContext.test.js +99 -0
- package/dist/src/learning/graphContextAssembler.js +3 -13
- package/dist/src/learning/graphContextAssembler.test.js +2 -2
- package/dist/src/learning/graphExpansionPolicy.js +22 -0
- package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
- package/dist/src/learning/identityMetadata.test.js +26 -0
- package/dist/src/learning/index.js +1 -0
- package/dist/src/learning/mobileAnswerProjection.js +104 -0
- package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
- package/dist/src/learning/queryBackend.js +34 -10
- package/dist/src/learning/queryBackend.test.js +21 -0
- package/dist/src/learning/ragContextPack.js +115 -16
- package/dist/src/learning/ragContextPack.test.js +151 -0
- package/dist/src/learning/ragEvidenceQuality.js +96 -0
- package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
- package/dist/src/learning/ragPublicText.js +67 -4
- package/dist/src/learning/ragPublicText.test.js +57 -0
- package/dist/src/learning/ragSufficiencyJudge.js +11 -1
- package/dist/src/learning/ragSufficiencyProviderJudge.js +16 -26
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +51 -0
- package/dist/src/learning/requestNormalization.js +106 -0
- package/dist/src/learning/requestNormalization.test.js +101 -0
- package/dist/src/learning/store.js +116 -12
- package/dist/src/learning/store.test.js +299 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +22 -6
- package/dist/src/learning/vectorAccelerationAdapter.test.js +30 -0
- package/dist/src/learning/workspaceHydration.js +168 -0
- package/dist/src/learning/workspaceHydration.test.js +30 -0
- package/dist/src/license.policy.contract.test.js +9 -4
- package/dist/src/markdown/MarkdownGateway.js +4 -0
- package/dist/src/markdown/MarkdownGateway.test.js +59 -0
- package/dist/src/middleware/SseResponseWriter.js +122 -0
- package/dist/src/middleware/SseResponseWriter.test.js +54 -0
- package/dist/src/middleware/auth.js +28 -7
- package/dist/src/middleware/auth.test.js +33 -0
- package/dist/src/middleware/index.js +2 -1
- package/dist/src/mobile.android.evidence.contract.test.js +146 -0
- package/dist/src/mobile.android.signing.contract.test.js +66 -0
- package/dist/src/mobile.artifact.contract.test.js +144 -0
- package/dist/src/mobile.budget.contract.test.js +90 -0
- package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
- package/dist/src/mobile.graph.parity.contract.test.js +207 -0
- package/dist/src/mobile.identity.contract.test.js +66 -0
- package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
- package/dist/src/mobile.identity.corpus.test.js +40 -0
- package/dist/src/mobile.native.recovery.contract.test.js +64 -0
- package/dist/src/mobile.pipeline.test.js +11 -1
- package/dist/src/mobile.profile.contract.test.js +225 -0
- package/dist/src/mobile.semantic.parity.test.js +92 -0
- package/dist/src/mobile_exact_analyzer.test.js +113 -0
- package/dist/src/notemd/ContentGenerator.js +4 -9
- package/dist/src/notemd/FileProcessor.js +4 -9
- package/dist/src/notemd/NotemdService.js +12 -17
- package/dist/src/notemd/Translator.js +4 -9
- package/dist/src/notemd/progressReporter.js +9 -0
- package/dist/src/notemd.server.integration.test.js +119 -4
- package/dist/src/notemd.workflow.pipeline.test.js +1 -1
- package/dist/src/pathbridge.handshake.contract.test.js +29 -0
- package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
- package/dist/src/pathcore.bundle.contract.test.js +8 -0
- package/dist/src/platform/ExportProfile.js +28 -0
- package/dist/src/platform/MobileBudget.js +11 -0
- package/dist/src/platform/PlatformCapabilities.js +14 -0
- package/dist/src/platform/PlatformCapabilities.test.js +6 -0
- package/dist/src/query_backend.external_http.integration.test.js +2 -2
- package/dist/src/release.godot.mirror.contract.test.js +28 -4
- package/dist/src/resources/ResourceRegistry.js +40 -0
- package/dist/src/resources/ResourceRegistry.test.js +36 -0
- package/dist/src/routes/index.js +0 -4
- package/dist/src/routes/knowledge.js +46 -33
- package/dist/src/routes/knowledgePayload.js +185 -0
- package/dist/src/routes/knowledgePayload.test.js +53 -0
- package/dist/src/routes/markdown.js +8 -0
- package/dist/src/routes/notemd.js +990 -234
- package/dist/src/routes/registry.contract.test.js +5 -2
- package/dist/src/routes/registry.shadow.contract.test.js +22 -0
- package/dist/src/routes/render.js +14 -51
- package/dist/src/runtime.capabilities.test.js +18 -2
- package/dist/src/server.js +566 -1090
- package/dist/src/server.migration.test.js +417 -15
- package/dist/src/sidecar.build.lock.contract.test.js +201 -0
- package/dist/src/sidecar.freshness.contract.test.js +184 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +6 -3
- package/dist/src/sidecar.supply.readiness.contract.test.js +11 -0
- package/dist/src/simulation.worker.offline.test.js +104 -0
- package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
- package/dist/src/storage.provider.contract.test.js +152 -1
- package/dist/src/tauri.frontend.build.contract.test.js +8 -0
- package/dist/src/tauri.window.evidence.execution.test.js +68 -0
- package/dist/src/utils/RuntimePaths.js +6 -0
- package/dist/src/utils/RuntimePaths.test.js +27 -0
- package/dist/src/windows.installer.build.test.js +109 -0
- package/dist/src/windows.installer.payload.test.js +76 -0
- package/dist/src/windows.installer.runtime.test.js +69 -0
- package/dist/src/workspace/WorkspaceRegistry.js +13 -0
- package/package.json +32 -10
- package/dist/src/routes/data.js +0 -267
- package/dist/src/routes/diagnostics.js +0 -51
|
@@ -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(
|
|
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
|
-
:
|
|
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).
|
|
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
|
|
48
|
-
expect(workflow).toContain('
|
|
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);
|