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,955 @@
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 os = __importStar(require("os"));
38
+ const path = __importStar(require("path"));
39
+ const queryBackend_1 = require("./queryBackend");
40
+ function makeAtom(id, title, content, keywords, language = 'en') {
41
+ return {
42
+ id,
43
+ stableKey: id,
44
+ documentId: `${id}_doc`,
45
+ sourcePath: `Knowledge_Base/${id}.md`,
46
+ title,
47
+ content,
48
+ representationType: 'text',
49
+ keywords,
50
+ evidenceSpanIds: [],
51
+ createdAt: '2026-01-01T00:00:00.000Z',
52
+ updatedAt: '2026-01-01T00:00:00.000Z',
53
+ metadata: {
54
+ sectionPath: [title],
55
+ version: 1,
56
+ sourceHash: 'hash',
57
+ language,
58
+ },
59
+ };
60
+ }
61
+ describe('query backend factory', () => {
62
+ test('normalizes query backend aliases', () => {
63
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('keyword')).toBe('keyword_only');
64
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('keyword-only')).toBe('keyword_only');
65
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('vector')).toBe('local_vector');
66
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('semantic-vector')).toBe('local_vector');
67
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('local_hybrid')).toBe('local_hybrid');
68
+ expect((0, queryBackend_1.normalizeGraphQueryBackendType)('unknown')).toBe('local_hybrid');
69
+ });
70
+ test('normalizes local vector acceleration failure mode aliases', () => {
71
+ expect((0, queryBackend_1.normalizeLocalVectorAccelerationFailureMode)('fail_open')).toBe('fail_open');
72
+ expect((0, queryBackend_1.normalizeLocalVectorAccelerationFailureMode)('fail-closed')).toBe('fail_closed');
73
+ expect((0, queryBackend_1.normalizeLocalVectorAccelerationFailureMode)('strict')).toBe('fail_closed');
74
+ expect((0, queryBackend_1.normalizeLocalVectorAccelerationFailureMode)('unknown')).toBe('fail_open');
75
+ });
76
+ test('keyword-only backend returns keyword/temporal retrieval modes', async () => {
77
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'keyword_only' });
78
+ const atoms = [
79
+ makeAtom('atom_a', 'Alpha Topic', 'alpha retrieval baseline', ['alpha', 'retrieval']),
80
+ makeAtom('atom_b', 'Beta Topic', 'beta unrelated', ['beta']),
81
+ ];
82
+ const activeEdges = [];
83
+ const result = await backend.query({
84
+ request: {
85
+ query: 'alpha retrieval',
86
+ topK: 3,
87
+ },
88
+ query: 'alpha retrieval',
89
+ queryTokens: ['alpha', 'retrieval'],
90
+ asOf: '2026-01-01T00:00:00.000Z',
91
+ topK: 3,
92
+ atoms,
93
+ activeEdges,
94
+ });
95
+ expect(backend.id).toBe('keyword-only-v1');
96
+ expect(result.candidates.length).toBeGreaterThan(0);
97
+ expect(result.trace?.retrievalModes).toContain('keyword');
98
+ expect(result.trace?.retrievalModes).toContain('temporal_filter');
99
+ expect(result.trace?.retrievalModes).not.toContain('semantic_similarity');
100
+ });
101
+ test('local hybrid backend includes semantic similarity mode', async () => {
102
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
103
+ const atoms = [
104
+ makeAtom('atom_a', 'Alpha Topic', 'alpha retrieval baseline', ['alpha', 'retrieval']),
105
+ makeAtom('atom_b', 'Beta Topic', 'beta unrelated', ['beta']),
106
+ ];
107
+ const activeEdges = [
108
+ {
109
+ id: 'edge_ab',
110
+ sourceAtomId: 'atom_a',
111
+ targetAtomId: 'atom_b',
112
+ relationKind: 'reference',
113
+ provenance: 'fact',
114
+ confidence: 0.8,
115
+ evidenceSpanIds: [],
116
+ temporal: {
117
+ validFrom: '2026-01-01T00:00:00.000Z',
118
+ },
119
+ },
120
+ ];
121
+ const result = await backend.query({
122
+ request: {
123
+ query: 'alpha retrieval',
124
+ topK: 3,
125
+ },
126
+ query: 'alpha retrieval',
127
+ queryTokens: ['alpha', 'retrieval'],
128
+ asOf: '2026-01-01T00:00:00.000Z',
129
+ topK: 3,
130
+ atoms,
131
+ activeEdges,
132
+ });
133
+ expect(backend.id).toBe('local-hybrid-v1');
134
+ expect(result.candidates.length).toBeGreaterThan(0);
135
+ expect(result.trace?.retrievalModes).toContain('semantic_similarity');
136
+ expect(result.trace?.modeWeights?.semantic).toBeGreaterThan(0);
137
+ });
138
+ test('local vector backend includes vector similarity mode', async () => {
139
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
140
+ const atoms = [
141
+ makeAtom('atom_a', 'Retrieval Vector Topic', 'vector embeddings and semantic index', ['vector', 'semantic']),
142
+ makeAtom('atom_b', 'Keyword Topic', 'keyword baseline only', ['keyword']),
143
+ ];
144
+ const activeEdges = [
145
+ {
146
+ id: 'edge_ab',
147
+ sourceAtomId: 'atom_a',
148
+ targetAtomId: 'atom_b',
149
+ relationKind: 'reference',
150
+ provenance: 'fact',
151
+ confidence: 0.8,
152
+ evidenceSpanIds: [],
153
+ temporal: {
154
+ validFrom: '2026-01-01T00:00:00.000Z',
155
+ },
156
+ },
157
+ ];
158
+ const result = await backend.query({
159
+ request: {
160
+ query: 'semantic vector retrieval',
161
+ topK: 3,
162
+ },
163
+ query: 'semantic vector retrieval',
164
+ queryTokens: ['semantic', 'vector', 'retrieval'],
165
+ asOf: '2026-01-01T00:00:00.000Z',
166
+ topK: 3,
167
+ atoms,
168
+ activeEdges,
169
+ });
170
+ expect(backend.id).toBe('local-vector-v1');
171
+ expect(result.candidates.length).toBeGreaterThan(0);
172
+ expect(result.trace?.retrievalModes).toContain('vector_similarity');
173
+ expect(result.trace?.modeWeights?.vector).toBeGreaterThan(0);
174
+ expect(result.trace?.vectorAcceleration?.representationVersion).toBe('local-vector-representation-v2');
175
+ expect(result.trace?.vectorAcceleration?.embeddingModelId).toBe('local-semantic-tfidf-unicode-v2');
176
+ expect(result.trace?.vectorAcceleration?.representationStatus).toBe('aligned');
177
+ expect(typeof result.trace?.vectorAcceleration?.embeddingDimension).toBe('number');
178
+ });
179
+ test('local vector backend reuses a global index while enforcing scoped candidate scoring', async () => {
180
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
181
+ backend: 'local_vector',
182
+ localVectorAnnPrefilterEnabled: false,
183
+ });
184
+ const atoms = [
185
+ makeAtom('waterglass_atom', 'Water Glass', 'A water glass is a physical container for drinking water.', ['water', 'glass']),
186
+ makeAtom('physics_atom', 'Physics', 'Thermodynamics and mass-energy conservation.', ['physics']),
187
+ makeAtom('finance_atom', 'Finance', 'Equity risk premium and cash-flow discounting.', ['finance']),
188
+ ];
189
+ const activeEdges = [];
190
+ await backend.query({
191
+ request: {
192
+ query: 'warmup',
193
+ topK: 3,
194
+ },
195
+ query: 'warmup',
196
+ queryTokens: ['warmup'],
197
+ asOf: '2026-01-01T00:00:00.000Z',
198
+ topK: 3,
199
+ atoms,
200
+ activeEdges,
201
+ });
202
+ expect(backend.getDiagnostics).toBeDefined();
203
+ const globalSignature = backend.getDiagnostics().vectorIndex?.signature;
204
+ const scopedResult = await backend.query({
205
+ request: {
206
+ query: 'water glass',
207
+ topK: 3,
208
+ },
209
+ query: 'water glass',
210
+ queryTokens: ['water', 'glass'],
211
+ asOf: '2026-01-01T00:00:00.000Z',
212
+ topK: 3,
213
+ atoms: [atoms[0]],
214
+ indexAtoms: atoms,
215
+ activeEdges,
216
+ });
217
+ expect(backend.getDiagnostics().vectorIndex?.signature).toBe(globalSignature);
218
+ expect(scopedResult.candidates.map((candidate) => candidate.atomId)).toEqual(['waterglass_atom']);
219
+ });
220
+ test('local vector backend falls back to scoped scoring when global ann candidates miss the scope', async () => {
221
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
222
+ backend: 'local_vector',
223
+ localVectorAccelerationAdapter: {
224
+ id: 'scope-miss-adapter-v1',
225
+ selectCandidates: () => ({
226
+ used: true,
227
+ candidateAtomIds: ['finance_atom'],
228
+ mode: 'token_prefilter',
229
+ }),
230
+ },
231
+ });
232
+ const atoms = [
233
+ makeAtom('waterglass_atom', 'Water Glass', 'A water glass is a physical container for drinking water.', ['water', 'glass']),
234
+ makeAtom('finance_atom', 'Finance', 'Equity risk premium and cash-flow discounting.', ['finance']),
235
+ ];
236
+ const scopedResult = await backend.query({
237
+ request: {
238
+ query: 'water glass',
239
+ topK: 3,
240
+ },
241
+ query: 'water glass',
242
+ queryTokens: ['water', 'glass'],
243
+ asOf: '2026-01-01T00:00:00.000Z',
244
+ topK: 3,
245
+ atoms: [atoms[0]],
246
+ indexAtoms: atoms,
247
+ activeEdges: [],
248
+ });
249
+ expect(scopedResult.candidates.map((candidate) => candidate.atomId)).toEqual(['waterglass_atom']);
250
+ expect(scopedResult.trace?.retrievalModes).not.toContain('ann_prefilter');
251
+ expect(scopedResult.trace?.vectorAcceleration?.mode).toBe('full_scan');
252
+ });
253
+ test('local vector backend matches Chinese semantic overlap without ASCII-only token loss', async () => {
254
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
255
+ const atoms = [
256
+ makeAtom('atom_cn', '吸收系数', '材料的吸收系数决定光在介质中的衰减与穿透深度。', ['吸收', '系数', '光学'], 'zh'),
257
+ makeAtom('atom_other', '散射机制', '散射描述光线偏离原始传播方向的过程。', ['散射'], 'zh'),
258
+ ];
259
+ const result = await backend.query({
260
+ request: {
261
+ query: '吸收系数 光学',
262
+ topK: 3,
263
+ },
264
+ query: '吸收系数 光学',
265
+ queryTokens: ['吸收系数', '光学'],
266
+ asOf: '2026-01-01T00:00:00.000Z',
267
+ topK: 3,
268
+ atoms,
269
+ activeEdges: [],
270
+ });
271
+ expect(result.candidates.length).toBeGreaterThan(0);
272
+ expect(result.candidates[0]?.atomId).toBe('atom_cn');
273
+ expect(result.trace?.retrievalModes).toContain('vector_similarity');
274
+ });
275
+ test('local hybrid backend consumes planner query variants for compact mixed-language aliases', async () => {
276
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
277
+ const atoms = [
278
+ makeAtom('atom_water_glass', 'Water Glass', 'A water glass is a transparent container filled with water.', ['water', 'glass', 'waterglass']),
279
+ makeAtom('atom_other', 'Liquidity', 'Liquidity analysis explains cash conversion and working capital timing.', ['liquidity']),
280
+ ];
281
+ const result = await backend.query({
282
+ request: {
283
+ query: '什么是waterglass?',
284
+ topK: 3,
285
+ },
286
+ query: '什么是waterglass?',
287
+ queryTokens: ['什么是waterglass'],
288
+ queryVariants: ['什么是waterglass', 'waterglass', 'water glass', '水玻璃'],
289
+ asOf: '2026-01-01T00:00:00.000Z',
290
+ topK: 3,
291
+ atoms,
292
+ activeEdges: [],
293
+ });
294
+ expect(result.candidates.length).toBeGreaterThan(0);
295
+ expect(result.candidates[0]?.atomId).toBe('atom_water_glass');
296
+ });
297
+ test('local vector backend consumes planner query variants for compact mixed-language aliases', async () => {
298
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
299
+ const atoms = [
300
+ makeAtom('atom_water_glass', 'Water Glass', 'A water glass is a transparent container filled with water.', ['water', 'glass', 'waterglass']),
301
+ makeAtom('atom_other', 'Liquidity', 'Liquidity analysis explains cash conversion and working capital timing.', ['liquidity']),
302
+ ];
303
+ const result = await backend.query({
304
+ request: {
305
+ query: '什么是waterglass?',
306
+ topK: 3,
307
+ },
308
+ query: '什么是waterglass?',
309
+ queryTokens: ['什么是waterglass'],
310
+ queryVariants: ['什么是waterglass', 'waterglass', 'water glass', '水玻璃'],
311
+ asOf: '2026-01-01T00:00:00.000Z',
312
+ topK: 3,
313
+ atoms,
314
+ activeEdges: [],
315
+ });
316
+ expect(result.candidates.length).toBeGreaterThan(0);
317
+ expect(result.candidates[0]?.atomId).toBe('atom_water_glass');
318
+ });
319
+ test('local vector backend persists and reuses vector index snapshot', async () => {
320
+ const tempDir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'query-vector-index-'));
321
+ const indexPath = path.join(tempDir, 'local_vector_index.v1.json');
322
+ try {
323
+ const atoms = [
324
+ makeAtom('atom_a', 'Semantic Retrieval', 'semantic vector retrieval coverage', ['semantic', 'retrieval']),
325
+ makeAtom('atom_b', 'Temporal Alignment', 'temporal validity and trace checks', ['temporal', 'trace']),
326
+ ];
327
+ const activeEdges = [];
328
+ const context = {
329
+ request: {
330
+ query: 'semantic retrieval',
331
+ topK: 3,
332
+ },
333
+ query: 'semantic retrieval',
334
+ queryTokens: ['semantic', 'retrieval'],
335
+ asOf: '2026-01-01T00:00:00.000Z',
336
+ topK: 3,
337
+ atoms,
338
+ activeEdges,
339
+ };
340
+ const firstBackend = (0, queryBackend_1.createGraphQueryBackend)({
341
+ backend: 'local_vector',
342
+ localVectorIndexPath: indexPath,
343
+ });
344
+ const firstResult = await firstBackend.query(context);
345
+ expect(firstResult.trace?.retrievalModes).toContain('vector_similarity');
346
+ expect(fs.existsSync(indexPath)).toBe(true);
347
+ const firstDiagnostics = firstBackend.getDiagnostics?.();
348
+ expect(firstDiagnostics?.vectorIndex?.status).toBe('ready');
349
+ expect(firstDiagnostics?.vectorIndex?.persisted).toBe(true);
350
+ expect(firstDiagnostics?.vectorIndex?.loadedFromDisk).toBe(false);
351
+ expect(firstDiagnostics?.vectorIndex?.acceleration?.representationStatus).toBe('aligned');
352
+ expect(firstDiagnostics?.vectorIndex?.acceleration?.representationVersion).toBe('local-vector-representation-v2');
353
+ const signature = firstDiagnostics?.vectorIndex?.signature;
354
+ const secondBackend = (0, queryBackend_1.createGraphQueryBackend)({
355
+ backend: 'local_vector',
356
+ localVectorIndexPath: indexPath,
357
+ });
358
+ const secondResult = await secondBackend.query(context);
359
+ expect(secondResult.trace?.retrievalModes).toContain('vector_similarity');
360
+ const secondDiagnostics = secondBackend.getDiagnostics?.();
361
+ expect(secondDiagnostics?.vectorIndex?.status).toBe('ready');
362
+ expect(secondDiagnostics?.vectorIndex?.loadedFromDisk).toBe(true);
363
+ expect(secondDiagnostics?.vectorIndex?.persisted).toBe(true);
364
+ expect(secondDiagnostics?.vectorIndex?.signature).toBe(signature);
365
+ expect(secondDiagnostics?.vectorIndex?.location).toBe(path.resolve(indexPath));
366
+ expect(secondDiagnostics?.vectorIndex?.acceleration?.representationStatus).toBe('aligned');
367
+ expect(secondDiagnostics?.vectorIndex?.acceleration?.embeddingModelId).toBe('local-semantic-tfidf-unicode-v2');
368
+ }
369
+ finally {
370
+ fs.rmSync(tempDir, { recursive: true, force: true });
371
+ }
372
+ });
373
+ test('local vector backend diagnostics move stale->ready after explicit invalidation and refresh', async () => {
374
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
375
+ const atoms = [
376
+ makeAtom('atom_a', 'Retention Loop', 'retention and retrieval stability', ['retention', 'retrieval']),
377
+ makeAtom('atom_b', 'Spacing Loop', 'spacing and review cadence', ['spacing', 'review']),
378
+ ];
379
+ const activeEdges = [];
380
+ const context = {
381
+ request: {
382
+ query: 'retrieval retention',
383
+ topK: 3,
384
+ },
385
+ query: 'retrieval retention',
386
+ queryTokens: ['retrieval', 'retention'],
387
+ asOf: '2026-01-01T00:00:00.000Z',
388
+ topK: 3,
389
+ atoms,
390
+ activeEdges,
391
+ };
392
+ await backend.query(context);
393
+ const readyDiagnostics = backend.getDiagnostics?.();
394
+ expect(readyDiagnostics?.vectorIndex?.status).toBe('ready');
395
+ backend.invalidate?.('unit_test_ingest_refresh');
396
+ const staleDiagnostics = backend.getDiagnostics?.();
397
+ expect(staleDiagnostics?.vectorIndex?.status).toBe('stale');
398
+ await backend.query(context);
399
+ const recoveredDiagnostics = backend.getDiagnostics?.();
400
+ expect(recoveredDiagnostics?.vectorIndex?.status).toBe('ready');
401
+ });
402
+ test('local hybrid backend matches lexical variants through semantic stemming', async () => {
403
+ const localBackend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
404
+ const keywordBackend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'keyword_only' });
405
+ const atoms = [
406
+ makeAtom('atom_retest', 'Retesting Cadence', 'Use periodic retesting windows to stabilize mastery after diagnostics.', ['cadence', 'mastery']),
407
+ makeAtom('atom_other', 'Baseline Topic', 'baseline note unrelated to retest loop', ['baseline']),
408
+ ];
409
+ const activeEdges = [];
410
+ const context = {
411
+ request: {
412
+ query: 'retests',
413
+ topK: 3,
414
+ },
415
+ query: 'retests',
416
+ queryTokens: ['retests'],
417
+ asOf: '2026-01-01T00:00:00.000Z',
418
+ topK: 3,
419
+ atoms,
420
+ activeEdges,
421
+ };
422
+ const keywordResult = await keywordBackend.query(context);
423
+ const localResult = await localBackend.query(context);
424
+ expect(keywordResult.candidates.some((candidate) => candidate.atomId === 'atom_retest')).toBe(false);
425
+ expect(localResult.candidates.some((candidate) => candidate.atomId === 'atom_retest')).toBe(true);
426
+ expect(localResult.trace?.retrievalModes).toContain('semantic_similarity');
427
+ });
428
+ test('local hybrid ranking favors anchor-connected prerequisite structure over an unrelated high-degree hub', async () => {
429
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
430
+ const atoms = [
431
+ makeAtom('atom_anchor', 'Ground State', 'ground state calibration target for the optics workflow', ['ground', 'state', 'calibration']),
432
+ makeAtom('atom_prerequisite', 'Foundation Step', 'foundation prerequisite for calibration and setup', ['foundation', 'calibration', 'setup']),
433
+ makeAtom('atom_hub', 'Optics Index', 'broad optics index with calibration overview references', ['optics', 'calibration', 'overview']),
434
+ makeAtom('atom_extra_a', 'Reference A', 'generic optics reference', ['reference']),
435
+ makeAtom('atom_extra_b', 'Reference B', 'generic optics reference', ['reference']),
436
+ makeAtom('atom_extra_c', 'Reference C', 'generic optics reference', ['reference']),
437
+ makeAtom('atom_extra_d', 'Reference D', 'generic optics reference', ['reference']),
438
+ ];
439
+ const activeEdges = [
440
+ {
441
+ id: 'edge_prerequisite_anchor',
442
+ sourceAtomId: 'atom_prerequisite',
443
+ targetAtomId: 'atom_anchor',
444
+ relationKind: 'prerequisite',
445
+ provenance: 'fact',
446
+ confidence: 0.94,
447
+ evidenceSpanIds: [],
448
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
449
+ },
450
+ {
451
+ id: 'edge_hub_a',
452
+ sourceAtomId: 'atom_hub',
453
+ targetAtomId: 'atom_extra_a',
454
+ relationKind: 'reference',
455
+ provenance: 'fact',
456
+ confidence: 0.81,
457
+ evidenceSpanIds: [],
458
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
459
+ },
460
+ {
461
+ id: 'edge_hub_b',
462
+ sourceAtomId: 'atom_hub',
463
+ targetAtomId: 'atom_extra_b',
464
+ relationKind: 'reference',
465
+ provenance: 'fact',
466
+ confidence: 0.81,
467
+ evidenceSpanIds: [],
468
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
469
+ },
470
+ {
471
+ id: 'edge_hub_c',
472
+ sourceAtomId: 'atom_hub',
473
+ targetAtomId: 'atom_extra_c',
474
+ relationKind: 'reference',
475
+ provenance: 'fact',
476
+ confidence: 0.81,
477
+ evidenceSpanIds: [],
478
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
479
+ },
480
+ {
481
+ id: 'edge_hub_d',
482
+ sourceAtomId: 'atom_hub',
483
+ targetAtomId: 'atom_extra_d',
484
+ relationKind: 'reference',
485
+ provenance: 'fact',
486
+ confidence: 0.81,
487
+ evidenceSpanIds: [],
488
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
489
+ },
490
+ ];
491
+ const result = await backend.query({
492
+ request: {
493
+ query: 'how to calibrate ground state',
494
+ topK: 4,
495
+ },
496
+ query: 'how to calibrate ground state',
497
+ queryTokens: ['how', 'to', 'calibrate', 'ground', 'state'],
498
+ asOf: '2026-01-01T00:00:00.000Z',
499
+ topK: 4,
500
+ atoms,
501
+ activeEdges,
502
+ });
503
+ const rankedAtomIds = result.candidates.map((candidate) => candidate.atomId);
504
+ expect(rankedAtomIds.indexOf('atom_anchor')).toBeGreaterThanOrEqual(0);
505
+ expect(rankedAtomIds.indexOf('atom_prerequisite')).toBeGreaterThanOrEqual(0);
506
+ expect(rankedAtomIds.slice(0, 2)).toEqual(expect.arrayContaining(['atom_anchor', 'atom_prerequisite']));
507
+ if (rankedAtomIds.includes('atom_hub')) {
508
+ expect(rankedAtomIds.indexOf('atom_anchor')).toBeLessThan(rankedAtomIds.indexOf('atom_hub'));
509
+ expect(rankedAtomIds.indexOf('atom_prerequisite')).toBeLessThan(rankedAtomIds.indexOf('atom_hub'));
510
+ }
511
+ expect(result.trace?.retrievalModes).toEqual(expect.arrayContaining([
512
+ 'graph_anchor_distance',
513
+ 'graph_path_confidence',
514
+ 'graph_intent_match',
515
+ ]));
516
+ });
517
+ test('local hybrid ranking uses Chinese compare intent to favor contrast structure over a lexically stronger reference note', async () => {
518
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_hybrid' });
519
+ const atoms = [
520
+ makeAtom('atom_anchor_cn', '反射', '反射描述光线返回原介质的现象。', ['反射'], 'zh'),
521
+ makeAtom('atom_contrast_cn', '吸收', '吸收表示光能被材料消耗。', ['吸收'], 'zh'),
522
+ makeAtom('atom_reference_cn', '光学概览', '反射 概览 与 现象 说明。', ['概览'], 'zh'),
523
+ ];
524
+ const activeEdges = [
525
+ {
526
+ id: 'edge_contrast_cn',
527
+ sourceAtomId: 'atom_contrast_cn',
528
+ targetAtomId: 'atom_anchor_cn',
529
+ relationKind: 'contrast',
530
+ provenance: 'fact',
531
+ confidence: 0.96,
532
+ evidenceSpanIds: [],
533
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
534
+ },
535
+ {
536
+ id: 'edge_reference_cn',
537
+ sourceAtomId: 'atom_reference_cn',
538
+ targetAtomId: 'atom_anchor_cn',
539
+ relationKind: 'reference',
540
+ provenance: 'fact',
541
+ confidence: 0.88,
542
+ evidenceSpanIds: [],
543
+ temporal: { validFrom: '2026-01-01T00:00:00.000Z' },
544
+ },
545
+ ];
546
+ const result = await backend.query({
547
+ request: {
548
+ query: '对比反射现象',
549
+ topK: 3,
550
+ },
551
+ query: '对比反射现象',
552
+ queryTokens: ['对比', '反射', '现象'],
553
+ asOf: '2026-01-01T00:00:00.000Z',
554
+ topK: 3,
555
+ atoms,
556
+ activeEdges,
557
+ });
558
+ const rankedAtomIds = result.candidates.map((candidate) => candidate.atomId);
559
+ expect(rankedAtomIds.indexOf('atom_anchor_cn')).toBeGreaterThanOrEqual(0);
560
+ expect(rankedAtomIds.indexOf('atom_contrast_cn')).toBeGreaterThanOrEqual(0);
561
+ expect(rankedAtomIds.indexOf('atom_reference_cn')).toBeGreaterThanOrEqual(0);
562
+ expect(rankedAtomIds.indexOf('atom_contrast_cn')).toBeLessThan(rankedAtomIds.indexOf('atom_reference_cn'));
563
+ });
564
+ test('local vector ranking penalizes a temporally invalid candidate even when semantic overlap is strong', async () => {
565
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
566
+ const atoms = [
567
+ makeAtom('atom_fresh', 'Ground State', 'ground state calibration target with verified optics semantics', ['ground', 'state', 'calibration']),
568
+ makeAtom('atom_stale', 'Ground State Draft', 'ground state calibration target with verified optics semantics', ['ground', 'state', 'calibration']),
569
+ ];
570
+ const result = await backend.query({
571
+ request: {
572
+ query: 'ground state calibration',
573
+ topK: 3,
574
+ },
575
+ query: 'ground state calibration',
576
+ queryTokens: ['ground', 'state', 'calibration'],
577
+ asOf: '2026-01-01T00:00:00.000Z',
578
+ topK: 3,
579
+ atoms,
580
+ activeEdges: [],
581
+ atomTemporalValidity: {
582
+ atom_fresh: {
583
+ isValid: true,
584
+ reasonCount: 1,
585
+ supersedesCount: 1,
586
+ },
587
+ atom_stale: {
588
+ isValid: false,
589
+ reasonCount: 2,
590
+ supersedesCount: 0,
591
+ },
592
+ },
593
+ });
594
+ expect(result.candidates[0]?.atomId).toBe('atom_fresh');
595
+ expect(result.candidates.map((candidate) => candidate.atomId)).toContain('atom_stale');
596
+ expect(result.trace?.retrievalModes).toEqual(expect.arrayContaining([
597
+ 'graph_anchor_distance',
598
+ 'graph_path_confidence',
599
+ 'graph_intent_match',
600
+ 'temporal_filter',
601
+ ]));
602
+ });
603
+ test('local vector backend enables ann prefilter on large corpora', async () => {
604
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
605
+ const atoms = [];
606
+ for (let index = 0; index < 160; index += 1) {
607
+ const bucket = index < 72 ? 'retrieval' : 'baseline';
608
+ atoms.push(makeAtom(`atom_${index}`, `${bucket} topic ${index}`, `${bucket} mastery flow diagnostics coverage ${index}`, [bucket, 'mastery', 'diagnostics']));
609
+ }
610
+ const context = {
611
+ request: {
612
+ query: 'retrieval mastery diagnostics',
613
+ topK: 4,
614
+ },
615
+ query: 'retrieval mastery diagnostics',
616
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
617
+ asOf: '2026-01-01T00:00:00.000Z',
618
+ topK: 4,
619
+ atoms,
620
+ activeEdges: [],
621
+ };
622
+ const result = await backend.query(context);
623
+ expect(result.candidates.length).toBeGreaterThan(0);
624
+ expect(result.trace?.retrievalModes).toContain('vector_similarity');
625
+ expect(result.trace?.retrievalModes).toContain('ann_prefilter');
626
+ expect(result.trace?.vectorAcceleration?.mode).toBe('ann_prefilter');
627
+ expect(['token_prefilter', 'token_signature_prefilter']).toContain(String(result.trace?.vectorAcceleration?.selectionMode || ''));
628
+ expect(Number(result.trace?.vectorAcceleration?.candidateCount || 0)).toBeGreaterThan(0);
629
+ expect(typeof result.trace?.vectorAcceleration?.adapterId).toBe('string');
630
+ });
631
+ test('local vector backend falls back to full scan when ann candidate pool is too small', async () => {
632
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({ backend: 'local_vector' });
633
+ const atoms = [];
634
+ for (let index = 0; index < 120; index += 1) {
635
+ atoms.push(makeAtom(`atom_${index}`, `baseline topic ${index}`, `baseline mastery diagnostics coverage ${index}`, ['baseline', 'mastery', 'diagnostics']));
636
+ }
637
+ atoms.push(makeAtom('atom_rare', 'ultrarare diagnostics', 'ultrarare retrieval branch', ['ultrarare', 'retrieval']));
638
+ const context = {
639
+ request: {
640
+ query: 'ultrarare retrieval',
641
+ topK: 3,
642
+ },
643
+ query: 'ultrarare retrieval',
644
+ queryTokens: ['ultrarare', 'retrieval'],
645
+ asOf: '2026-01-01T00:00:00.000Z',
646
+ topK: 3,
647
+ atoms,
648
+ activeEdges: [],
649
+ };
650
+ const result = await backend.query(context);
651
+ expect(result.candidates.some((candidate) => candidate.atomId === 'atom_rare')).toBe(true);
652
+ expect(result.trace?.retrievalModes).toContain('vector_similarity');
653
+ expect(result.trace?.retrievalModes).not.toContain('ann_prefilter');
654
+ expect(result.trace?.vectorAcceleration?.mode).toBe('full_scan');
655
+ expect(result.trace?.vectorAcceleration?.selectionMode).toBe('full_scan');
656
+ });
657
+ test('local vector backend can disable ann prefilter via factory option and exposes diagnostics mode', async () => {
658
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
659
+ backend: 'local_vector',
660
+ localVectorAnnPrefilterEnabled: false,
661
+ });
662
+ const atoms = [];
663
+ for (let index = 0; index < 160; index += 1) {
664
+ atoms.push(makeAtom(`atom_${index}`, `retrieval topic ${index}`, `retrieval mastery diagnostics coverage ${index}`, ['retrieval', 'mastery', 'diagnostics']));
665
+ }
666
+ const result = await backend.query({
667
+ request: {
668
+ query: 'retrieval mastery diagnostics',
669
+ topK: 4,
670
+ },
671
+ query: 'retrieval mastery diagnostics',
672
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
673
+ asOf: '2026-01-01T00:00:00.000Z',
674
+ topK: 4,
675
+ atoms,
676
+ activeEdges: [],
677
+ });
678
+ expect(result.trace?.retrievalModes).toContain('vector_similarity');
679
+ expect(result.trace?.retrievalModes).not.toContain('ann_prefilter');
680
+ const diagnostics = backend.getDiagnostics?.();
681
+ expect(diagnostics?.vectorIndex?.acceleration?.enabled).toBe(false);
682
+ expect(diagnostics?.vectorIndex?.acceleration?.mode).toBe('full_scan');
683
+ });
684
+ test('local vector backend supports pluggable acceleration adapter contract', async () => {
685
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
686
+ backend: 'local_vector',
687
+ localVectorAccelerationAdapter: {
688
+ id: 'unit-adapter-v1',
689
+ selectCandidates: () => ({
690
+ used: true,
691
+ candidateAtomIds: ['atom_focus'],
692
+ mode: 'token_prefilter',
693
+ }),
694
+ },
695
+ });
696
+ const atoms = [];
697
+ for (let index = 0; index < 160; index += 1) {
698
+ atoms.push(makeAtom(`atom_${index}`, `baseline topic ${index}`, `baseline mastery diagnostics coverage ${index}`, ['baseline', 'mastery']));
699
+ }
700
+ atoms.push(makeAtom('atom_focus', 'retrieval focus', 'retrieval mastery diagnostics critical note', ['retrieval', 'mastery', 'diagnostics']));
701
+ const result = await backend.query({
702
+ request: {
703
+ query: 'retrieval mastery diagnostics',
704
+ topK: 4,
705
+ },
706
+ query: 'retrieval mastery diagnostics',
707
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
708
+ asOf: '2026-01-01T00:00:00.000Z',
709
+ topK: 4,
710
+ atoms,
711
+ activeEdges: [],
712
+ });
713
+ expect(result.candidates.some((candidate) => candidate.atomId === 'atom_focus')).toBe(true);
714
+ expect(result.trace?.retrievalModes).toContain('ann_prefilter');
715
+ expect(result.trace?.vectorAcceleration?.adapterId).toBe('unit-adapter-v1');
716
+ expect(result.trace?.vectorAcceleration?.mode).toBe('ann_prefilter');
717
+ const diagnostics = backend.getDiagnostics?.();
718
+ expect(diagnostics?.vectorIndex?.acceleration?.adapterId).toBe('unit-adapter-v1');
719
+ expect(diagnostics?.vectorIndex?.acceleration?.mode).toBe('ann_prefilter');
720
+ });
721
+ test('local vector backend syncs acceleration adapter index before candidate selection', async () => {
722
+ const callSequence = [];
723
+ const syncedIndexSignatures = [];
724
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
725
+ backend: 'local_vector',
726
+ localVectorAccelerationAdapter: {
727
+ id: 'sync-aware-adapter-v1',
728
+ syncIndex: (input) => {
729
+ callSequence.push('sync');
730
+ syncedIndexSignatures.push(String(input.indexSignature || ''));
731
+ return {
732
+ synced: true,
733
+ atomCount: input.atomCount,
734
+ indexSignature: String(input.indexSignature || ''),
735
+ representation: {
736
+ version: String(input.representationVersion || ''),
737
+ embeddingModelId: String(input.embeddingModelId || ''),
738
+ embeddingDimension: Number(input.embeddingDimension || 0),
739
+ indexSignature: String(input.indexSignature || ''),
740
+ validated: true,
741
+ },
742
+ };
743
+ },
744
+ selectCandidates: () => {
745
+ callSequence.push('select');
746
+ return {
747
+ used: true,
748
+ candidateAtomIds: ['atom_focus'],
749
+ mode: 'token_prefilter',
750
+ };
751
+ },
752
+ getHealth: () => ({
753
+ status: 'ready',
754
+ indexSyncStatus: 'ready',
755
+ syncRequestCount: 1,
756
+ syncSuccessCount: 1,
757
+ syncedIndexSignature: syncedIndexSignatures[0] || '',
758
+ syncedAtomCount: 161,
759
+ representationVersion: 'local-vector-representation-v2',
760
+ embeddingModelId: 'local-semantic-tfidf-unicode-v2',
761
+ embeddingDimension: 3,
762
+ indexSignature: syncedIndexSignatures[0] || '',
763
+ representationStatus: 'aligned',
764
+ }),
765
+ },
766
+ });
767
+ const atoms = [];
768
+ for (let index = 0; index < 160; index += 1) {
769
+ atoms.push(makeAtom(`atom_${index}`, `baseline topic ${index}`, `baseline mastery diagnostics coverage ${index}`, ['baseline', 'mastery']));
770
+ }
771
+ atoms.push(makeAtom('atom_focus', 'retrieval focus', 'retrieval mastery diagnostics critical note', ['retrieval', 'mastery', 'diagnostics']));
772
+ const result = await backend.query({
773
+ request: {
774
+ query: 'retrieval mastery diagnostics',
775
+ topK: 4,
776
+ },
777
+ query: 'retrieval mastery diagnostics',
778
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
779
+ asOf: '2026-01-01T00:00:00.000Z',
780
+ topK: 4,
781
+ atoms,
782
+ activeEdges: [],
783
+ });
784
+ expect(result.candidates.some((candidate) => candidate.atomId === 'atom_focus')).toBe(true);
785
+ expect(callSequence[0]).toBe('sync');
786
+ expect(callSequence[1]).toBe('select');
787
+ expect(String(syncedIndexSignatures[0] || '')).not.toBe('');
788
+ const diagnostics = backend.getDiagnostics?.();
789
+ expect(diagnostics?.vectorIndex?.acceleration?.indexSyncStatus).toBe('ready');
790
+ expect(diagnostics?.vectorIndex?.acceleration?.syncRequestCount).toBe(1);
791
+ expect(diagnostics?.vectorIndex?.acceleration?.syncSuccessCount).toBe(1);
792
+ expect(String(diagnostics?.vectorIndex?.acceleration?.syncedIndexSignature || '')).toBe(String(syncedIndexSignatures[0] || ''));
793
+ });
794
+ test('local vector backend exposes adapter health telemetry in query trace vectorAcceleration', async () => {
795
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
796
+ backend: 'local_vector',
797
+ localVectorAccelerationAdapter: {
798
+ id: 'telemetry-adapter-v1',
799
+ selectCandidates: () => ({
800
+ used: true,
801
+ candidateAtomIds: ['atom_focus'],
802
+ mode: 'token_prefilter',
803
+ }),
804
+ getHealth: () => ({
805
+ status: 'degraded',
806
+ circuitState: 'open',
807
+ lastRequestId: 'connector-req-42',
808
+ lastErrorCode: 'upstream_503',
809
+ lastRetryAfterMs: 1200,
810
+ representationVersion: 'remote-representation-v2',
811
+ embeddingModelId: 'remote-embedding-v2',
812
+ embeddingDimension: 64,
813
+ indexSignature: 'remote_sig_v2',
814
+ }),
815
+ },
816
+ });
817
+ const atoms = [];
818
+ for (let index = 0; index < 160; index += 1) {
819
+ atoms.push(makeAtom(`atom_${index}`, `baseline topic ${index}`, `baseline mastery diagnostics coverage ${index}`, ['baseline', 'mastery']));
820
+ }
821
+ atoms.push(makeAtom('atom_focus', 'retrieval focus', 'retrieval mastery diagnostics critical note', ['retrieval', 'mastery', 'diagnostics']));
822
+ const result = await backend.query({
823
+ request: {
824
+ query: 'retrieval mastery diagnostics',
825
+ topK: 4,
826
+ },
827
+ query: 'retrieval mastery diagnostics',
828
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
829
+ asOf: '2026-01-01T00:00:00.000Z',
830
+ topK: 4,
831
+ atoms,
832
+ activeEdges: [],
833
+ });
834
+ expect(result.trace?.vectorAcceleration?.adapterId).toBe('telemetry-adapter-v1');
835
+ expect(result.trace?.vectorAcceleration?.healthStatus).toBe('degraded');
836
+ expect(result.trace?.vectorAcceleration?.circuitState).toBe('open');
837
+ expect(result.trace?.vectorAcceleration?.lastRequestId).toBe('connector-req-42');
838
+ expect(result.trace?.vectorAcceleration?.lastErrorCode).toBe('upstream_503');
839
+ expect(result.trace?.vectorAcceleration?.lastRetryAfterMs).toBe(1200);
840
+ expect(result.trace?.vectorAcceleration?.representationVersion).toBe('remote-representation-v2');
841
+ expect(result.trace?.vectorAcceleration?.embeddingModelId).toBe('remote-embedding-v2');
842
+ expect(result.trace?.vectorAcceleration?.representationStatus).toBe('mismatch');
843
+ expect(String(result.trace?.vectorAcceleration?.representationStatusReason || '')).toContain('representation_version');
844
+ const diagnostics = backend.getDiagnostics?.();
845
+ expect(diagnostics?.vectorIndex?.acceleration?.representationVersion).toBe('remote-representation-v2');
846
+ expect(diagnostics?.vectorIndex?.acceleration?.representationStatus).toBe('mismatch');
847
+ });
848
+ test('local vector backend fails when representation mismatch is detected in strict mode', async () => {
849
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
850
+ backend: 'local_vector',
851
+ localVectorAccelerationRepresentationStrict: true,
852
+ localVectorAccelerationAdapter: {
853
+ id: 'strict-representation-adapter-v1',
854
+ selectCandidates: () => ({
855
+ used: true,
856
+ candidateAtomIds: ['atom_focus'],
857
+ mode: 'token_prefilter',
858
+ }),
859
+ getHealth: () => ({
860
+ status: 'ready',
861
+ circuitState: 'closed',
862
+ representationVersion: 'remote-representation-v2',
863
+ embeddingModelId: 'remote-embedding-v2',
864
+ embeddingDimension: 64,
865
+ indexSignature: 'remote_sig_v2',
866
+ }),
867
+ },
868
+ });
869
+ const atoms = [];
870
+ for (let index = 0; index < 160; index += 1) {
871
+ atoms.push(makeAtom(`atom_${index}`, `baseline topic ${index}`, `baseline mastery diagnostics coverage ${index}`, ['baseline', 'mastery']));
872
+ }
873
+ atoms.push(makeAtom('atom_focus', 'retrieval focus', 'retrieval mastery diagnostics critical note', ['retrieval', 'mastery', 'diagnostics']));
874
+ await expect(backend.query({
875
+ request: {
876
+ query: 'retrieval mastery diagnostics',
877
+ topK: 4,
878
+ },
879
+ query: 'retrieval mastery diagnostics',
880
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
881
+ asOf: '2026-01-01T00:00:00.000Z',
882
+ topK: 4,
883
+ atoms,
884
+ activeEdges: [],
885
+ })).rejects.toThrow('vector_acceleration_representation_mismatch');
886
+ const diagnostics = backend.getDiagnostics?.();
887
+ expect(diagnostics?.vectorIndex?.acceleration?.representationStrictMode).toBe(true);
888
+ expect(diagnostics?.vectorIndex?.acceleration?.representationStatus).toBe('mismatch');
889
+ expect(String(diagnostics?.vectorIndex?.acceleration?.adapterError || '')).toContain('vector_acceleration_representation_mismatch');
890
+ });
891
+ test('local vector backend falls back to full scan when acceleration adapter throws', async () => {
892
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
893
+ backend: 'local_vector',
894
+ localVectorAccelerationAdapter: {
895
+ id: 'unit-adapter-throw',
896
+ selectCandidates: () => {
897
+ throw new Error('adapter_crash');
898
+ },
899
+ },
900
+ });
901
+ const atoms = [];
902
+ for (let index = 0; index < 160; index += 1) {
903
+ atoms.push(makeAtom(`atom_${index}`, `retrieval topic ${index}`, `retrieval mastery diagnostics coverage ${index}`, ['retrieval', 'mastery', 'diagnostics']));
904
+ }
905
+ const result = await backend.query({
906
+ request: {
907
+ query: 'retrieval mastery diagnostics',
908
+ topK: 4,
909
+ },
910
+ query: 'retrieval mastery diagnostics',
911
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
912
+ asOf: '2026-01-01T00:00:00.000Z',
913
+ topK: 4,
914
+ atoms,
915
+ activeEdges: [],
916
+ });
917
+ expect(result.candidates.length).toBeGreaterThan(0);
918
+ expect(result.trace?.retrievalModes).not.toContain('ann_prefilter');
919
+ const diagnostics = backend.getDiagnostics?.();
920
+ expect(diagnostics?.vectorIndex?.acceleration?.adapterId).toBe('unit-adapter-throw');
921
+ expect(diagnostics?.vectorIndex?.acceleration?.mode).toBe('full_scan');
922
+ expect(String(diagnostics?.vectorIndex?.acceleration?.adapterError || '')).toContain('adapter_crash');
923
+ });
924
+ test('local vector backend can fail closed when acceleration adapter throws', async () => {
925
+ const backend = (0, queryBackend_1.createGraphQueryBackend)({
926
+ backend: 'local_vector',
927
+ localVectorAccelerationFailureMode: 'fail_closed',
928
+ localVectorAccelerationAdapter: {
929
+ id: 'unit-adapter-fail-closed',
930
+ selectCandidates: () => {
931
+ throw new Error('adapter_crash_fail_closed');
932
+ },
933
+ },
934
+ });
935
+ const atoms = [];
936
+ for (let index = 0; index < 160; index += 1) {
937
+ atoms.push(makeAtom(`atom_${index}`, `retrieval topic ${index}`, `retrieval mastery diagnostics coverage ${index}`, ['retrieval', 'mastery', 'diagnostics']));
938
+ }
939
+ await expect(backend.query({
940
+ request: {
941
+ query: 'retrieval mastery diagnostics',
942
+ topK: 4,
943
+ },
944
+ query: 'retrieval mastery diagnostics',
945
+ queryTokens: ['retrieval', 'mastery', 'diagnostics'],
946
+ asOf: '2026-01-01T00:00:00.000Z',
947
+ topK: 4,
948
+ atoms,
949
+ activeEdges: [],
950
+ })).rejects.toThrow('vector_acceleration_adapter_failure');
951
+ const diagnostics = backend.getDiagnostics?.();
952
+ expect(diagnostics?.vectorIndex?.acceleration?.failureMode).toBe('fail_closed');
953
+ expect(String(diagnostics?.lastError || '')).toContain('vector_acceleration_adapter_failure');
954
+ });
955
+ });