noteconnection 1.7.0 → 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.
- package/LICENSE +674 -21
- package/README.md +242 -62
- package/dist/src/agent_workspace.contract.parity.test.js +475 -0
- package/dist/src/agent_workspace.frontend.test.js +9989 -0
- package/dist/src/agent_workspace.locale.contract.test.js +95 -0
- package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
- package/dist/src/copy.assets.contract.test.js +130 -0
- package/dist/src/core/PathBridge.js +28 -3
- package/dist/src/export/WorkspaceExportBundle.js +1130 -0
- package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
- package/dist/src/export/index.js +18 -0
- package/dist/src/export/types.js +2 -0
- package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
- package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
- package/dist/src/foundation.release.evidence.contract.test.js +406 -0
- package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
- package/dist/src/frontend/README.md +242 -62
- package/dist/src/frontend/agent_workspace.js +5353 -0
- package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
- package/dist/src/frontend/analysis.js +31 -4
- package/dist/src/frontend/app.js +2178 -62
- package/dist/src/frontend/focus_mode_interactions.js +33 -0
- package/dist/src/frontend/godot_future_path_renderer.js +540 -0
- package/dist/src/frontend/godot_tree_interactions.js +178 -0
- package/dist/src/frontend/graph_state.mjs +105 -0
- package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
- package/dist/src/frontend/i18n.mjs +186 -0
- package/dist/src/frontend/index.html +641 -266
- package/dist/src/frontend/layout_gpu.js +12 -4
- package/dist/src/frontend/locales/en.json +721 -5
- package/dist/src/frontend/locales/zh.json +721 -5
- package/dist/src/frontend/main.mjs +60 -0
- package/dist/src/frontend/markdown_runtime.js +827 -0
- package/dist/src/frontend/notemd.css +49 -0
- package/dist/src/frontend/notemd.html +7 -1
- package/dist/src/frontend/notemd.js +64 -0
- package/dist/src/frontend/path.html +107 -0
- package/dist/src/frontend/path_app.js +2108 -130
- package/dist/src/frontend/path_layout.mjs +143 -0
- package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
- package/dist/src/frontend/path_modules_bridge.js +486 -0
- package/dist/src/frontend/path_state.mjs +118 -0
- package/dist/src/frontend/path_styles.css +146 -0
- package/dist/src/frontend/path_worker_bridge.mjs +85 -0
- package/dist/src/frontend/reader.js +393 -33
- package/dist/src/frontend/runtime_bridge.js +67 -54
- package/dist/src/frontend/runtime_bridge.mjs +279 -0
- package/dist/src/frontend/settings.js +130 -12
- package/dist/src/frontend/source_manager.js +190 -21
- package/dist/src/frontend/styles.css +2720 -72
- package/dist/src/frontend/workbench_state.mjs +101 -0
- package/dist/src/frontend/workspace_panes.js +10168 -0
- package/dist/src/frontend.locale.contract.test.js +62 -0
- package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
- package/dist/src/indexing/IndexLifecycle.js +195 -0
- package/dist/src/indexing/IndexLifecycle.test.js +49 -0
- package/dist/src/indexing/SegmentBuilder.js +64 -0
- package/dist/src/indexing/UnitBuilder.js +48 -0
- package/dist/src/indexing/types.js +2 -0
- package/dist/src/knowledge.api.contract.test.js +170 -0
- package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
- package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
- package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
- package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
- package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
- package/dist/src/learning/answerReleaseReview.js +4319 -0
- package/dist/src/learning/answerReleaseReview.test.js +2888 -0
- package/dist/src/learning/api.js +2 -0
- package/dist/src/learning/conversationComposer.js +1480 -0
- package/dist/src/learning/conversationComposer.test.js +1817 -0
- package/dist/src/learning/domains/ConversationManager.js +53 -0
- package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
- package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
- package/dist/src/learning/domains/MasteryEngine.js +387 -0
- package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
- package/dist/src/learning/domains/QualityEvaluator.js +307 -0
- package/dist/src/learning/domains/TutorRouter.js +313 -0
- package/dist/src/learning/domains/index.js +33 -0
- package/dist/src/learning/domains/types.js +7 -0
- package/dist/src/learning/errors.js +29 -0
- package/dist/src/learning/evidenceContextAssembler.js +1176 -0
- package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
- package/dist/src/learning/graphContextAssembler.js +870 -0
- package/dist/src/learning/graphContextAssembler.test.js +1033 -0
- package/dist/src/learning/index.js +28 -0
- package/dist/src/learning/queryBackend.js +1898 -0
- package/dist/src/learning/queryBackend.test.js +955 -0
- package/dist/src/learning/ragContextPack.js +257 -0
- package/dist/src/learning/ragContextPack.test.js +160 -0
- package/dist/src/learning/ragPublicText.js +38 -0
- package/dist/src/learning/ragSufficiencyJudge.js +161 -0
- package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
- package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
- package/dist/src/learning/requestNormalization.js +198 -0
- package/dist/src/learning/runtimeCapability.js +4677 -0
- package/dist/src/learning/runtimeCapability.test.js +3635 -0
- package/dist/src/learning/store.js +1240 -0
- package/dist/src/learning/store.test.js +1126 -0
- package/dist/src/learning/tutorAdapter.js +2 -0
- package/dist/src/learning/types.js +2 -0
- package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
- package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
- package/dist/src/lfs.asset.policy.contract.test.js +153 -0
- package/dist/src/license.policy.contract.test.js +66 -0
- package/dist/src/memory/MemoryGovernance.js +74 -0
- package/dist/src/memory/MemoryGovernance.test.js +46 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
- package/dist/src/middleware/auth.js +17 -0
- package/dist/src/middleware/body-parser.js +45 -0
- package/dist/src/middleware/cors.js +44 -0
- package/dist/src/middleware/index.js +21 -0
- package/dist/src/middleware/request-trace.js +96 -0
- package/dist/src/notemd/AppConfigToml.js +6 -4
- package/dist/src/notemd/MermaidProcessor.js +400 -50
- package/dist/src/notemd/NotemdService.js +498 -13
- package/dist/src/notemd/PromptManager.js +15 -0
- package/dist/src/notemd/cli/commands.js +357 -0
- package/dist/src/notemd/cli/dispatcher.js +225 -0
- package/dist/src/notemd/cli/index.js +169 -0
- package/dist/src/notemd/cli/parser.js +68 -0
- package/dist/src/notemd/cli/types.js +2 -0
- package/dist/src/notemd/constants.js +43 -0
- package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
- package/dist/src/notemd/diagram/diagramSpec.js +79 -0
- package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
- package/dist/src/notemd/diagram/intent.js +95 -0
- package/dist/src/notemd/diagram/planner.js +71 -0
- package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
- package/dist/src/notemd/diagram/types.js +18 -0
- package/dist/src/notemd/index.js +26 -0
- package/dist/src/notemd/operations/capabilityManifest.js +23 -0
- package/dist/src/notemd/operations/cliContracts.js +17 -0
- package/dist/src/notemd/operations/configProfileCommands.js +85 -0
- package/dist/src/notemd/operations/registry.contract.test.js +95 -0
- package/dist/src/notemd/operations/registry.js +991 -0
- package/dist/src/notemd/operations/types.js +2 -0
- package/dist/src/notemd/providerDiagnostics.js +220 -0
- package/dist/src/notemd/providerProfiles.js +42 -0
- package/dist/src/notemd/providerTemplates.js +231 -0
- package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
- package/dist/src/notemd/search/SearchManager.js +13 -0
- package/dist/src/notemd/search/SearchProvider.js +2 -0
- package/dist/src/notemd/search/TavilyProvider.js +44 -0
- package/dist/src/notemd.agent.manifest.test.js +85 -0
- package/dist/src/notemd.api.contract.test.js +14 -1
- package/dist/src/notemd.app_config_toml.test.js +2 -1
- package/dist/src/notemd.batch.workflow.test.js +117 -0
- package/dist/src/notemd.cli.e2e.test.js +136 -0
- package/dist/src/notemd.core.test.js +51 -0
- package/dist/src/notemd.diagram.pipeline.test.js +233 -0
- package/dist/src/notemd.providerTemplates.test.js +34 -0
- package/dist/src/notemd.server.integration.test.js +143 -35
- package/dist/src/notemd.workflow.pipeline.test.js +162 -0
- package/dist/src/pathbridge.handshake.contract.test.js +16 -2
- package/dist/src/pathmode.background.contract.test.js +69 -0
- package/dist/src/pathmode.settings.api.contract.test.js +9 -0
- package/dist/src/pkg.sidecar.contract.test.js +9 -3
- package/dist/src/platform/ExportProfile.js +58 -0
- package/dist/src/platform/PlatformCapabilities.js +45 -0
- package/dist/src/platform/PlatformCapabilities.test.js +30 -0
- package/dist/src/platform/RenderMaterializer.js +33 -0
- package/dist/src/platform/RenderMaterializer.test.js +32 -0
- package/dist/src/query_backend.external_http.integration.test.js +410 -0
- package/dist/src/reader_renderer.js +404 -3
- package/dist/src/reader_renderer.test.js +87 -0
- package/dist/src/release.godot.mirror.contract.test.js +73 -0
- package/dist/src/resources/ResourceRegistry.js +223 -0
- package/dist/src/resources/ResourceRegistry.test.js +61 -0
- package/dist/src/resources/types.js +2 -0
- package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
- package/dist/src/routes/data.js +267 -0
- package/dist/src/routes/diagnostics.js +51 -0
- package/dist/src/routes/index.js +23 -0
- package/dist/src/routes/knowledge.js +968 -0
- package/dist/src/routes/markdown.js +287 -0
- package/dist/src/routes/notemd.js +565 -0
- package/dist/src/routes/registry.contract.test.js +130 -0
- package/dist/src/routes/render.js +285 -0
- package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
- package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
- package/dist/src/routes/settings.js +6 -0
- package/dist/src/routes/staticFiles.js +94 -0
- package/dist/src/routes/types.js +2 -0
- package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
- package/dist/src/server.js +11090 -1549
- package/dist/src/server.migration.test.js +193 -21
- package/dist/src/server.port.fallback.contract.test.js +63 -0
- package/dist/src/session/SessionStateStore.js +81 -0
- package/dist/src/session/SessionStateStore.test.js +58 -0
- package/dist/src/session/types.js +2 -0
- package/dist/src/settings.runtime.contract.test.js +50 -0
- package/dist/src/shared/types.contract.test.js +107 -0
- package/dist/src/shared/types.js +22 -0
- package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
- package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
- package/dist/src/source_manager.loadflow.test.js +46 -0
- package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
- package/dist/src/tauri.frontend.build.contract.test.js +60 -0
- package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
- package/dist/src/utils/RuntimePaths.js +4 -13
- package/dist/src/utils/platform.js +153 -0
- package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
- package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
- package/dist/src/workflows/types.js +2 -0
- package/dist/src/workspace/WorkspaceRegistry.js +122 -0
- package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
- package/dist/src/workspace/types.js +2 -0
- package/package.json +53 -10
|
@@ -0,0 +1,2971 @@
|
|
|
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 path = __importStar(require("path"));
|
|
38
|
+
const KnowledgeLearningPlatform_1 = require("./KnowledgeLearningPlatform");
|
|
39
|
+
const store_1 = require("./store");
|
|
40
|
+
describe('KnowledgeLearningPlatform', () => {
|
|
41
|
+
let nowIso;
|
|
42
|
+
let platform;
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
nowIso = '2026-03-31T08:00:00.000Z';
|
|
45
|
+
platform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform(() => new Date(nowIso));
|
|
46
|
+
});
|
|
47
|
+
test('ingest supports staleness detection and temporal supersede edges', async () => {
|
|
48
|
+
const firstIngest = await platform.ingestKnowledge({
|
|
49
|
+
incremental: true,
|
|
50
|
+
documents: [
|
|
51
|
+
{
|
|
52
|
+
documentId: 'doc_alpha',
|
|
53
|
+
sourcePath: 'Knowledge_Base/doc_alpha.md',
|
|
54
|
+
language: 'en',
|
|
55
|
+
content: '# Graph Foundations\nGraph states and retrieval evidence.\n\n## Query Layer\nUse explainable retrieval.',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
expect(firstIngest.summary.ingestedDocuments).toBe(1);
|
|
60
|
+
expect(firstIngest.summary.changedDocuments).toBe(1);
|
|
61
|
+
expect(firstIngest.atoms.length).toBeGreaterThan(0);
|
|
62
|
+
expect(firstIngest.evidenceSpans.length).toBeGreaterThan(0);
|
|
63
|
+
expect(firstIngest.staleness[0]?.status).toBe('new');
|
|
64
|
+
nowIso = '2026-03-31T08:10:00.000Z';
|
|
65
|
+
const unchangedIngest = await platform.ingestKnowledge({
|
|
66
|
+
incremental: true,
|
|
67
|
+
documents: [
|
|
68
|
+
{
|
|
69
|
+
documentId: 'doc_alpha',
|
|
70
|
+
sourcePath: 'Knowledge_Base/doc_alpha.md',
|
|
71
|
+
language: 'en',
|
|
72
|
+
content: '# Graph Foundations\nGraph states and retrieval evidence.\n\n## Query Layer\nUse explainable retrieval.',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
expect(unchangedIngest.summary.changedDocuments).toBe(0);
|
|
77
|
+
expect(unchangedIngest.staleness[0]?.status).toBe('unchanged');
|
|
78
|
+
nowIso = '2026-03-31T08:20:00.000Z';
|
|
79
|
+
const updatedIngest = await platform.ingestKnowledge({
|
|
80
|
+
incremental: true,
|
|
81
|
+
documents: [
|
|
82
|
+
{
|
|
83
|
+
documentId: 'doc_alpha',
|
|
84
|
+
sourcePath: 'Knowledge_Base/doc_alpha.md',
|
|
85
|
+
language: 'en',
|
|
86
|
+
content: '# Graph Foundations\nGraph states and retrieval evidence with temporal versioning.\n\n## Query Layer\nUse explainable retrieval with evidence paths.',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
expect(updatedIngest.summary.changedDocuments).toBe(1);
|
|
91
|
+
expect(updatedIngest.staleness[0]?.status).toBe('updated');
|
|
92
|
+
expect(updatedIngest.temporalEdges.length).toBeGreaterThan(0);
|
|
93
|
+
});
|
|
94
|
+
test('warmQueryBackend primes the configured backend without recording user query latency', async () => {
|
|
95
|
+
const backendQuery = jest.fn(async (context) => ({
|
|
96
|
+
candidates: context.atoms.slice(0, 1).map((atom) => ({
|
|
97
|
+
atomId: atom.id,
|
|
98
|
+
score: 1,
|
|
99
|
+
})),
|
|
100
|
+
trace: {
|
|
101
|
+
retrievalModes: ['warmup_probe'],
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
const warmPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
105
|
+
nowProvider: () => new Date(nowIso),
|
|
106
|
+
graphQueryBackend: {
|
|
107
|
+
id: 'warmup-test-backend',
|
|
108
|
+
query: backendQuery,
|
|
109
|
+
getDiagnostics: () => ({
|
|
110
|
+
backendId: 'warmup-test-backend',
|
|
111
|
+
ready: true,
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
graphQueryBackendFactoryOptions: {
|
|
115
|
+
backend: 'local_hybrid',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
await warmPlatform.ingestKnowledge({
|
|
119
|
+
incremental: true,
|
|
120
|
+
documents: [
|
|
121
|
+
{
|
|
122
|
+
documentId: 'doc_warmup_backend',
|
|
123
|
+
sourcePath: 'Knowledge_Base/warmup/backend.md',
|
|
124
|
+
language: 'en',
|
|
125
|
+
content: '# Warmup Backend\nWarmup should prime the configured query backend.',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
documentId: 'doc_warmup_other',
|
|
129
|
+
sourcePath: 'Knowledge_Base/other/backend.md',
|
|
130
|
+
language: 'en',
|
|
131
|
+
content: '# Other Backend\nThis note stays outside the requested warmup scope.',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
const before = warmPlatform.getKnowledgeState().retrievalTelemetry.queryCount;
|
|
136
|
+
const result = await warmPlatform.warmQueryBackend({
|
|
137
|
+
query: 'warmup backend',
|
|
138
|
+
topK: 1,
|
|
139
|
+
scope: {
|
|
140
|
+
sourcePathPrefixes: ['Knowledge_Base/warmup'],
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const after = warmPlatform.getKnowledgeState().retrievalTelemetry.queryCount;
|
|
144
|
+
expect(backendQuery).toHaveBeenCalledTimes(1);
|
|
145
|
+
expect(backendQuery.mock.calls[0]?.[0]).toEqual(expect.objectContaining({
|
|
146
|
+
query: 'warmup backend',
|
|
147
|
+
topK: 1,
|
|
148
|
+
atoms: expect.arrayContaining([
|
|
149
|
+
expect.objectContaining({ documentId: 'doc_warmup_backend' }),
|
|
150
|
+
]),
|
|
151
|
+
indexAtoms: expect.arrayContaining([
|
|
152
|
+
expect.objectContaining({ documentId: 'doc_warmup_backend' }),
|
|
153
|
+
expect.objectContaining({ documentId: 'doc_warmup_other' }),
|
|
154
|
+
]),
|
|
155
|
+
}));
|
|
156
|
+
expect(backendQuery.mock.calls[0]?.[0].atoms).toHaveLength(1);
|
|
157
|
+
expect(backendQuery.mock.calls[0]?.[0].indexAtoms).toHaveLength(2);
|
|
158
|
+
expect(result).toEqual(expect.objectContaining({
|
|
159
|
+
warmed: true,
|
|
160
|
+
backendId: 'warmup-test-backend',
|
|
161
|
+
totalAtomsInScope: 1,
|
|
162
|
+
candidateCount: 1,
|
|
163
|
+
}));
|
|
164
|
+
expect(after).toBe(before);
|
|
165
|
+
});
|
|
166
|
+
test('queryKnowledge does not persist a read-only query response', async () => {
|
|
167
|
+
const saveSnapshot = jest.fn(async () => undefined);
|
|
168
|
+
const readOnlyPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
169
|
+
nowProvider: () => new Date(nowIso),
|
|
170
|
+
store: {
|
|
171
|
+
loadSnapshot: jest.fn(async () => null),
|
|
172
|
+
saveSnapshot,
|
|
173
|
+
getDiagnostics: () => ({
|
|
174
|
+
storeType: 'memory',
|
|
175
|
+
exists: false,
|
|
176
|
+
loaded: false,
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
await readOnlyPlatform.ingestKnowledge({
|
|
181
|
+
incremental: true,
|
|
182
|
+
documents: [
|
|
183
|
+
{
|
|
184
|
+
documentId: 'doc_read_only_query',
|
|
185
|
+
sourcePath: 'Knowledge_Base/read_only/query.md',
|
|
186
|
+
language: 'en',
|
|
187
|
+
content: '# Read Only Query\nRetrieval should not rewrite the graph store.',
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
expect(saveSnapshot).toHaveBeenCalledTimes(1);
|
|
192
|
+
saveSnapshot.mockClear();
|
|
193
|
+
const result = await readOnlyPlatform.queryKnowledge({
|
|
194
|
+
query: 'read only retrieval',
|
|
195
|
+
topK: 1,
|
|
196
|
+
scope: {
|
|
197
|
+
sourcePathPrefixes: ['Knowledge_Base/read_only'],
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
expect(result.items.length).toBeGreaterThan(0);
|
|
201
|
+
expect(saveSnapshot).not.toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
test('previewLearningPath avoids synchronous snapshot persistence while buildLearningPath keeps artifact durability', async () => {
|
|
204
|
+
const saveSnapshot = jest.fn(async () => undefined);
|
|
205
|
+
const pathPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
206
|
+
nowProvider: () => new Date(nowIso),
|
|
207
|
+
store: {
|
|
208
|
+
loadSnapshot: jest.fn(async () => null),
|
|
209
|
+
saveSnapshot,
|
|
210
|
+
getDiagnostics: () => ({
|
|
211
|
+
storeType: 'memory',
|
|
212
|
+
exists: false,
|
|
213
|
+
loaded: false,
|
|
214
|
+
}),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
const ingest = await pathPlatform.ingestKnowledge({
|
|
218
|
+
incremental: true,
|
|
219
|
+
documents: [
|
|
220
|
+
{
|
|
221
|
+
documentId: 'doc_learning_path_preview',
|
|
222
|
+
sourcePath: 'Knowledge_Base/path/preview.md',
|
|
223
|
+
language: 'en',
|
|
224
|
+
content: '# Learning Path Preview\nPreviewing a learning path should not block on durable artifact persistence.',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
const atomId = ingest.atoms[0]?.id;
|
|
229
|
+
expect(atomId).toBeDefined();
|
|
230
|
+
expect(saveSnapshot).toHaveBeenCalledTimes(1);
|
|
231
|
+
saveSnapshot.mockClear();
|
|
232
|
+
const preview = await pathPlatform.previewLearningPath({
|
|
233
|
+
userId: 'preview_user',
|
|
234
|
+
focusAtomIds: [atomId],
|
|
235
|
+
maxMasteryPaths: 1,
|
|
236
|
+
maxDivergencePaths: 0,
|
|
237
|
+
});
|
|
238
|
+
expect(preview.masteryPaths.length).toBeGreaterThan(0);
|
|
239
|
+
expect(saveSnapshot).not.toHaveBeenCalled();
|
|
240
|
+
const durable = await pathPlatform.buildLearningPath({
|
|
241
|
+
userId: 'preview_user',
|
|
242
|
+
focusAtomIds: [atomId],
|
|
243
|
+
maxMasteryPaths: 1,
|
|
244
|
+
maxDivergencePaths: 0,
|
|
245
|
+
});
|
|
246
|
+
expect(durable.masteryPaths.length).toBeGreaterThan(0);
|
|
247
|
+
expect(saveSnapshot).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
test('query returns evidence-first results with relation path and temporal validity', async () => {
|
|
250
|
+
await platform.ingestKnowledge({
|
|
251
|
+
incremental: true,
|
|
252
|
+
documents: [
|
|
253
|
+
{
|
|
254
|
+
documentId: 'doc_a',
|
|
255
|
+
sourcePath: 'Knowledge_Base/doc_a.md',
|
|
256
|
+
language: 'en',
|
|
257
|
+
content: '# Mastery Loop\nMastery diagnostics and retest update.\n\n## Related\nSee [[Divergence Engine]].',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
documentId: 'doc_b',
|
|
261
|
+
sourcePath: 'Knowledge_Base/doc_b.md',
|
|
262
|
+
language: 'en',
|
|
263
|
+
content: '# Divergence Engine\nCross-domain expansion and transfer tasks.',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
});
|
|
267
|
+
const queryResult = await platform.queryKnowledge({
|
|
268
|
+
query: 'mastery diagnostics divergence',
|
|
269
|
+
topK: 3,
|
|
270
|
+
asOf: '2026-03-31T08:30:00.000Z',
|
|
271
|
+
});
|
|
272
|
+
expect(queryResult.items.length).toBeGreaterThan(0);
|
|
273
|
+
expect(queryResult.items[0].evidenceSpans.length).toBeGreaterThan(0);
|
|
274
|
+
expect(Array.isArray(queryResult.items[0].relationPath)).toBe(true);
|
|
275
|
+
expect(queryResult.items[0].temporalValidity.checkedAt).toBe('2026-03-31T08:30:00.000Z');
|
|
276
|
+
expect(queryResult.trace.retrievalModes).toContain('temporal_filter');
|
|
277
|
+
expect(queryResult.trace.modeWeights.keyword).toBeGreaterThan(0);
|
|
278
|
+
expect(queryResult.trace.modeWeights.graph).toBeGreaterThan(0);
|
|
279
|
+
expect(queryResult.trace.latencyMs).toBeGreaterThanOrEqual(0);
|
|
280
|
+
expect(queryResult.trace.evidenceCoverageRatio).toBeGreaterThan(0);
|
|
281
|
+
});
|
|
282
|
+
test('query temporal validity preserves supersession edge details after document updates', async () => {
|
|
283
|
+
await platform.ingestKnowledge({
|
|
284
|
+
incremental: true,
|
|
285
|
+
documents: [
|
|
286
|
+
{
|
|
287
|
+
documentId: 'doc_temporal_detail',
|
|
288
|
+
sourcePath: 'Knowledge_Base/temporal/detail.md',
|
|
289
|
+
language: 'en',
|
|
290
|
+
content: '# Temporal Detail\nInitial revision for temporal detail checks.',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
});
|
|
294
|
+
nowIso = '2026-03-31T08:45:00.000Z';
|
|
295
|
+
await platform.ingestKnowledge({
|
|
296
|
+
incremental: true,
|
|
297
|
+
documents: [
|
|
298
|
+
{
|
|
299
|
+
documentId: 'doc_temporal_detail',
|
|
300
|
+
sourcePath: 'Knowledge_Base/temporal/detail.md',
|
|
301
|
+
language: 'en',
|
|
302
|
+
content: '# Temporal Detail\nUpdated revision for temporal detail checks with supersession lineage.',
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
const queryResult = await platform.queryKnowledge({
|
|
307
|
+
query: 'temporal detail supersession lineage',
|
|
308
|
+
topK: 3,
|
|
309
|
+
asOf: '2026-03-31T08:50:00.000Z',
|
|
310
|
+
scope: {
|
|
311
|
+
sourcePathPrefixes: ['Knowledge_Base/temporal'],
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
expect(queryResult.items.length).toBeGreaterThan(0);
|
|
315
|
+
expect(queryResult.items[0].temporalValidity).toEqual(expect.objectContaining({
|
|
316
|
+
checkedAt: '2026-03-31T08:50:00.000Z',
|
|
317
|
+
}));
|
|
318
|
+
expect(queryResult.items[0].temporalValidity.details).toEqual(expect.arrayContaining([
|
|
319
|
+
expect.objectContaining({
|
|
320
|
+
edgeKind: 'supersedes',
|
|
321
|
+
targetAtomId: queryResult.items[0].atom.id,
|
|
322
|
+
isActive: true,
|
|
323
|
+
}),
|
|
324
|
+
]));
|
|
325
|
+
});
|
|
326
|
+
test('query scope constrains retrieval by corpus, language, and source path prefix', async () => {
|
|
327
|
+
await platform.ingestKnowledge({
|
|
328
|
+
incremental: true,
|
|
329
|
+
documents: [
|
|
330
|
+
{
|
|
331
|
+
documentId: 'doc_scope_cn',
|
|
332
|
+
sourcePath: 'Knowledge_Base/optics/absorption.md',
|
|
333
|
+
language: 'zh',
|
|
334
|
+
content: '# 吸收系数\n材料的吸收系数影响光学穿透深度与衰减。',
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
documentId: 'doc_scope_en',
|
|
338
|
+
sourcePath: 'Knowledge_Base/materials/absorption.md',
|
|
339
|
+
language: 'en',
|
|
340
|
+
content: '# Absorption Coefficient\nThe absorption coefficient affects penetration depth.',
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
const queryResult = await platform.queryKnowledge({
|
|
345
|
+
query: '吸收系数 光学',
|
|
346
|
+
topK: 4,
|
|
347
|
+
scope: {
|
|
348
|
+
corpusId: 'optics',
|
|
349
|
+
languages: ['zh'],
|
|
350
|
+
sourcePathPrefixes: ['Knowledge_Base/optics'],
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
expect(queryResult.items.length).toBeGreaterThan(0);
|
|
354
|
+
expect(queryResult.items.every((item) => item.atom.documentId === 'doc_scope_cn')).toBe(true);
|
|
355
|
+
expect(queryResult.trace.totalAtomsInScope).toBeGreaterThan(0);
|
|
356
|
+
expect(queryResult.trace.scope).toEqual(expect.objectContaining({
|
|
357
|
+
source: 'scoped',
|
|
358
|
+
corpusId: 'optics',
|
|
359
|
+
}));
|
|
360
|
+
});
|
|
361
|
+
test('query planner resolves compact mixed-language title queries inside an explicit workspace scope', async () => {
|
|
362
|
+
await platform.ingestKnowledge({
|
|
363
|
+
incremental: true,
|
|
364
|
+
documents: [
|
|
365
|
+
{
|
|
366
|
+
documentId: 'doc_water_glass',
|
|
367
|
+
sourcePath: 'Knowledge_Base/waterglass/water glass.md',
|
|
368
|
+
language: 'zh',
|
|
369
|
+
workspaceId: 'waterglass',
|
|
370
|
+
corpusId: 'waterglass',
|
|
371
|
+
content: '# 水杯 water glass\n水杯是由玻璃容器和内部液体组成的系统。',
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
});
|
|
375
|
+
const queryResult = await platform.queryKnowledge({
|
|
376
|
+
query: '什么是waterglass?',
|
|
377
|
+
topK: 5,
|
|
378
|
+
scope: {
|
|
379
|
+
workspaceId: 'waterglass',
|
|
380
|
+
corpusId: 'waterglass',
|
|
381
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
expect(queryResult.items.length).toBeGreaterThan(0);
|
|
385
|
+
expect(queryResult.trace.scope).toEqual(expect.objectContaining({
|
|
386
|
+
workspaceId: 'waterglass',
|
|
387
|
+
corpusId: 'waterglass',
|
|
388
|
+
scopeSource: 'explicit_request',
|
|
389
|
+
}));
|
|
390
|
+
expect(queryResult.trace.scope?.documentIds).toContain('doc_water_glass');
|
|
391
|
+
expect(queryResult.trace.scope?.readiness).toEqual(expect.objectContaining({
|
|
392
|
+
status: 'ready',
|
|
393
|
+
workspaceId: 'waterglass',
|
|
394
|
+
}));
|
|
395
|
+
expect(queryResult.trace.planner).toEqual(expect.objectContaining({
|
|
396
|
+
titleLikeQueries: expect.arrayContaining(['water glass', 'waterglass']),
|
|
397
|
+
titleHitDocumentIds: expect.arrayContaining(['doc_water_glass']),
|
|
398
|
+
}));
|
|
399
|
+
});
|
|
400
|
+
test('ingest diff operations support delete and dynamic relation recompute', async () => {
|
|
401
|
+
const seed = await platform.ingestKnowledge({
|
|
402
|
+
incremental: true,
|
|
403
|
+
documents: [
|
|
404
|
+
{
|
|
405
|
+
documentId: 'doc_a',
|
|
406
|
+
sourcePath: 'Knowledge_Base/doc_a.md',
|
|
407
|
+
language: 'en',
|
|
408
|
+
content: '# Topic A\nSee [[Topic B]] for prerequisite context.',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
documentId: 'doc_b',
|
|
412
|
+
sourcePath: 'Knowledge_Base/doc_b.md',
|
|
413
|
+
language: 'en',
|
|
414
|
+
content: '# Topic B\nLegacy concept that will be removed.',
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
});
|
|
418
|
+
expect(seed.summary.changedDocuments).toBe(2);
|
|
419
|
+
const diffResult = await platform.ingestKnowledge({
|
|
420
|
+
incremental: true,
|
|
421
|
+
recomputeRelations: true,
|
|
422
|
+
operations: [
|
|
423
|
+
{
|
|
424
|
+
op: 'upsert',
|
|
425
|
+
document: {
|
|
426
|
+
documentId: 'doc_a',
|
|
427
|
+
sourcePath: 'Knowledge_Base/doc_a.md',
|
|
428
|
+
language: 'en',
|
|
429
|
+
content: '# Topic A\nUpdated without outbound wiki links.',
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
op: 'delete',
|
|
434
|
+
document: {
|
|
435
|
+
documentId: 'doc_b',
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
});
|
|
440
|
+
expect(diffResult.summary.ingestedDocuments).toBe(1);
|
|
441
|
+
expect(diffResult.summary.changedDocuments).toBe(1);
|
|
442
|
+
expect(diffResult.summary.deletedDocuments).toBe(1);
|
|
443
|
+
expect(diffResult.summary.recomputedDynamicRelations).toBe(true);
|
|
444
|
+
expect(diffResult.summary.resolvedRelationRecomputeMode).toBe('full');
|
|
445
|
+
expect(diffResult.summary.relationRecomputeLatencyMs).toBeGreaterThanOrEqual(0);
|
|
446
|
+
expect(diffResult.summary.invalidatedRelationEdges).toBeGreaterThanOrEqual(1);
|
|
447
|
+
expect(diffResult.summary.regeneratedRelationEdges).toBeGreaterThanOrEqual(0);
|
|
448
|
+
expect(diffResult.staleness.some((entry) => entry.status === 'deleted' && entry.documentId === 'doc_b')).toBe(true);
|
|
449
|
+
const deletedQuery = await platform.queryKnowledge({
|
|
450
|
+
query: 'Legacy concept removed',
|
|
451
|
+
topK: 5,
|
|
452
|
+
asOf: '2026-03-31T09:00:00.000Z',
|
|
453
|
+
});
|
|
454
|
+
expect(deletedQuery.items.length).toBe(0);
|
|
455
|
+
});
|
|
456
|
+
test('relation recompute mode supports none and incremental strategies', async () => {
|
|
457
|
+
await platform.ingestKnowledge({
|
|
458
|
+
incremental: true,
|
|
459
|
+
relationRecomputeMode: 'none',
|
|
460
|
+
documents: [
|
|
461
|
+
{
|
|
462
|
+
documentId: 'doc_target',
|
|
463
|
+
sourcePath: 'Knowledge_Base/doc_target.md',
|
|
464
|
+
language: 'en',
|
|
465
|
+
content: '# Target Topic\nReference target concept.',
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
});
|
|
469
|
+
const noneModeResult = await platform.ingestKnowledge({
|
|
470
|
+
incremental: true,
|
|
471
|
+
relationRecomputeMode: 'none',
|
|
472
|
+
documents: [
|
|
473
|
+
{
|
|
474
|
+
documentId: 'doc_source',
|
|
475
|
+
sourcePath: 'Knowledge_Base/doc_source.md',
|
|
476
|
+
language: 'en',
|
|
477
|
+
content: '# Source Topic\nSee [[Target Topic]] for details.',
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
});
|
|
481
|
+
expect(noneModeResult.summary.resolvedRelationRecomputeMode).toBe('none');
|
|
482
|
+
expect(noneModeResult.summary.recomputedDynamicRelations).toBe(false);
|
|
483
|
+
expect(noneModeResult.summary.relationRecomputeLatencyMs).toBe(0);
|
|
484
|
+
expect(noneModeResult.relationEdges.some((edge) => edge.relationKind === 'reference')).toBe(false);
|
|
485
|
+
const incrementalResult = await platform.ingestKnowledge({
|
|
486
|
+
incremental: true,
|
|
487
|
+
relationRecomputeMode: 'incremental',
|
|
488
|
+
documents: [
|
|
489
|
+
{
|
|
490
|
+
documentId: 'doc_source',
|
|
491
|
+
sourcePath: 'Knowledge_Base/doc_source.md',
|
|
492
|
+
language: 'en',
|
|
493
|
+
content: '# Source Topic\nUpdated and still references [[Target Topic]].',
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
});
|
|
497
|
+
expect(incrementalResult.summary.resolvedRelationRecomputeMode).toBe('incremental');
|
|
498
|
+
expect(incrementalResult.summary.recomputedDynamicRelations).toBe(false);
|
|
499
|
+
expect(incrementalResult.summary.relationRecomputeLatencyMs).toBe(0);
|
|
500
|
+
expect(incrementalResult.relationEdges.some((edge) => edge.relationKind === 'reference')).toBe(true);
|
|
501
|
+
});
|
|
502
|
+
test('ingest extracts markdown text, code, formula, and mermaid atoms', async () => {
|
|
503
|
+
const ingest = await platform.ingestKnowledge({
|
|
504
|
+
incremental: true,
|
|
505
|
+
documents: [
|
|
506
|
+
{
|
|
507
|
+
documentId: 'doc_structured',
|
|
508
|
+
sourcePath: 'Knowledge_Base/doc_structured.md',
|
|
509
|
+
language: 'en',
|
|
510
|
+
content: [
|
|
511
|
+
'# Structured Parsing',
|
|
512
|
+
'This section includes code, formula, and diagram evidence.',
|
|
513
|
+
'',
|
|
514
|
+
'```ts',
|
|
515
|
+
'const mastery = estimateMastery(state);',
|
|
516
|
+
'```',
|
|
517
|
+
'',
|
|
518
|
+
'$$',
|
|
519
|
+
'P(A|B) = P(B|A) P(A) / P(B)',
|
|
520
|
+
'$$',
|
|
521
|
+
'',
|
|
522
|
+
'```mermaid',
|
|
523
|
+
'graph TD',
|
|
524
|
+
'A[Atom] --> B[Evidence]',
|
|
525
|
+
'```',
|
|
526
|
+
].join('\n'),
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
});
|
|
530
|
+
const representationTypes = new Set(ingest.atoms.map((atom) => atom.representationType));
|
|
531
|
+
expect(representationTypes.has('text')).toBe(true);
|
|
532
|
+
expect(representationTypes.has('code')).toBe(true);
|
|
533
|
+
expect(representationTypes.has('formula')).toBe(true);
|
|
534
|
+
expect(representationTypes.has('mermaid')).toBe(true);
|
|
535
|
+
const mermaidAtom = ingest.atoms.find((atom) => atom.representationType === 'mermaid');
|
|
536
|
+
expect(mermaidAtom).toBeDefined();
|
|
537
|
+
expect(mermaidAtom?.content.toLowerCase()).toContain('graph td');
|
|
538
|
+
});
|
|
539
|
+
test('mastery diagnostics and path generation produce actionable outputs', async () => {
|
|
540
|
+
const ingest = await platform.ingestKnowledge({
|
|
541
|
+
incremental: true,
|
|
542
|
+
documents: [
|
|
543
|
+
{
|
|
544
|
+
documentId: 'doc_mastery',
|
|
545
|
+
sourcePath: 'Knowledge_Base/doc_mastery.md',
|
|
546
|
+
language: 'en',
|
|
547
|
+
content: '# Retrieval Practice\nPractice improves mastery probability.',
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
});
|
|
551
|
+
const atomId = ingest.atoms[0]?.id;
|
|
552
|
+
expect(atomId).toBeDefined();
|
|
553
|
+
const diagnostics = await platform.diagnoseMastery({
|
|
554
|
+
userId: 'user_a',
|
|
555
|
+
observations: [
|
|
556
|
+
{
|
|
557
|
+
atomId: atomId,
|
|
558
|
+
outcome: 'incorrect',
|
|
559
|
+
errorTag: 'concept_boundary',
|
|
560
|
+
errorTags: ['retrieval_failure'],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
atomId: atomId,
|
|
564
|
+
outcome: 'correct',
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
});
|
|
568
|
+
expect(diagnostics.summary.updatedCount).toBe(2);
|
|
569
|
+
expect(diagnostics.updatedStates[0].reviewCount).toBeGreaterThan(0);
|
|
570
|
+
expect(diagnostics.updatedStates[0].errorTagStats.length).toBeGreaterThan(0);
|
|
571
|
+
const pathResult = await platform.buildLearningPath({
|
|
572
|
+
userId: 'user_a',
|
|
573
|
+
focusAtomIds: [atomId],
|
|
574
|
+
maxMasteryPaths: 2,
|
|
575
|
+
maxDivergencePaths: 2,
|
|
576
|
+
});
|
|
577
|
+
expect(pathResult.masteryPaths.length).toBeGreaterThan(0);
|
|
578
|
+
expect(pathResult.recommendedActions.length).toBeGreaterThan(0);
|
|
579
|
+
});
|
|
580
|
+
test('misconception query aggregates recurring error tags and guides path prioritization', async () => {
|
|
581
|
+
const ingest = await platform.ingestKnowledge({
|
|
582
|
+
incremental: true,
|
|
583
|
+
documents: [
|
|
584
|
+
{
|
|
585
|
+
documentId: 'doc_misconception_a',
|
|
586
|
+
sourcePath: 'Knowledge_Base/doc_misconception_a.md',
|
|
587
|
+
language: 'en',
|
|
588
|
+
content: '# Retrieval Stability\nPractice retrieval under varied prompts.',
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
documentId: 'doc_misconception_b',
|
|
592
|
+
sourcePath: 'Knowledge_Base/doc_misconception_b.md',
|
|
593
|
+
language: 'en',
|
|
594
|
+
content: '# Transfer Bridge\nApply the same concept in new domains.',
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
});
|
|
598
|
+
const atomA = ingest.atoms[0]?.id;
|
|
599
|
+
const atomB = ingest.atoms[1]?.id;
|
|
600
|
+
expect(atomA).toBeDefined();
|
|
601
|
+
expect(atomB).toBeDefined();
|
|
602
|
+
await platform.diagnoseMastery({
|
|
603
|
+
userId: 'user_misconception',
|
|
604
|
+
observations: [
|
|
605
|
+
{ atomId: atomA, outcome: 'incorrect', errorTag: 'retrieval_failure' },
|
|
606
|
+
{ atomId: atomA, outcome: 'incorrect', errorTags: ['retrieval_failure', 'evidence_mismatch'] },
|
|
607
|
+
{ atomId: atomA, outcome: 'partial', errorTag: 'retrieval_failure' },
|
|
608
|
+
{ atomId: atomB, outcome: 'correct' },
|
|
609
|
+
],
|
|
610
|
+
});
|
|
611
|
+
const misconception = await platform.queryMasteryMisconceptions({
|
|
612
|
+
userId: 'user_misconception',
|
|
613
|
+
topK: 5,
|
|
614
|
+
});
|
|
615
|
+
expect(misconception.summary.totalObservations).toBeGreaterThanOrEqual(3);
|
|
616
|
+
expect(misconception.items.length).toBeGreaterThan(0);
|
|
617
|
+
expect(misconception.items[0].errorTag).toBe('retrieval_failure');
|
|
618
|
+
expect(misconception.items[0].recommendedActionKinds).toContain('quiz');
|
|
619
|
+
expect(misconception.items[0].severityScore).toBeGreaterThan(0);
|
|
620
|
+
const pathResult = await platform.buildLearningPath({
|
|
621
|
+
userId: 'user_misconception',
|
|
622
|
+
focusAtomIds: [atomA, atomB],
|
|
623
|
+
maxMasteryPaths: 2,
|
|
624
|
+
maxDivergencePaths: 1,
|
|
625
|
+
});
|
|
626
|
+
expect(pathResult.masteryPaths.length).toBeGreaterThan(0);
|
|
627
|
+
expect(pathResult.masteryPaths[0]?.targetAtomId).toBe(atomA);
|
|
628
|
+
expect(pathResult.masteryPaths[0]?.actions.some((action) => action.kind === 'quiz')).toBe(true);
|
|
629
|
+
});
|
|
630
|
+
test('study session plan orchestrates misconception remediation, retrain, and mastery actions', async () => {
|
|
631
|
+
const ingest = await platform.ingestKnowledge({
|
|
632
|
+
incremental: true,
|
|
633
|
+
documents: [
|
|
634
|
+
{
|
|
635
|
+
documentId: 'doc_session_a',
|
|
636
|
+
sourcePath: 'Knowledge_Base/doc_session_a.md',
|
|
637
|
+
language: 'en',
|
|
638
|
+
content: '# Session A\nFocus on retrieval and evidence alignment.',
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
documentId: 'doc_session_b',
|
|
642
|
+
sourcePath: 'Knowledge_Base/doc_session_b.md',
|
|
643
|
+
language: 'en',
|
|
644
|
+
content: '# Session B\nCross-apply the core concept.',
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
});
|
|
648
|
+
const atomA = ingest.atoms[0]?.id;
|
|
649
|
+
const atomB = ingest.atoms[1]?.id;
|
|
650
|
+
await platform.diagnoseMastery({
|
|
651
|
+
userId: 'user_session',
|
|
652
|
+
observedAt: '2026-03-31T08:00:00.000Z',
|
|
653
|
+
observations: [
|
|
654
|
+
{ atomId: atomA, outcome: 'incorrect', errorTag: 'retrieval_failure' },
|
|
655
|
+
{ atomId: atomA, outcome: 'incorrect', errorTag: 'evidence_mismatch' },
|
|
656
|
+
{ atomId: atomB, outcome: 'partial', errorTag: 'transfer_failure' },
|
|
657
|
+
],
|
|
658
|
+
});
|
|
659
|
+
const session = await platform.buildStudySession({
|
|
660
|
+
userId: 'user_session',
|
|
661
|
+
focusAtomIds: [atomA, atomB],
|
|
662
|
+
includeDivergence: false,
|
|
663
|
+
includeRetrain: true,
|
|
664
|
+
maxActions: 8,
|
|
665
|
+
generatedAt: '2026-04-20T00:00:00.000Z',
|
|
666
|
+
});
|
|
667
|
+
expect(session.actions.length).toBeGreaterThan(0);
|
|
668
|
+
expect(session.signals.misconceptions.length).toBeGreaterThan(0);
|
|
669
|
+
expect(session.signals.misconceptions[0]?.errorTag).toBe('retrieval_failure');
|
|
670
|
+
expect(session.signals.dueRetrainAtoms).toContain(atomA);
|
|
671
|
+
expect(session.signals.divergenceTargets.length).toBe(0);
|
|
672
|
+
expect(session.actions.some((action) => action.source === 'misconception_remediation')).toBe(true);
|
|
673
|
+
expect(session.actions.some((action) => action.source === 'retrain_plan')).toBe(true);
|
|
674
|
+
expect(session.summary.totalActions).toBe(session.actions.length);
|
|
675
|
+
expect(session.summary.evidenceCoverageRatio).toBeGreaterThan(0);
|
|
676
|
+
});
|
|
677
|
+
test('session action execution orchestrates tutor, memory persistence, and mastery update', async () => {
|
|
678
|
+
const ingest = await platform.ingestKnowledge({
|
|
679
|
+
incremental: true,
|
|
680
|
+
documents: [
|
|
681
|
+
{
|
|
682
|
+
documentId: 'doc_session_action',
|
|
683
|
+
sourcePath: 'Knowledge_Base/doc_session_action.md',
|
|
684
|
+
language: 'en',
|
|
685
|
+
content: '# Session Action\nExecute learning actions as a closed-loop operation.',
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
});
|
|
689
|
+
const atomId = ingest.atoms[0]?.id;
|
|
690
|
+
const execution = await platform.executeStudySessionAction({
|
|
691
|
+
userId: 'user_session_action',
|
|
692
|
+
action: {
|
|
693
|
+
atomId,
|
|
694
|
+
kind: 'quiz',
|
|
695
|
+
source: 'mastery_path',
|
|
696
|
+
},
|
|
697
|
+
outcome: 'incorrect',
|
|
698
|
+
errorTag: 'retrieval_failure',
|
|
699
|
+
persistMemory: true,
|
|
700
|
+
memoryLayer: 'session',
|
|
701
|
+
});
|
|
702
|
+
expect(execution.trace.tutorActionKind).toBe('generate_quiz');
|
|
703
|
+
expect(execution.trace.persistedMemory).toBe(true);
|
|
704
|
+
expect(execution.trace.analyzedAnswer).toBe(false);
|
|
705
|
+
expect(execution.trace.masterySource).toBe('explicit');
|
|
706
|
+
expect(execution.trace.effectiveOutcome).toBe('incorrect');
|
|
707
|
+
expect(execution.trace.effectiveErrorTag).toBe('retrieval_failure');
|
|
708
|
+
expect(execution.tutor.message).toContain('Question:');
|
|
709
|
+
expect(execution.answerAnalysis).toBeNull();
|
|
710
|
+
expect(execution.memory).not.toBeNull();
|
|
711
|
+
expect(execution.memory?.stats.session).toBeGreaterThan(0);
|
|
712
|
+
expect(execution.mastery).not.toBeNull();
|
|
713
|
+
expect(execution.mastery?.summary.updatedCount).toBe(1);
|
|
714
|
+
const stateAfterExecution = platform.getKnowledgeState();
|
|
715
|
+
expect(stateAfterExecution.sessionActionTelemetry.executionCount).toBe(1);
|
|
716
|
+
expect(stateAfterExecution.sessionActionTelemetry.explicitMasteryUpdateCount).toBe(1);
|
|
717
|
+
expect(stateAfterExecution.sessionActionTelemetry.inferredMasteryUpdateCount).toBe(0);
|
|
718
|
+
expect(stateAfterExecution.sessionActionTelemetry.outcomeCounts.incorrect).toBe(1);
|
|
719
|
+
const memoryRead = await platform.applyMemoryPolicy({
|
|
720
|
+
userId: 'user_session_action',
|
|
721
|
+
layer: 'session',
|
|
722
|
+
operation: 'read',
|
|
723
|
+
query: 'session_action',
|
|
724
|
+
limit: 5,
|
|
725
|
+
});
|
|
726
|
+
expect(memoryRead.entries.length).toBeGreaterThan(0);
|
|
727
|
+
const misconceptions = await platform.queryMasteryMisconceptions({
|
|
728
|
+
userId: 'user_session_action',
|
|
729
|
+
topK: 5,
|
|
730
|
+
});
|
|
731
|
+
expect(misconceptions.items.some((item) => item.errorTag === 'retrieval_failure')).toBe(true);
|
|
732
|
+
});
|
|
733
|
+
test('session action execution auto-analyzes answers and infers mastery diagnostics', async () => {
|
|
734
|
+
const ingest = await platform.ingestKnowledge({
|
|
735
|
+
incremental: true,
|
|
736
|
+
documents: [
|
|
737
|
+
{
|
|
738
|
+
documentId: 'doc_session_action_auto',
|
|
739
|
+
sourcePath: 'Knowledge_Base/doc_session_action_auto.md',
|
|
740
|
+
language: 'en',
|
|
741
|
+
content: '# Session Action Auto\nAuto diagnostics should infer outcome from answer quality.',
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
});
|
|
745
|
+
const atomId = ingest.atoms[0]?.id;
|
|
746
|
+
const execution = await platform.executeStudySessionAction({
|
|
747
|
+
userId: 'user_session_action_auto',
|
|
748
|
+
action: {
|
|
749
|
+
atomId,
|
|
750
|
+
kind: 'quiz',
|
|
751
|
+
source: 'mastery_path',
|
|
752
|
+
answer: 'xylophone quasar nebula',
|
|
753
|
+
},
|
|
754
|
+
persistMemory: true,
|
|
755
|
+
memoryLayer: 'session',
|
|
756
|
+
});
|
|
757
|
+
expect(execution.trace.tutorActionKind).toBe('generate_quiz');
|
|
758
|
+
expect(execution.trace.analyzedAnswer).toBe(true);
|
|
759
|
+
expect(execution.trace.masterySource).toBe('inferred');
|
|
760
|
+
expect(execution.trace.effectiveOutcome).toBe('incorrect');
|
|
761
|
+
expect(execution.trace.effectiveErrorTag).toBe('retrieval_failure');
|
|
762
|
+
expect(execution.answerAnalysis).not.toBeNull();
|
|
763
|
+
expect(execution.answerAnalysis?.trace.actionKind).toBe('analyze_answer');
|
|
764
|
+
expect(execution.mastery).not.toBeNull();
|
|
765
|
+
expect(execution.mastery?.summary.updatedCount).toBe(1);
|
|
766
|
+
const stateAfterExecution = platform.getKnowledgeState();
|
|
767
|
+
expect(stateAfterExecution.sessionActionTelemetry.executionCount).toBe(1);
|
|
768
|
+
expect(stateAfterExecution.sessionActionTelemetry.analyzedAnswerCount).toBe(1);
|
|
769
|
+
expect(stateAfterExecution.sessionActionTelemetry.inferredMasteryUpdateCount).toBe(1);
|
|
770
|
+
expect(stateAfterExecution.sessionActionTelemetry.explicitMasteryUpdateCount).toBe(0);
|
|
771
|
+
expect(stateAfterExecution.sessionActionTelemetry.outcomeCounts.incorrect).toBe(1);
|
|
772
|
+
});
|
|
773
|
+
test('session plan execution runs top actions and returns aggregate execution summary', async () => {
|
|
774
|
+
const ingest = await platform.ingestKnowledge({
|
|
775
|
+
incremental: true,
|
|
776
|
+
documents: [
|
|
777
|
+
{
|
|
778
|
+
documentId: 'doc_session_exec_a',
|
|
779
|
+
sourcePath: 'Knowledge_Base/doc_session_exec_a.md',
|
|
780
|
+
language: 'en',
|
|
781
|
+
content: '# Session Execute A\nPractice evidence-backed retrieval for core concept A.',
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
documentId: 'doc_session_exec_b',
|
|
785
|
+
sourcePath: 'Knowledge_Base/doc_session_exec_b.md',
|
|
786
|
+
language: 'en',
|
|
787
|
+
content: '# Session Execute B\nApply concept B to transfer scenarios.',
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
});
|
|
791
|
+
const focusAtomIds = ingest.atoms.map((item) => item.id);
|
|
792
|
+
const sessionPlan = await platform.buildStudySession({
|
|
793
|
+
userId: 'user_session_execute',
|
|
794
|
+
focusAtomIds,
|
|
795
|
+
maxActions: 8,
|
|
796
|
+
includeDivergence: true,
|
|
797
|
+
includeRetrain: true,
|
|
798
|
+
});
|
|
799
|
+
expect(sessionPlan.actions.length).toBeGreaterThan(0);
|
|
800
|
+
const execution = await platform.executeStudySessionPlan({
|
|
801
|
+
userId: 'user_session_execute',
|
|
802
|
+
sessionPlan,
|
|
803
|
+
executionKind: 'session',
|
|
804
|
+
actionLimit: 3,
|
|
805
|
+
includeRetestPlan: false,
|
|
806
|
+
persistMemory: true,
|
|
807
|
+
memoryLayer: 'session',
|
|
808
|
+
});
|
|
809
|
+
expect(execution.items.length).toBe(3);
|
|
810
|
+
expect(execution.summary.plannedActions).toBe(sessionPlan.actions.length);
|
|
811
|
+
expect(execution.summary.attemptedActions).toBe(3);
|
|
812
|
+
expect(execution.summary.executedCount).toBe(3);
|
|
813
|
+
expect(execution.summary.failedCount).toBe(0);
|
|
814
|
+
expect(execution.summary.skippedCount).toBe(0);
|
|
815
|
+
expect(execution.summary.stoppedEarly).toBe(false);
|
|
816
|
+
expect(execution.summary.averageTutorConfidence).toBeGreaterThan(0);
|
|
817
|
+
expect(execution.summary.averageMasteryBefore).toBeGreaterThanOrEqual(0);
|
|
818
|
+
expect(execution.summary.averageMasteryAfter).toBeGreaterThanOrEqual(0);
|
|
819
|
+
expect(execution.masteryDelta.comparedAtoms).toBeGreaterThan(0);
|
|
820
|
+
expect(execution.masteryDelta.items.length).toBe(execution.masteryDelta.comparedAtoms);
|
|
821
|
+
expect(execution.retestPlan.summary.totalActions).toBe(0);
|
|
822
|
+
expect(execution.record.userId).toBe('user_session_execute');
|
|
823
|
+
expect(execution.record.executionKind).toBe('session');
|
|
824
|
+
expect(execution.record.focusAtomIds.length).toBeGreaterThan(0);
|
|
825
|
+
const history = await platform.queryStudySessionHistory({
|
|
826
|
+
userId: 'user_session_execute',
|
|
827
|
+
limit: 5,
|
|
828
|
+
});
|
|
829
|
+
expect(history.records.length).toBeGreaterThanOrEqual(1);
|
|
830
|
+
expect(history.records[0]?.id).toBe(execution.record.id);
|
|
831
|
+
expect(history.summary.totalExecutedActions).toBeGreaterThan(0);
|
|
832
|
+
const stateAfterExecution = platform.getKnowledgeState();
|
|
833
|
+
expect(stateAfterExecution.sessionActionTelemetry.executionCount).toBeGreaterThanOrEqual(3);
|
|
834
|
+
expect(stateAfterExecution.sessionExecutionHistoryRecords).toBeGreaterThanOrEqual(1);
|
|
835
|
+
});
|
|
836
|
+
test('session plan execution consumes answersByActionId for auto analysis and inferred mastery updates', async () => {
|
|
837
|
+
const ingest = await platform.ingestKnowledge({
|
|
838
|
+
incremental: true,
|
|
839
|
+
documents: [
|
|
840
|
+
{
|
|
841
|
+
documentId: 'doc_session_exec_answers',
|
|
842
|
+
sourcePath: 'Knowledge_Base/doc_session_exec_answers.md',
|
|
843
|
+
language: 'en',
|
|
844
|
+
content: '# Session Execute Answers\nSupport answer-driven diagnostics during batch execution.',
|
|
845
|
+
},
|
|
846
|
+
],
|
|
847
|
+
});
|
|
848
|
+
const focusAtomIds = ingest.atoms.map((item) => item.id);
|
|
849
|
+
const sessionPlan = await platform.buildStudySession({
|
|
850
|
+
userId: 'user_session_exec_answers',
|
|
851
|
+
focusAtomIds,
|
|
852
|
+
maxActions: 4,
|
|
853
|
+
includeDivergence: false,
|
|
854
|
+
includeRetrain: false,
|
|
855
|
+
});
|
|
856
|
+
expect(sessionPlan.actions.length).toBeGreaterThan(0);
|
|
857
|
+
const firstAction = sessionPlan.actions[0];
|
|
858
|
+
expect(firstAction).toBeDefined();
|
|
859
|
+
const execution = await platform.executeStudySessionPlan({
|
|
860
|
+
userId: 'user_session_exec_answers',
|
|
861
|
+
sessionPlan: {
|
|
862
|
+
...sessionPlan,
|
|
863
|
+
actions: [firstAction],
|
|
864
|
+
},
|
|
865
|
+
executionKind: 'retest',
|
|
866
|
+
actionLimit: 1,
|
|
867
|
+
answersByActionId: {
|
|
868
|
+
[firstAction.id]: 'xylophone quasar nebula',
|
|
869
|
+
},
|
|
870
|
+
autoAnalyzeAnswer: true,
|
|
871
|
+
autoUpdateMasteryFromAnswer: true,
|
|
872
|
+
persistMemory: true,
|
|
873
|
+
memoryLayer: 'session',
|
|
874
|
+
});
|
|
875
|
+
expect(execution.summary.executedCount).toBe(1);
|
|
876
|
+
expect(execution.summary.analyzedAnswerCount).toBe(1);
|
|
877
|
+
expect(execution.summary.inferredMasteryCount).toBe(1);
|
|
878
|
+
expect(execution.summary.averageMasteryDelta).toBeLessThan(0);
|
|
879
|
+
expect(execution.masteryDelta.regressedCount).toBeGreaterThanOrEqual(1);
|
|
880
|
+
const firstItem = execution.items[0];
|
|
881
|
+
expect(firstItem?.status).toBe('executed');
|
|
882
|
+
expect(firstItem?.result?.answerAnalysis).not.toBeNull();
|
|
883
|
+
expect(firstItem?.result?.trace.masterySource).toBe('inferred');
|
|
884
|
+
expect(execution.masteryDelta.items[0]?.updatedByExecution).toBe(true);
|
|
885
|
+
expect(execution.retestPlan.summary.totalActions).toBeGreaterThanOrEqual(1);
|
|
886
|
+
expect(execution.retestPlan.actions[0]?.source).toBe('retrain_plan');
|
|
887
|
+
expect(execution.record.executionKind).toBe('retest');
|
|
888
|
+
expect(execution.record.focusAtomIds.length).toBeGreaterThan(0);
|
|
889
|
+
});
|
|
890
|
+
test('session history supports execution-kind filtering, time windows, and pagination', async () => {
|
|
891
|
+
const ingest = await platform.ingestKnowledge({
|
|
892
|
+
incremental: true,
|
|
893
|
+
documents: [
|
|
894
|
+
{
|
|
895
|
+
documentId: 'doc_session_history_query',
|
|
896
|
+
sourcePath: 'Knowledge_Base/doc_session_history_query.md',
|
|
897
|
+
language: 'en',
|
|
898
|
+
content: '# Session History Query\nFilter and paginate execution records.',
|
|
899
|
+
},
|
|
900
|
+
],
|
|
901
|
+
});
|
|
902
|
+
const focusAtomIds = ingest.atoms.map((item) => item.id);
|
|
903
|
+
const sessionPlan = await platform.buildStudySession({
|
|
904
|
+
userId: 'user_session_history_query',
|
|
905
|
+
focusAtomIds,
|
|
906
|
+
maxActions: 3,
|
|
907
|
+
includeDivergence: false,
|
|
908
|
+
includeRetrain: false,
|
|
909
|
+
});
|
|
910
|
+
await platform.executeStudySessionPlan({
|
|
911
|
+
userId: 'user_session_history_query',
|
|
912
|
+
executionKind: 'session',
|
|
913
|
+
sessionPlan,
|
|
914
|
+
actionLimit: 1,
|
|
915
|
+
includeRetestPlan: false,
|
|
916
|
+
persistMemory: false,
|
|
917
|
+
executedAt: '2026-04-01T08:00:00.000Z',
|
|
918
|
+
});
|
|
919
|
+
await platform.executeStudySessionPlan({
|
|
920
|
+
userId: 'user_session_history_query',
|
|
921
|
+
executionKind: 'retest',
|
|
922
|
+
sessionPlan,
|
|
923
|
+
actionLimit: 1,
|
|
924
|
+
includeRetestPlan: false,
|
|
925
|
+
persistMemory: false,
|
|
926
|
+
executedAt: '2026-04-02T08:00:00.000Z',
|
|
927
|
+
});
|
|
928
|
+
await platform.executeStudySessionPlan({
|
|
929
|
+
userId: 'user_session_history_query',
|
|
930
|
+
executionKind: 'custom',
|
|
931
|
+
sessionPlan,
|
|
932
|
+
actionLimit: 1,
|
|
933
|
+
includeRetestPlan: false,
|
|
934
|
+
persistMemory: false,
|
|
935
|
+
executedAt: '2026-04-03T08:00:00.000Z',
|
|
936
|
+
});
|
|
937
|
+
const paged = await platform.queryStudySessionHistory({
|
|
938
|
+
userId: 'user_session_history_query',
|
|
939
|
+
limit: 1,
|
|
940
|
+
offset: 1,
|
|
941
|
+
});
|
|
942
|
+
expect(paged.records.length).toBe(1);
|
|
943
|
+
expect(paged.records[0]?.executionKind).toBe('retest');
|
|
944
|
+
expect(paged.records[0]?.focusAtomIds.length).toBeGreaterThan(0);
|
|
945
|
+
expect(paged.page.limit).toBe(1);
|
|
946
|
+
expect(paged.page.offset).toBe(1);
|
|
947
|
+
expect(paged.page.totalFilteredRecords).toBe(3);
|
|
948
|
+
expect(paged.page.hasMore).toBe(true);
|
|
949
|
+
expect(paged.page.nextOffset).toBe(2);
|
|
950
|
+
expect(paged.summary.totalRecords).toBe(3);
|
|
951
|
+
expect(paged.summary.totalExecutedActions).toBeGreaterThanOrEqual(3);
|
|
952
|
+
const retestOnly = await platform.queryStudySessionHistory({
|
|
953
|
+
userId: 'user_session_history_query',
|
|
954
|
+
executionKinds: ['retest'],
|
|
955
|
+
limit: 5,
|
|
956
|
+
});
|
|
957
|
+
expect(retestOnly.records.length).toBe(1);
|
|
958
|
+
expect(retestOnly.records[0]?.executionKind).toBe('retest');
|
|
959
|
+
expect(retestOnly.summary.totalRecords).toBe(1);
|
|
960
|
+
expect(retestOnly.page.totalFilteredRecords).toBe(1);
|
|
961
|
+
expect(retestOnly.summary.executionKindBreakdown.find((item) => item.executionKind === 'retest')?.recordCount).toBe(1);
|
|
962
|
+
expect(retestOnly.summary.executionKindBreakdown.find((item) => item.executionKind === 'session')?.recordCount).toBe(0);
|
|
963
|
+
const rangeFiltered = await platform.queryStudySessionHistory({
|
|
964
|
+
userId: 'user_session_history_query',
|
|
965
|
+
fromExecutedAt: '2026-04-02T00:00:00.000Z',
|
|
966
|
+
toExecutedAt: '2026-04-03T23:59:59.000Z',
|
|
967
|
+
limit: 10,
|
|
968
|
+
});
|
|
969
|
+
expect(rangeFiltered.records.length).toBe(2);
|
|
970
|
+
expect(rangeFiltered.summary.totalRecords).toBe(2);
|
|
971
|
+
const reversedRange = await platform.queryStudySessionHistory({
|
|
972
|
+
userId: 'user_session_history_query',
|
|
973
|
+
fromExecutedAt: '2026-04-03T23:59:59.000Z',
|
|
974
|
+
toExecutedAt: '2026-04-02T00:00:00.000Z',
|
|
975
|
+
limit: 10,
|
|
976
|
+
});
|
|
977
|
+
expect(reversedRange.summary.totalRecords).toBe(2);
|
|
978
|
+
});
|
|
979
|
+
test('learning quality evaluation enforces mastery and evidence thresholds', async () => {
|
|
980
|
+
await platform.ingestKnowledge({
|
|
981
|
+
incremental: true,
|
|
982
|
+
documents: [
|
|
983
|
+
{
|
|
984
|
+
documentId: 'doc_eval',
|
|
985
|
+
sourcePath: 'Knowledge_Base/doc_eval.md',
|
|
986
|
+
language: 'en',
|
|
987
|
+
content: '# Eval\nQuality gates require evidence and mastery uplift.',
|
|
988
|
+
},
|
|
989
|
+
],
|
|
990
|
+
});
|
|
991
|
+
await platform.queryKnowledge({ query: 'quality gates evidence mastery', topK: 3 });
|
|
992
|
+
await platform.queryKnowledge({ query: 'quality gates evidence mastery', topK: 3 });
|
|
993
|
+
const passResult = await platform.evaluateLearningQuality({
|
|
994
|
+
baseline: {
|
|
995
|
+
retestPassRatePct: 50,
|
|
996
|
+
misconceptionRecurrenceRatePct: 40,
|
|
997
|
+
evidenceBackedSuggestionRatioPct: 80,
|
|
998
|
+
averagePathMasteryGainPct: 22,
|
|
999
|
+
randomPathMasteryGainPct: 12,
|
|
1000
|
+
historyWindowAverageMasteryDelta: 0.01,
|
|
1001
|
+
},
|
|
1002
|
+
current: {
|
|
1003
|
+
retestPassRatePct: 74,
|
|
1004
|
+
misconceptionRecurrenceRatePct: 12,
|
|
1005
|
+
evidenceBackedSuggestionRatioPct: 93,
|
|
1006
|
+
averagePathMasteryGainPct: 28,
|
|
1007
|
+
randomPathMasteryGainPct: 18,
|
|
1008
|
+
historyWindowAverageMasteryDelta: 0.04,
|
|
1009
|
+
},
|
|
1010
|
+
});
|
|
1011
|
+
expect(passResult.overallPassed).toBe(true);
|
|
1012
|
+
expect(passResult.deltas.retestPassRateUpliftPct).toBeGreaterThanOrEqual(20);
|
|
1013
|
+
expect(passResult.deltas.misconceptionRecurrenceReductionPct).toBeGreaterThanOrEqual(25);
|
|
1014
|
+
expect(passResult.deltas.historyWindowAverageMasteryDeltaUplift).toBeGreaterThan(0);
|
|
1015
|
+
expect(passResult.gates.find((gate) => gate.gateId === 'evidence_ratio')?.passed).toBe(true);
|
|
1016
|
+
expect(passResult.gates.find((gate) => gate.gateId === 'history_mastery_delta_uplift')?.passed).toBe(true);
|
|
1017
|
+
const failResult = await platform.evaluateLearningQuality({
|
|
1018
|
+
baseline: {
|
|
1019
|
+
retestPassRatePct: 65,
|
|
1020
|
+
misconceptionRecurrenceRatePct: 30,
|
|
1021
|
+
evidenceBackedSuggestionRatioPct: 92,
|
|
1022
|
+
averagePathMasteryGainPct: 21,
|
|
1023
|
+
randomPathMasteryGainPct: 17,
|
|
1024
|
+
historyWindowAverageMasteryDelta: 0.03,
|
|
1025
|
+
},
|
|
1026
|
+
current: {
|
|
1027
|
+
retestPassRatePct: 70,
|
|
1028
|
+
misconceptionRecurrenceRatePct: 28,
|
|
1029
|
+
evidenceBackedSuggestionRatioPct: 85,
|
|
1030
|
+
averagePathMasteryGainPct: 18,
|
|
1031
|
+
randomPathMasteryGainPct: 17,
|
|
1032
|
+
historyWindowAverageMasteryDelta: 0.01,
|
|
1033
|
+
},
|
|
1034
|
+
});
|
|
1035
|
+
expect(failResult.overallPassed).toBe(false);
|
|
1036
|
+
expect(failResult.gates.some((gate) => gate.passed === false)).toBe(true);
|
|
1037
|
+
expect(failResult.gates.find((gate) => gate.gateId === 'history_mastery_delta_uplift')?.passed).toBe(false);
|
|
1038
|
+
});
|
|
1039
|
+
test('quality snapshot captures runtime learning metrics for governance', async () => {
|
|
1040
|
+
const ingest = await platform.ingestKnowledge({
|
|
1041
|
+
incremental: true,
|
|
1042
|
+
documents: [
|
|
1043
|
+
{
|
|
1044
|
+
documentId: 'doc_quality_snapshot',
|
|
1045
|
+
sourcePath: 'Knowledge_Base/doc_quality_snapshot.md',
|
|
1046
|
+
language: 'en',
|
|
1047
|
+
content: '# Snapshot\nQuality metrics should reflect runtime evidence and mastery.',
|
|
1048
|
+
},
|
|
1049
|
+
],
|
|
1050
|
+
});
|
|
1051
|
+
const atomId = ingest.atoms[0]?.id;
|
|
1052
|
+
await platform.diagnoseMastery({
|
|
1053
|
+
userId: 'user_quality_snapshot',
|
|
1054
|
+
observations: [
|
|
1055
|
+
{ atomId, outcome: 'correct' },
|
|
1056
|
+
{ atomId, outcome: 'incorrect', errorTag: 'retrieval_failure' },
|
|
1057
|
+
],
|
|
1058
|
+
});
|
|
1059
|
+
await platform.queryKnowledge({
|
|
1060
|
+
query: 'quality snapshot runtime',
|
|
1061
|
+
topK: 2,
|
|
1062
|
+
});
|
|
1063
|
+
await platform.executeTutorAction({
|
|
1064
|
+
userId: 'user_quality_snapshot',
|
|
1065
|
+
actionKind: 'recap',
|
|
1066
|
+
atomId,
|
|
1067
|
+
});
|
|
1068
|
+
const sessionPlan = await platform.buildStudySession({
|
|
1069
|
+
userId: 'user_quality_snapshot',
|
|
1070
|
+
focusAtomIds: [atomId],
|
|
1071
|
+
maxActions: 3,
|
|
1072
|
+
includeDivergence: false,
|
|
1073
|
+
includeRetrain: true,
|
|
1074
|
+
});
|
|
1075
|
+
await platform.executeStudySessionPlan({
|
|
1076
|
+
userId: 'user_quality_snapshot',
|
|
1077
|
+
executionKind: 'session',
|
|
1078
|
+
sessionPlan,
|
|
1079
|
+
actionLimit: 1,
|
|
1080
|
+
includeRetestPlan: false,
|
|
1081
|
+
persistMemory: false,
|
|
1082
|
+
executedAt: '2026-04-02T09:30:00.000Z',
|
|
1083
|
+
});
|
|
1084
|
+
await platform.executeStudySessionPlan({
|
|
1085
|
+
userId: 'user_quality_snapshot',
|
|
1086
|
+
executionKind: 'retest',
|
|
1087
|
+
sessionPlan,
|
|
1088
|
+
actionLimit: 1,
|
|
1089
|
+
includeRetestPlan: false,
|
|
1090
|
+
persistMemory: false,
|
|
1091
|
+
executedAt: '2026-04-03T09:30:00.000Z',
|
|
1092
|
+
});
|
|
1093
|
+
const snapshotResult = await platform.captureLearningQualitySnapshot({
|
|
1094
|
+
userId: 'user_quality_snapshot',
|
|
1095
|
+
sampledAt: '2026-04-05T09:30:00.000Z',
|
|
1096
|
+
historyWindowDays: 14,
|
|
1097
|
+
});
|
|
1098
|
+
expect(snapshotResult.snapshot.retestPassRatePct).toBeGreaterThanOrEqual(0);
|
|
1099
|
+
expect(snapshotResult.snapshot.retestPassRatePct).toBeLessThanOrEqual(100);
|
|
1100
|
+
expect(snapshotResult.snapshot.misconceptionRecurrenceRatePct).toBeGreaterThanOrEqual(0);
|
|
1101
|
+
expect(snapshotResult.snapshot.evidenceBackedSuggestionRatioPct).toBeGreaterThanOrEqual(0);
|
|
1102
|
+
expect(snapshotResult.snapshot.averagePathMasteryGainPct).toBeGreaterThanOrEqual(0);
|
|
1103
|
+
expect(snapshotResult.snapshot.historyWindowDays).toBe(14);
|
|
1104
|
+
expect(snapshotResult.snapshot.historyWindowRecords).toBeGreaterThan(0);
|
|
1105
|
+
expect(snapshotResult.snapshot.historyWindowAverageMasteryDelta).toBeGreaterThanOrEqual(-1);
|
|
1106
|
+
expect(snapshotResult.snapshot.historyWindowAverageMasteryDelta).toBeLessThanOrEqual(1);
|
|
1107
|
+
expect(snapshotResult.snapshot.historyWindowRetestPositiveDeltaRatePct).toBeGreaterThanOrEqual(0);
|
|
1108
|
+
expect(snapshotResult.snapshot.historyWindowRetestPositiveDeltaRatePct).toBeLessThanOrEqual(100);
|
|
1109
|
+
expect(snapshotResult.snapshot.queryP95Ms).toBeGreaterThanOrEqual(0);
|
|
1110
|
+
expect(snapshotResult.diagnostics.learnerStates).toBeGreaterThan(0);
|
|
1111
|
+
expect(snapshotResult.diagnostics.totalTutorTraces).toBeGreaterThan(0);
|
|
1112
|
+
expect(snapshotResult.diagnostics.historyWindowRecords).toBeGreaterThan(0);
|
|
1113
|
+
expect(snapshotResult.diagnostics.historyWindowRetestRecords).toBeGreaterThanOrEqual(0);
|
|
1114
|
+
});
|
|
1115
|
+
test('learning quality baseline APIs support get/set/clear lifecycle', async () => {
|
|
1116
|
+
const initialBaseline = await platform.getLearningQualityBaseline({
|
|
1117
|
+
userId: 'baseline_user_a',
|
|
1118
|
+
});
|
|
1119
|
+
expect(initialBaseline.found).toBe(false);
|
|
1120
|
+
expect(initialBaseline.storedAt).toBeNull();
|
|
1121
|
+
expect(initialBaseline.snapshot).toBeNull();
|
|
1122
|
+
const setBaseline = await platform.setLearningQualityBaseline({
|
|
1123
|
+
userId: 'baseline_user_a',
|
|
1124
|
+
storedAt: '2026-05-10T10:00:00.000Z',
|
|
1125
|
+
snapshot: {
|
|
1126
|
+
retestPassRatePct: 72,
|
|
1127
|
+
misconceptionRecurrenceRatePct: 18,
|
|
1128
|
+
evidenceBackedSuggestionRatioPct: 90,
|
|
1129
|
+
averagePathMasteryGainPct: 21,
|
|
1130
|
+
randomPathMasteryGainPct: 11,
|
|
1131
|
+
historyWindowDays: 14,
|
|
1132
|
+
historyWindowRecords: 8,
|
|
1133
|
+
historyWindowAverageMasteryDelta: 0.12,
|
|
1134
|
+
historyWindowRetestPositiveDeltaRatePct: 75,
|
|
1135
|
+
queryP95Ms: 120,
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
expect(setBaseline.found).toBe(true);
|
|
1139
|
+
expect(setBaseline.storedAt).toBe('2026-05-10T10:00:00.000Z');
|
|
1140
|
+
expect(setBaseline.snapshot?.retestPassRatePct).toBe(72);
|
|
1141
|
+
const loadedBaseline = await platform.getLearningQualityBaseline({
|
|
1142
|
+
userId: 'baseline_user_a',
|
|
1143
|
+
});
|
|
1144
|
+
expect(loadedBaseline.found).toBe(true);
|
|
1145
|
+
expect(loadedBaseline.storedAt).toBe('2026-05-10T10:00:00.000Z');
|
|
1146
|
+
expect(loadedBaseline.snapshot?.misconceptionRecurrenceRatePct).toBe(18);
|
|
1147
|
+
const clearedBaseline = await platform.clearLearningQualityBaseline({
|
|
1148
|
+
userId: 'baseline_user_a',
|
|
1149
|
+
});
|
|
1150
|
+
expect(clearedBaseline.found).toBe(false);
|
|
1151
|
+
expect(clearedBaseline.snapshot).toBeNull();
|
|
1152
|
+
expect(clearedBaseline.storedAt).toBeNull();
|
|
1153
|
+
});
|
|
1154
|
+
test('learning quality baseline evaluation uses stored baseline and current snapshot', async () => {
|
|
1155
|
+
await platform.setLearningQualityBaseline({
|
|
1156
|
+
userId: 'baseline_eval_user',
|
|
1157
|
+
storedAt: '2026-05-10T10:00:00.000Z',
|
|
1158
|
+
snapshot: {
|
|
1159
|
+
retestPassRatePct: 60,
|
|
1160
|
+
misconceptionRecurrenceRatePct: 40,
|
|
1161
|
+
evidenceBackedSuggestionRatioPct: 70,
|
|
1162
|
+
averagePathMasteryGainPct: 18,
|
|
1163
|
+
randomPathMasteryGainPct: 10,
|
|
1164
|
+
queryP95Ms: 250,
|
|
1165
|
+
},
|
|
1166
|
+
});
|
|
1167
|
+
const result = await platform.evaluateLearningQualityAgainstBaseline({
|
|
1168
|
+
userId: 'baseline_eval_user',
|
|
1169
|
+
current: {
|
|
1170
|
+
retestPassRatePct: 78,
|
|
1171
|
+
misconceptionRecurrenceRatePct: 22,
|
|
1172
|
+
evidenceBackedSuggestionRatioPct: 88,
|
|
1173
|
+
averagePathMasteryGainPct: 24,
|
|
1174
|
+
randomPathMasteryGainPct: 12,
|
|
1175
|
+
queryP95Ms: 180,
|
|
1176
|
+
},
|
|
1177
|
+
sampledAt: '2026-05-11T10:00:00.000Z',
|
|
1178
|
+
});
|
|
1179
|
+
expect(result.userId).toBe('baseline_eval_user');
|
|
1180
|
+
expect(result.baseline.found).toBe(true);
|
|
1181
|
+
expect(result.currentSnapshot.snapshot.retestPassRatePct).toBe(78);
|
|
1182
|
+
expect(result.evaluation.baseline.retestPassRatePct).toBe(60);
|
|
1183
|
+
expect(result.evaluation.current.retestPassRatePct).toBe(78);
|
|
1184
|
+
expect(typeof result.evaluation.overallPassed).toBe('boolean');
|
|
1185
|
+
});
|
|
1186
|
+
test('ingest guardrail evaluation enforces thresholds over latest ingest and telemetry', async () => {
|
|
1187
|
+
await platform.ingestKnowledge({
|
|
1188
|
+
incremental: true,
|
|
1189
|
+
relationRecomputeMode: 'full',
|
|
1190
|
+
documents: [
|
|
1191
|
+
{
|
|
1192
|
+
documentId: 'doc_guardrail_a',
|
|
1193
|
+
sourcePath: 'Knowledge_Base/doc_guardrail_a.md',
|
|
1194
|
+
language: 'en',
|
|
1195
|
+
content: '# Guardrail A\nIngest guardrails track changed docs and active atoms.',
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
documentId: 'doc_guardrail_b',
|
|
1199
|
+
sourcePath: 'Knowledge_Base/doc_guardrail_b.md',
|
|
1200
|
+
language: 'en',
|
|
1201
|
+
content: '# Guardrail B\nTelemetry should stay under threshold budgets.',
|
|
1202
|
+
},
|
|
1203
|
+
],
|
|
1204
|
+
});
|
|
1205
|
+
const passResult = await platform.evaluateIngestGuardrails({
|
|
1206
|
+
thresholds: {
|
|
1207
|
+
maxChangedDocuments: 10,
|
|
1208
|
+
maxDeletedDocuments: 5,
|
|
1209
|
+
maxActiveAtoms: 1000,
|
|
1210
|
+
maxIngestP95Ms: 60000,
|
|
1211
|
+
maxRecomputeP95Ms: 60000,
|
|
1212
|
+
},
|
|
1213
|
+
});
|
|
1214
|
+
expect(passResult.overallPassed).toBe(true);
|
|
1215
|
+
expect(passResult.latestSummary?.changedDocuments).toBe(2);
|
|
1216
|
+
expect(passResult.gates.find((gate) => gate.gateId === 'changed_documents')?.passed).toBe(true);
|
|
1217
|
+
const failResult = await platform.evaluateIngestGuardrails({
|
|
1218
|
+
thresholds: {
|
|
1219
|
+
maxChangedDocuments: 0,
|
|
1220
|
+
maxDeletedDocuments: 0,
|
|
1221
|
+
maxActiveAtoms: 1,
|
|
1222
|
+
maxIngestP95Ms: 1,
|
|
1223
|
+
maxRecomputeP95Ms: 1,
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1226
|
+
expect(failResult.overallPassed).toBe(false);
|
|
1227
|
+
expect(failResult.gates.find((gate) => gate.gateId === 'changed_documents')?.passed).toBe(false);
|
|
1228
|
+
});
|
|
1229
|
+
test('tutor actions and memory policy APIs are operational', async () => {
|
|
1230
|
+
const ingest = await platform.ingestKnowledge({
|
|
1231
|
+
incremental: true,
|
|
1232
|
+
documents: [
|
|
1233
|
+
{
|
|
1234
|
+
documentId: 'doc_tutor',
|
|
1235
|
+
sourcePath: 'Knowledge_Base/doc_tutor.md',
|
|
1236
|
+
language: 'en',
|
|
1237
|
+
content: '# Evidence First\nEvery tutor response must cite evidence spans.',
|
|
1238
|
+
},
|
|
1239
|
+
],
|
|
1240
|
+
});
|
|
1241
|
+
const atomId = ingest.atoms[0]?.id;
|
|
1242
|
+
const tutor = await platform.executeTutorAction({
|
|
1243
|
+
userId: 'user_tutor',
|
|
1244
|
+
actionKind: 'generate_quiz',
|
|
1245
|
+
atomId,
|
|
1246
|
+
});
|
|
1247
|
+
expect(tutor.message).toContain('Question:');
|
|
1248
|
+
expect(tutor.trace.source).toBe('rule-engine');
|
|
1249
|
+
expect(tutor.evidenceSpans.length).toBeGreaterThan(0);
|
|
1250
|
+
await platform.diagnoseMastery({
|
|
1251
|
+
userId: 'user_tutor',
|
|
1252
|
+
observations: [
|
|
1253
|
+
{
|
|
1254
|
+
atomId,
|
|
1255
|
+
outcome: 'incorrect',
|
|
1256
|
+
errorTag: 'retrieval_failure',
|
|
1257
|
+
},
|
|
1258
|
+
],
|
|
1259
|
+
observedAt: '2026-03-31T08:40:00.000Z',
|
|
1260
|
+
});
|
|
1261
|
+
const writeResult = await platform.applyMemoryPolicy({
|
|
1262
|
+
userId: 'user_tutor',
|
|
1263
|
+
layer: 'session',
|
|
1264
|
+
operation: 'write',
|
|
1265
|
+
entries: [
|
|
1266
|
+
{
|
|
1267
|
+
key: 'quiz-1',
|
|
1268
|
+
value: 'Learner confused evidence and inference.',
|
|
1269
|
+
tags: ['misconception'],
|
|
1270
|
+
confidence: 0.82,
|
|
1271
|
+
references: [atomId],
|
|
1272
|
+
createdAt: '2026-03-31T08:40:00.000Z',
|
|
1273
|
+
updatedAt: '2026-03-31T08:40:00.000Z',
|
|
1274
|
+
expiresAt: '2026-04-01T00:00:00.000Z',
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
key: 'expired-1',
|
|
1278
|
+
value: 'old note',
|
|
1279
|
+
tags: [],
|
|
1280
|
+
confidence: 0.2,
|
|
1281
|
+
references: [],
|
|
1282
|
+
createdAt: '2026-03-20T00:00:00.000Z',
|
|
1283
|
+
updatedAt: '2026-03-20T00:00:00.000Z',
|
|
1284
|
+
expiresAt: '2026-03-21T00:00:00.000Z',
|
|
1285
|
+
},
|
|
1286
|
+
],
|
|
1287
|
+
});
|
|
1288
|
+
expect(writeResult.entries.length).toBeGreaterThan(0);
|
|
1289
|
+
expect(writeResult.evictedCount).toBeGreaterThanOrEqual(1);
|
|
1290
|
+
const readResult = await platform.applyMemoryPolicy({
|
|
1291
|
+
userId: 'user_tutor',
|
|
1292
|
+
layer: 'session',
|
|
1293
|
+
operation: 'read',
|
|
1294
|
+
query: 'evidence',
|
|
1295
|
+
});
|
|
1296
|
+
expect(readResult.entries.length).toBeGreaterThan(0);
|
|
1297
|
+
const evictResult = await platform.applyMemoryPolicy({
|
|
1298
|
+
userId: 'user_tutor',
|
|
1299
|
+
layer: 'session',
|
|
1300
|
+
operation: 'evict',
|
|
1301
|
+
now: '2026-03-31T23:59:59.000Z',
|
|
1302
|
+
});
|
|
1303
|
+
expect(evictResult.evictedCount).toBeGreaterThanOrEqual(0);
|
|
1304
|
+
const retrainResult = await platform.applyMemoryPolicy({
|
|
1305
|
+
userId: 'user_tutor',
|
|
1306
|
+
layer: 'session',
|
|
1307
|
+
operation: 'retrain_plan',
|
|
1308
|
+
limit: 3,
|
|
1309
|
+
now: '2026-04-10T00:00:00.000Z',
|
|
1310
|
+
});
|
|
1311
|
+
expect(retrainResult.recommendedActions?.length).toBeGreaterThan(0);
|
|
1312
|
+
expect(retrainResult.recommendedActions?.[0]?.atomId).toBe(atomId);
|
|
1313
|
+
const state = platform.getKnowledgeState();
|
|
1314
|
+
expect(state.ingestTelemetry.ingestCount).toBeGreaterThan(0);
|
|
1315
|
+
expect(state.ingestTelemetry.ingestP95Ms).toBeGreaterThanOrEqual(0);
|
|
1316
|
+
expect(state.retrievalTelemetry.queryCount).toBeGreaterThanOrEqual(0);
|
|
1317
|
+
expect(state.retrievalTelemetry.queryP95Ms).toBeGreaterThanOrEqual(0);
|
|
1318
|
+
expect(state.sessionActionTelemetry.executionCount).toBeGreaterThanOrEqual(0);
|
|
1319
|
+
});
|
|
1320
|
+
test('tutor action uses misconception context for targeted guidance', async () => {
|
|
1321
|
+
const ingest = await platform.ingestKnowledge({
|
|
1322
|
+
incremental: true,
|
|
1323
|
+
documents: [
|
|
1324
|
+
{
|
|
1325
|
+
documentId: 'doc_tutor_focus',
|
|
1326
|
+
sourcePath: 'Knowledge_Base/doc_tutor_focus.md',
|
|
1327
|
+
language: 'en',
|
|
1328
|
+
content: '# Tutor Focus\nAnswers should map claims to source evidence.',
|
|
1329
|
+
},
|
|
1330
|
+
],
|
|
1331
|
+
});
|
|
1332
|
+
const atomId = ingest.atoms[0]?.id;
|
|
1333
|
+
await platform.diagnoseMastery({
|
|
1334
|
+
userId: 'user_tutor_focus',
|
|
1335
|
+
observations: [
|
|
1336
|
+
{
|
|
1337
|
+
atomId,
|
|
1338
|
+
outcome: 'incorrect',
|
|
1339
|
+
errorTag: 'evidence_mismatch',
|
|
1340
|
+
},
|
|
1341
|
+
],
|
|
1342
|
+
});
|
|
1343
|
+
const tutor = await platform.executeTutorAction({
|
|
1344
|
+
userId: 'user_tutor_focus',
|
|
1345
|
+
actionKind: 'analyze_answer',
|
|
1346
|
+
atomId,
|
|
1347
|
+
answer: 'I summarized the topic without citing exact evidence.',
|
|
1348
|
+
});
|
|
1349
|
+
expect(tutor.message).toContain('Known misconception to repair: evidence_mismatch.');
|
|
1350
|
+
expect(tutor.suggestedActions.some((action) => action.kind === 'review')).toBe(true);
|
|
1351
|
+
expect(tutor.trace.notes).toContain('misconception focus: evidence_mismatch');
|
|
1352
|
+
});
|
|
1353
|
+
test('tutor adapter output is guarded by evidence binding and confidence thresholds', async () => {
|
|
1354
|
+
const guardedPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
1355
|
+
nowProvider: () => new Date(nowIso),
|
|
1356
|
+
tutorAdapter: {
|
|
1357
|
+
id: 'mock-adapter',
|
|
1358
|
+
mode: 'local',
|
|
1359
|
+
async execute(input) {
|
|
1360
|
+
return {
|
|
1361
|
+
message: `Synthetic tutor response for ${input.atom.title}`,
|
|
1362
|
+
confidence: 0.42,
|
|
1363
|
+
evidenceSpanIds: [],
|
|
1364
|
+
};
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1368
|
+
const ingest = await guardedPlatform.ingestKnowledge({
|
|
1369
|
+
incremental: true,
|
|
1370
|
+
documents: [
|
|
1371
|
+
{
|
|
1372
|
+
documentId: 'doc_guardrail',
|
|
1373
|
+
sourcePath: 'Knowledge_Base/doc_guardrail.md',
|
|
1374
|
+
language: 'en',
|
|
1375
|
+
content: '# Guardrails\nTutor output should be evidence bound.',
|
|
1376
|
+
},
|
|
1377
|
+
],
|
|
1378
|
+
});
|
|
1379
|
+
const atomId = ingest.atoms[0]?.id;
|
|
1380
|
+
const result = await guardedPlatform.executeTutorAction({
|
|
1381
|
+
userId: 'user_guardrail',
|
|
1382
|
+
actionKind: 'follow_up',
|
|
1383
|
+
atomId,
|
|
1384
|
+
prompt: 'Generate a follow-up explanation.',
|
|
1385
|
+
});
|
|
1386
|
+
expect(result.trace.source).toBe('llm-adapter');
|
|
1387
|
+
expect(result.trace.confidence).toBeLessThan(0.65);
|
|
1388
|
+
expect(result.message).toContain('Low-confidence tutor output detected');
|
|
1389
|
+
expect(result.trace.notes.toLowerCase()).toContain('downgraded');
|
|
1390
|
+
});
|
|
1391
|
+
test('phase-3 tutor telemetry and provider trend diagnostics summarize llm traces', async () => {
|
|
1392
|
+
let adapterCallCount = 0;
|
|
1393
|
+
const telemetryPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
1394
|
+
nowProvider: () => new Date(nowIso),
|
|
1395
|
+
tutorAdapter: {
|
|
1396
|
+
id: 'mock-cloud',
|
|
1397
|
+
mode: 'cloud',
|
|
1398
|
+
async execute(input) {
|
|
1399
|
+
adapterCallCount += 1;
|
|
1400
|
+
if (adapterCallCount <= 3) {
|
|
1401
|
+
return {
|
|
1402
|
+
message: `Accepted tutor answer for ${input.atom.title}`,
|
|
1403
|
+
confidence: 0.86,
|
|
1404
|
+
evidenceSpanIds: input.evidenceSpans.slice(0, 1).map((span) => span.id),
|
|
1405
|
+
adapterId: 'mock-cloud',
|
|
1406
|
+
providerName: 'cloud_llm',
|
|
1407
|
+
providerMode: 'cloud',
|
|
1408
|
+
metadata: {
|
|
1409
|
+
attemptedProviders: ['cloud_llm'],
|
|
1410
|
+
selectedProvider: 'cloud_llm',
|
|
1411
|
+
},
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
message: `Fallback-heavy tutor answer for ${input.atom.title}`,
|
|
1416
|
+
confidence: 0.34,
|
|
1417
|
+
evidenceSpanIds: [],
|
|
1418
|
+
adapterId: 'mock-cloud',
|
|
1419
|
+
providerName: 'cloud_llm',
|
|
1420
|
+
providerMode: 'cloud',
|
|
1421
|
+
metadata: {
|
|
1422
|
+
attemptedProviders: ['cloud_llm', 'local_llm'],
|
|
1423
|
+
selectedProvider: 'cloud_llm',
|
|
1424
|
+
},
|
|
1425
|
+
};
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
});
|
|
1429
|
+
const ingest = await telemetryPlatform.ingestKnowledge({
|
|
1430
|
+
incremental: true,
|
|
1431
|
+
documents: [
|
|
1432
|
+
{
|
|
1433
|
+
documentId: 'doc_phase3_tutor',
|
|
1434
|
+
sourcePath: 'Knowledge_Base/doc_phase3_tutor.md',
|
|
1435
|
+
language: 'en',
|
|
1436
|
+
content: '# Tutor Telemetry\nTrack provider routing, fallback, and trace quality.',
|
|
1437
|
+
},
|
|
1438
|
+
],
|
|
1439
|
+
});
|
|
1440
|
+
const atomId = ingest.atoms[0]?.id;
|
|
1441
|
+
for (let index = 0; index < 6; index += 1) {
|
|
1442
|
+
nowIso = `2026-03-31T${String(8 + index).padStart(2, '0')}:00:00.000Z`;
|
|
1443
|
+
await telemetryPlatform.executeTutorAction({
|
|
1444
|
+
userId: 'phase3_tutor_user',
|
|
1445
|
+
actionKind: 'recap',
|
|
1446
|
+
atomId,
|
|
1447
|
+
prompt: `phase3 tutor trace ${index + 1}`,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
const catalog = await telemetryPlatform.getTutorAdapterCatalog();
|
|
1451
|
+
expect(catalog.summary.totalAdapters).toBe(1);
|
|
1452
|
+
expect(catalog.adapters[0]?.adapterId).toBe('mock-cloud');
|
|
1453
|
+
const telemetry = await telemetryPlatform.getTutorAdapterTelemetry();
|
|
1454
|
+
expect(telemetry.summary.totalRequests).toBe(6);
|
|
1455
|
+
expect(telemetry.summary.acceptedResponses).toBe(3);
|
|
1456
|
+
expect(telemetry.summary.providerFallbackResponses).toBe(3);
|
|
1457
|
+
expect(telemetry.summary.averageProviderAttemptCount).toBeGreaterThan(1);
|
|
1458
|
+
expect(telemetry.adapters[0]?.adapterId).toBe('mock-cloud');
|
|
1459
|
+
const diagnostics = await telemetryPlatform.queryTutorTraceDiagnostics({
|
|
1460
|
+
source: 'llm-adapter',
|
|
1461
|
+
providerName: 'cloud_llm',
|
|
1462
|
+
limit: 4,
|
|
1463
|
+
});
|
|
1464
|
+
expect(diagnostics.summary.matchedTraces).toBe(6);
|
|
1465
|
+
expect(diagnostics.summary.returnedTraces).toBe(4);
|
|
1466
|
+
expect(diagnostics.providerBreakdown[0]?.providerName).toBe('cloud_llm');
|
|
1467
|
+
expect(diagnostics.providerBreakdown[0]?.fallbackTraces).toBe(3);
|
|
1468
|
+
const trendDiagnostics = await telemetryPlatform.queryTutorProviderTrendDiagnostics({
|
|
1469
|
+
source: 'llm-adapter',
|
|
1470
|
+
limit: 4,
|
|
1471
|
+
windowSize: 3,
|
|
1472
|
+
minSamples: 2,
|
|
1473
|
+
});
|
|
1474
|
+
expect(trendDiagnostics.providers[0]?.providerName).toBe('cloud_llm');
|
|
1475
|
+
expect(trendDiagnostics.providers[0]?.trendStatus).toBe('regressing');
|
|
1476
|
+
expect(Number(trendDiagnostics.providers[0]?.deltas?.fallbackRatioDeltaPct || 0)).toBeGreaterThan(0);
|
|
1477
|
+
const trendHistory = await telemetryPlatform.queryTutorProviderTrendHistory({
|
|
1478
|
+
source: 'llm-adapter',
|
|
1479
|
+
limit: 6,
|
|
1480
|
+
windowSize: 3,
|
|
1481
|
+
minSamples: 2,
|
|
1482
|
+
});
|
|
1483
|
+
expect(trendHistory.summary.totalProviders).toBe(1);
|
|
1484
|
+
expect(trendHistory.summary.totalRecords).toBeGreaterThan(0);
|
|
1485
|
+
expect(trendHistory.records[0]?.providerName).toBe('cloud_llm');
|
|
1486
|
+
});
|
|
1487
|
+
test('conversation memory lifecycle supports add search feedback list and delete', async () => {
|
|
1488
|
+
const addResult = await platform.addConversationMemory({
|
|
1489
|
+
userId: 'conversation_memory_user',
|
|
1490
|
+
namespace: 'conversation',
|
|
1491
|
+
content: 'Remember to revisit focus evidence before transfer tasks.',
|
|
1492
|
+
tags: ['focus', 'evidence'],
|
|
1493
|
+
source: 'manual_note',
|
|
1494
|
+
confidence: 0.74,
|
|
1495
|
+
now: '2026-04-02T10:00:00.000Z',
|
|
1496
|
+
});
|
|
1497
|
+
const memoryId = String(addResult.memory?.memoryId || '');
|
|
1498
|
+
expect(addResult.added).toBe(true);
|
|
1499
|
+
expect(memoryId).toContain('conv_memory_');
|
|
1500
|
+
await platform.addConversationMemory({
|
|
1501
|
+
userId: 'conversation_memory_user',
|
|
1502
|
+
namespace: 'study_session',
|
|
1503
|
+
content: 'Review transfer path sequencing after the recap.',
|
|
1504
|
+
tags: ['transfer'],
|
|
1505
|
+
now: '2026-04-02T10:05:00.000Z',
|
|
1506
|
+
});
|
|
1507
|
+
const searchResult = await platform.searchConversationMemory({
|
|
1508
|
+
userId: 'conversation_memory_user',
|
|
1509
|
+
namespace: 'conversation',
|
|
1510
|
+
query: 'focus evidence',
|
|
1511
|
+
limit: 5,
|
|
1512
|
+
now: '2026-04-02T10:10:00.000Z',
|
|
1513
|
+
});
|
|
1514
|
+
expect(searchResult.summary.matchedResults).toBe(1);
|
|
1515
|
+
expect(searchResult.message).toContain('Conversation memory recall (1/1)');
|
|
1516
|
+
expect(searchResult.results[0]?.namespace).toBe('conversation');
|
|
1517
|
+
const feedbackResult = await platform.feedbackConversationMemory({
|
|
1518
|
+
userId: 'conversation_memory_user',
|
|
1519
|
+
namespace: 'conversation',
|
|
1520
|
+
memoryId,
|
|
1521
|
+
feedback: 'correct',
|
|
1522
|
+
correctedContent: 'Remember to revisit focus evidence before transfer tasks and cite the exact span.',
|
|
1523
|
+
now: '2026-04-02T10:15:00.000Z',
|
|
1524
|
+
});
|
|
1525
|
+
expect(feedbackResult.recorded).toBe(true);
|
|
1526
|
+
expect(feedbackResult.memory?.content).toContain('cite the exact span');
|
|
1527
|
+
expect(Number(feedbackResult.memory?.confidence || 0)).toBeGreaterThanOrEqual(0.9);
|
|
1528
|
+
const listResult = await platform.listConversationMemory({
|
|
1529
|
+
userId: 'conversation_memory_user',
|
|
1530
|
+
namespace: 'conversation',
|
|
1531
|
+
limit: 5,
|
|
1532
|
+
now: '2026-04-02T10:20:00.000Z',
|
|
1533
|
+
});
|
|
1534
|
+
expect(listResult.summary.returnedEntries).toBe(1);
|
|
1535
|
+
expect(listResult.entries[0]?.memoryId).toBe(memoryId);
|
|
1536
|
+
const deleteResult = await platform.deleteConversationMemory({
|
|
1537
|
+
userId: 'conversation_memory_user',
|
|
1538
|
+
namespace: 'conversation',
|
|
1539
|
+
memoryId,
|
|
1540
|
+
now: '2026-04-02T10:25:00.000Z',
|
|
1541
|
+
});
|
|
1542
|
+
expect(deleteResult.deleted).toBe(true);
|
|
1543
|
+
const finalList = await platform.listConversationMemory({
|
|
1544
|
+
userId: 'conversation_memory_user',
|
|
1545
|
+
namespace: 'conversation',
|
|
1546
|
+
limit: 5,
|
|
1547
|
+
now: '2026-04-02T10:30:00.000Z',
|
|
1548
|
+
});
|
|
1549
|
+
expect(finalList.summary.returnedEntries).toBe(0);
|
|
1550
|
+
});
|
|
1551
|
+
test('agent conversation returns grounded citations and persists scoped turn state', async () => {
|
|
1552
|
+
await platform.ingestKnowledge({
|
|
1553
|
+
incremental: true,
|
|
1554
|
+
documents: [
|
|
1555
|
+
{
|
|
1556
|
+
documentId: 'doc_agent_scope',
|
|
1557
|
+
sourcePath: 'Knowledge_Base/optics/absorption.md',
|
|
1558
|
+
language: 'zh',
|
|
1559
|
+
content: '# 吸收\n吸收系数与光学衰减共同决定材料中的能量损失。',
|
|
1560
|
+
},
|
|
1561
|
+
],
|
|
1562
|
+
});
|
|
1563
|
+
const response = await platform.agentConversation({
|
|
1564
|
+
userId: 'agent_scope_user',
|
|
1565
|
+
sessionId: 'session_absorption',
|
|
1566
|
+
message: '解释一下吸收系数和光学衰减',
|
|
1567
|
+
scope: {
|
|
1568
|
+
corpusId: 'optics',
|
|
1569
|
+
languages: ['zh'],
|
|
1570
|
+
},
|
|
1571
|
+
persistMemory: true,
|
|
1572
|
+
});
|
|
1573
|
+
expect(response.answer).toContain('吸收');
|
|
1574
|
+
expect(response.citations.length).toBeGreaterThan(0);
|
|
1575
|
+
expect(Array.isArray(response.assistantBlocks)).toBe(true);
|
|
1576
|
+
expect(response.assistantBlocks?.map((block) => block.type)).toEqual(expect.arrayContaining(['structured_answer', 'system_notice', 'citations', 'knowledge_actions', 'knowledge_run_summary']));
|
|
1577
|
+
const structuredBlock = (response.assistantBlocks || []).find((block) => block.type === 'structured_answer');
|
|
1578
|
+
expect(structuredBlock).toEqual(expect.objectContaining({
|
|
1579
|
+
type: 'structured_answer',
|
|
1580
|
+
}));
|
|
1581
|
+
expect(structuredBlock && 'overviewMarkdown' in structuredBlock
|
|
1582
|
+
? String(structuredBlock.overviewMarkdown || '')
|
|
1583
|
+
: '').toContain('## Answer Context');
|
|
1584
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1585
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1586
|
+
: '').toContain('## Explanation');
|
|
1587
|
+
expect(structuredBlock && 'evidenceMarkdown' in structuredBlock
|
|
1588
|
+
? String(structuredBlock.evidenceMarkdown || '')
|
|
1589
|
+
: '').toContain('## Evidence Summary');
|
|
1590
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1591
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1592
|
+
: '').toContain('best scoped anchor');
|
|
1593
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
1594
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
1595
|
+
: '').toContain('## Next Actions');
|
|
1596
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
1597
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
1598
|
+
: '').toContain('Persist the latest user focus to scoped conversation memory');
|
|
1599
|
+
expect(response.trace.usedScope.corpusId).toBe('optics');
|
|
1600
|
+
expect(response.summary.appliedMemoryCount).toBeGreaterThan(0);
|
|
1601
|
+
const persistedMemory = await platform.searchConversationMemory({
|
|
1602
|
+
userId: 'agent_scope_user',
|
|
1603
|
+
namespace: 'conversation',
|
|
1604
|
+
query: '吸收系数',
|
|
1605
|
+
limit: 5,
|
|
1606
|
+
});
|
|
1607
|
+
expect(persistedMemory.results.length).toBeGreaterThan(0);
|
|
1608
|
+
expect(Array.isArray(persistedMemory.results[0]?.tags)).toBe(true);
|
|
1609
|
+
expect(persistedMemory.results[0]?.tags).toContain('scope_corpus:optics');
|
|
1610
|
+
});
|
|
1611
|
+
test('agent conversation groups matched sections under one knowledge point with evidence spans for compact alias queries', async () => {
|
|
1612
|
+
await platform.ingestKnowledge({
|
|
1613
|
+
incremental: true,
|
|
1614
|
+
documents: [
|
|
1615
|
+
{
|
|
1616
|
+
documentId: 'doc_water_glass_grouped',
|
|
1617
|
+
sourcePath: 'Knowledge_Base/waterglass/water glass.md',
|
|
1618
|
+
language: 'en',
|
|
1619
|
+
workspaceId: 'waterglass',
|
|
1620
|
+
corpusId: 'waterglass',
|
|
1621
|
+
content: [
|
|
1622
|
+
'# Water Glass',
|
|
1623
|
+
'A water glass is a physical system made of a transparent container and water.',
|
|
1624
|
+
'',
|
|
1625
|
+
'## Material boundary',
|
|
1626
|
+
'The water glass boundary is commonly soda-lime glass that contains the liquid.',
|
|
1627
|
+
'',
|
|
1628
|
+
'## Thermal exchange',
|
|
1629
|
+
'The water glass exchanges heat with the environment through conduction and convection.',
|
|
1630
|
+
].join('\n'),
|
|
1631
|
+
},
|
|
1632
|
+
],
|
|
1633
|
+
});
|
|
1634
|
+
const response = await platform.agentConversation({
|
|
1635
|
+
userId: 'agent_grouped_user',
|
|
1636
|
+
sessionId: 'session_water_glass_grouped',
|
|
1637
|
+
message: '什么是waterglass?',
|
|
1638
|
+
scope: {
|
|
1639
|
+
workspaceId: 'waterglass',
|
|
1640
|
+
corpusId: 'waterglass',
|
|
1641
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
1642
|
+
},
|
|
1643
|
+
topK: 8,
|
|
1644
|
+
persistMemory: false,
|
|
1645
|
+
});
|
|
1646
|
+
expect(response.answer).toMatch(/^A water glass is/i);
|
|
1647
|
+
expect(response.answer).not.toContain('The strongest scoped match is');
|
|
1648
|
+
expect(response.knowledgePoints).toHaveLength(1);
|
|
1649
|
+
expect(response.summary.returnedKnowledgePoints).toBe(1);
|
|
1650
|
+
expect(response.citations.length).toBeGreaterThanOrEqual(2);
|
|
1651
|
+
const groupedPoint = response.knowledgePoints[0];
|
|
1652
|
+
expect(groupedPoint.title).toBe('Water Glass');
|
|
1653
|
+
expect(groupedPoint.documentId).toBe('doc_water_glass_grouped');
|
|
1654
|
+
expect(groupedPoint.matchCount).toBeGreaterThanOrEqual(2);
|
|
1655
|
+
expect(groupedPoint.matchedSpans.length).toBeGreaterThanOrEqual(2);
|
|
1656
|
+
expect(groupedPoint.matchedSpans.map((span) => span.title)).toEqual(expect.arrayContaining(['Water Glass', 'Material boundary']));
|
|
1657
|
+
expect(groupedPoint.citations.length).toBeGreaterThanOrEqual(2);
|
|
1658
|
+
expect(new Set(groupedPoint.citations.map((citation) => citation.documentId))).toEqual(new Set(['doc_water_glass_grouped']));
|
|
1659
|
+
});
|
|
1660
|
+
test('agent conversation recovers a title-like knowledge point when the active scope misses another corpus', async () => {
|
|
1661
|
+
await platform.ingestKnowledge({
|
|
1662
|
+
incremental: true,
|
|
1663
|
+
documents: [
|
|
1664
|
+
{
|
|
1665
|
+
documentId: 'doc_financial_scope',
|
|
1666
|
+
sourcePath: 'Knowledge_Base/financial/liquidity.md',
|
|
1667
|
+
language: 'en',
|
|
1668
|
+
workspaceId: 'financial',
|
|
1669
|
+
corpusId: 'financial',
|
|
1670
|
+
content: '# Liquidity\nLiquidity analysis explains cash conversion and working capital timing.',
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
documentId: 'doc_water_glass_scope_recovery',
|
|
1674
|
+
sourcePath: 'Knowledge_Base/waterglass/water glass.md',
|
|
1675
|
+
language: 'en',
|
|
1676
|
+
workspaceId: 'waterglass',
|
|
1677
|
+
corpusId: 'waterglass',
|
|
1678
|
+
content: [
|
|
1679
|
+
'# Water Glass',
|
|
1680
|
+
'A water glass is a transparent drinking vessel that contains water for use.',
|
|
1681
|
+
'',
|
|
1682
|
+
'## Material role',
|
|
1683
|
+
'The water glass body provides a boundary between the liquid and the environment.',
|
|
1684
|
+
].join('\n'),
|
|
1685
|
+
},
|
|
1686
|
+
],
|
|
1687
|
+
});
|
|
1688
|
+
const response = await platform.agentConversation({
|
|
1689
|
+
userId: 'agent_scope_recovery_user',
|
|
1690
|
+
sessionId: 'session_scope_recovery',
|
|
1691
|
+
message: 'what is water glass?',
|
|
1692
|
+
scope: {
|
|
1693
|
+
workspaceId: 'financial',
|
|
1694
|
+
corpusId: 'financial',
|
|
1695
|
+
sourcePathPrefixes: ['Knowledge_Base/financial'],
|
|
1696
|
+
},
|
|
1697
|
+
topK: 8,
|
|
1698
|
+
persistMemory: false,
|
|
1699
|
+
});
|
|
1700
|
+
expect(response.answer).toMatch(/^A water glass is/i);
|
|
1701
|
+
expect(response.knowledgePoints).toHaveLength(1);
|
|
1702
|
+
expect(response.summary.returnedKnowledgePoints).toBe(1);
|
|
1703
|
+
expect(response.summary.returnedCitations).toBeGreaterThanOrEqual(2);
|
|
1704
|
+
expect(response.trace.usedScope.scopeSource).toBe('planner_scope_recovery');
|
|
1705
|
+
expect(response.trace.retrieval.retrievalModes).toContain('planner_scope_recovery');
|
|
1706
|
+
expect(response.trace.planner?.titleHitDocumentIds).toContain('doc_water_glass_scope_recovery');
|
|
1707
|
+
const recoveredPoint = response.knowledgePoints[0];
|
|
1708
|
+
expect(recoveredPoint.documentId).toBe('doc_water_glass_scope_recovery');
|
|
1709
|
+
expect(recoveredPoint.sourcePath).toBe('Knowledge_Base/waterglass/water glass.md');
|
|
1710
|
+
expect(recoveredPoint.matchCount).toBeGreaterThanOrEqual(2);
|
|
1711
|
+
expect(recoveredPoint.matchedSpans.map((span) => span.title)).toEqual(expect.arrayContaining(['Water Glass', 'Material role']));
|
|
1712
|
+
});
|
|
1713
|
+
test('agent conversation keeps compare queries inside an explicit scope when scoped operands exist', async () => {
|
|
1714
|
+
await platform.ingestKnowledge({
|
|
1715
|
+
incremental: true,
|
|
1716
|
+
documents: [
|
|
1717
|
+
{
|
|
1718
|
+
documentId: 'doc_status_conflict_outside_scope',
|
|
1719
|
+
sourcePath: 'Knowledge_Base/ragstatuscodeconflict/response status code conflict probe.md',
|
|
1720
|
+
language: 'en',
|
|
1721
|
+
workspaceId: 'ragstatuscodeconflict',
|
|
1722
|
+
corpusId: 'ragstatuscodeconflict',
|
|
1723
|
+
content: [
|
|
1724
|
+
'# Response Status Code Conflict Probe',
|
|
1725
|
+
'Response status code conflict probe records competing HTTP response status values.',
|
|
1726
|
+
'',
|
|
1727
|
+
'## Endpoint Response Code',
|
|
1728
|
+
'The response status code is 200 in the release manifest.',
|
|
1729
|
+
'',
|
|
1730
|
+
'The response status code is 503 in the rollback manifest.',
|
|
1731
|
+
].join('\n'),
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
documentId: 'doc_status_staging_scoped',
|
|
1735
|
+
sourcePath: 'Knowledge_Base/ragconditionstatuscodecrossscope/cross environment staging response status code source.md',
|
|
1736
|
+
language: 'en',
|
|
1737
|
+
workspaceId: 'ragconditionstatuscodecrossscope',
|
|
1738
|
+
corpusId: 'ragconditionstatuscodecrossscope',
|
|
1739
|
+
content: [
|
|
1740
|
+
'# Cross Environment Staging Response Status Code Source',
|
|
1741
|
+
'Cross environment staging response status code source records that the response status code is 200 in the staging environment.',
|
|
1742
|
+
'',
|
|
1743
|
+
'## Staging Response Status Code',
|
|
1744
|
+
'The response status code is 200 in the staging environment.',
|
|
1745
|
+
].join('\n'),
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
documentId: 'doc_status_production_scoped',
|
|
1749
|
+
sourcePath: 'Knowledge_Base/ragconditionstatuscodecrossscope/cross environment production response status code source.md',
|
|
1750
|
+
language: 'en',
|
|
1751
|
+
workspaceId: 'ragconditionstatuscodecrossscope',
|
|
1752
|
+
corpusId: 'ragconditionstatuscodecrossscope',
|
|
1753
|
+
content: [
|
|
1754
|
+
'# Cross Environment Production Response Status Code Source',
|
|
1755
|
+
'Cross environment production response status code source records that the response status code is 503 in the production environment.',
|
|
1756
|
+
'',
|
|
1757
|
+
'## Production Response Status Code',
|
|
1758
|
+
'The response status code is 503 in the production environment.',
|
|
1759
|
+
].join('\n'),
|
|
1760
|
+
},
|
|
1761
|
+
],
|
|
1762
|
+
});
|
|
1763
|
+
const response = await platform.agentConversation({
|
|
1764
|
+
userId: 'agent_compare_scope_boundary_user',
|
|
1765
|
+
sessionId: 'session_compare_scope_boundary',
|
|
1766
|
+
message: 'compare cross environment staging response status code source with cross environment production response status code source',
|
|
1767
|
+
scope: {
|
|
1768
|
+
workspaceId: 'ragconditionstatuscodecrossscope',
|
|
1769
|
+
corpusId: 'ragconditionstatuscodecrossscope',
|
|
1770
|
+
sourcePathPrefixes: ['Knowledge_Base/ragconditionstatuscodecrossscope'],
|
|
1771
|
+
},
|
|
1772
|
+
topK: 8,
|
|
1773
|
+
persistMemory: false,
|
|
1774
|
+
});
|
|
1775
|
+
expect(response.trace.usedScope.scopeSource).toBe('explicit_request');
|
|
1776
|
+
expect(response.answer).toContain('response status code');
|
|
1777
|
+
expect(response.answer).toContain('200');
|
|
1778
|
+
expect(response.answer).toContain('503');
|
|
1779
|
+
expect(response.answer).toContain('staging');
|
|
1780
|
+
expect(response.answer).toContain('production');
|
|
1781
|
+
expect(response.answer).not.toContain('Conflicting evidence');
|
|
1782
|
+
expect(response.knowledgePoints.map((point) => point.sourcePath)).toEqual(expect.arrayContaining([
|
|
1783
|
+
'Knowledge_Base/ragconditionstatuscodecrossscope/cross environment staging response status code source.md',
|
|
1784
|
+
'Knowledge_Base/ragconditionstatuscodecrossscope/cross environment production response status code source.md',
|
|
1785
|
+
]));
|
|
1786
|
+
expect(response.knowledgePoints.some((point) => (String(point.sourcePath || '').includes('ragstatuscodeconflict')))).toBe(false);
|
|
1787
|
+
});
|
|
1788
|
+
test('workflow artifact review follow-up consumes review cards and archives completed batches', async () => {
|
|
1789
|
+
await platform.ingestKnowledge({
|
|
1790
|
+
incremental: true,
|
|
1791
|
+
documents: [
|
|
1792
|
+
{
|
|
1793
|
+
documentId: 'doc_follow_up',
|
|
1794
|
+
sourcePath: 'Knowledge_Base/waterglass/water glass.md',
|
|
1795
|
+
language: 'en',
|
|
1796
|
+
workspaceId: 'waterglass',
|
|
1797
|
+
corpusId: 'waterglass',
|
|
1798
|
+
content: '# Water Glass\nA water glass is a transparent drinking vessel that contains water for use.',
|
|
1799
|
+
},
|
|
1800
|
+
],
|
|
1801
|
+
});
|
|
1802
|
+
const conversation = await platform.agentConversation({
|
|
1803
|
+
userId: 'follow_up_user',
|
|
1804
|
+
sessionId: 'follow_up_session',
|
|
1805
|
+
message: 'what is water glass?',
|
|
1806
|
+
scope: {
|
|
1807
|
+
workspaceId: 'waterglass',
|
|
1808
|
+
corpusId: 'waterglass',
|
|
1809
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
1810
|
+
},
|
|
1811
|
+
persistMemory: false,
|
|
1812
|
+
});
|
|
1813
|
+
const artifacts = await platform.queryWorkflowArtifacts({
|
|
1814
|
+
workspaceId: 'waterglass',
|
|
1815
|
+
userId: 'follow_up_user',
|
|
1816
|
+
artifactKinds: ['flashcard_batch', 'knowledge_run'],
|
|
1817
|
+
limit: 10,
|
|
1818
|
+
});
|
|
1819
|
+
const flashcardArtifact = artifacts.artifacts.find((artifact) => artifact.kind === 'flashcard_batch');
|
|
1820
|
+
const knowledgeRunArtifact = artifacts.artifacts.find((artifact) => artifact.kind === 'knowledge_run');
|
|
1821
|
+
expect(flashcardArtifact).toBeDefined();
|
|
1822
|
+
expect(knowledgeRunArtifact).toBeDefined();
|
|
1823
|
+
expect(knowledgeRunArtifact?.payload?.graphContext).toEqual(expect.objectContaining({
|
|
1824
|
+
anchorTitle: 'Water Glass',
|
|
1825
|
+
diagnostics: expect.objectContaining({
|
|
1826
|
+
graphOpsAvailable: false,
|
|
1827
|
+
usedFallback: true,
|
|
1828
|
+
}),
|
|
1829
|
+
}));
|
|
1830
|
+
const reviewCards = (flashcardArtifact?.payload || {}).reviewCards || [];
|
|
1831
|
+
expect(reviewCards).toHaveLength(1);
|
|
1832
|
+
const followUp = await platform.executeWorkflowArtifactReviewFollowUp({
|
|
1833
|
+
userId: 'follow_up_user',
|
|
1834
|
+
sessionId: 'follow_up_session',
|
|
1835
|
+
artifactId: String(flashcardArtifact?.artifactId || ''),
|
|
1836
|
+
cardId: String(reviewCards[0]?.cardId || ''),
|
|
1837
|
+
action: {
|
|
1838
|
+
atomId: String(reviewCards[0]?.atomId || conversation.knowledgePoints[0]?.atomId || ''),
|
|
1839
|
+
kind: 'review',
|
|
1840
|
+
source: 'flashcard_batch',
|
|
1841
|
+
prompt: String(reviewCards[0]?.prompt || ''),
|
|
1842
|
+
},
|
|
1843
|
+
persistMemory: false,
|
|
1844
|
+
});
|
|
1845
|
+
expect(followUp.consumedCardId).toBe(String(reviewCards[0]?.cardId || ''));
|
|
1846
|
+
expect(followUp.completedReviewCardCount).toBe(1);
|
|
1847
|
+
expect(followUp.remainingReviewCardCount).toBe(0);
|
|
1848
|
+
expect(followUp.archivedArtifact).toBe(true);
|
|
1849
|
+
expect(followUp.artifact.status).toBe('archived');
|
|
1850
|
+
expect(followUp.artifact.payload.reviewState.consumedCardIds).toEqual([
|
|
1851
|
+
String(reviewCards[0]?.cardId || ''),
|
|
1852
|
+
]);
|
|
1853
|
+
expect(followUp.relatedKnowledgeRunArtifact?.status).toBe('archived');
|
|
1854
|
+
expect((followUp.relatedKnowledgeRunArtifact?.payload || {}).knowledgeRun.summary.completedReviewCardCount).toBe(1);
|
|
1855
|
+
expect((followUp.relatedKnowledgeRunArtifact?.payload || {}).knowledgeRun.summary.remainingReviewCardCount).toBe(0);
|
|
1856
|
+
const refreshedArtifacts = await platform.queryWorkflowArtifacts({
|
|
1857
|
+
workspaceId: 'waterglass',
|
|
1858
|
+
userId: 'follow_up_user',
|
|
1859
|
+
artifactKinds: ['flashcard_batch'],
|
|
1860
|
+
limit: 10,
|
|
1861
|
+
});
|
|
1862
|
+
const refreshedFlashcardArtifact = refreshedArtifacts.artifacts.find((artifact) => artifact.artifactId === flashcardArtifact?.artifactId);
|
|
1863
|
+
expect(refreshedFlashcardArtifact?.status).toBe('archived');
|
|
1864
|
+
expect((refreshedFlashcardArtifact?.payload.reviewState).remainingReviewCardCount).toBe(0);
|
|
1865
|
+
});
|
|
1866
|
+
test('agent conversation explanation and next actions adapt to comparison-style queries', async () => {
|
|
1867
|
+
await platform.ingestKnowledge({
|
|
1868
|
+
incremental: true,
|
|
1869
|
+
documents: [
|
|
1870
|
+
{
|
|
1871
|
+
documentId: 'doc_compare_scope',
|
|
1872
|
+
sourcePath: 'Knowledge_Base/optics/reflection.md',
|
|
1873
|
+
language: 'en',
|
|
1874
|
+
content: '# Reflection\nReflection and absorption differ in how optical energy is redirected versus dissipated.\n\n## Support\nSee [[Transmission]].',
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
documentId: 'doc_compare_scope_2',
|
|
1878
|
+
sourcePath: 'Knowledge_Base/optics/transmission.md',
|
|
1879
|
+
language: 'en',
|
|
1880
|
+
content: '# Transmission\nTransmission complements reflection when comparing optical interface behavior.',
|
|
1881
|
+
},
|
|
1882
|
+
],
|
|
1883
|
+
});
|
|
1884
|
+
const response = await platform.agentConversation({
|
|
1885
|
+
userId: 'agent_compare_user',
|
|
1886
|
+
sessionId: 'session_compare_scope',
|
|
1887
|
+
message: 'compare reflection vs absorption',
|
|
1888
|
+
scope: {
|
|
1889
|
+
corpusId: 'optics',
|
|
1890
|
+
languages: ['en'],
|
|
1891
|
+
},
|
|
1892
|
+
persistMemory: true,
|
|
1893
|
+
});
|
|
1894
|
+
const structuredBlock = (response.assistantBlocks || []).find((block) => block.type === 'structured_answer');
|
|
1895
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1896
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1897
|
+
: '').toContain('comparison baseline');
|
|
1898
|
+
expect(structuredBlock && 'overviewMarkdown' in structuredBlock
|
|
1899
|
+
? String(structuredBlock.overviewMarkdown || '')
|
|
1900
|
+
: '').toContain('Graph-supported relations');
|
|
1901
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1902
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1903
|
+
: '').toContain('Supporting comparison nodes');
|
|
1904
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1905
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1906
|
+
: '').toContain('Graph support around');
|
|
1907
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
1908
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
1909
|
+
: '').toContain('inspect the strongest nodes side by side');
|
|
1910
|
+
expect(response.trace.graphContext).toEqual(expect.objectContaining({
|
|
1911
|
+
anchorTitle: 'Reflection',
|
|
1912
|
+
relationKinds: expect.arrayContaining(['reference']),
|
|
1913
|
+
temporalValidity: expect.objectContaining({
|
|
1914
|
+
allPointsValid: true,
|
|
1915
|
+
}),
|
|
1916
|
+
}));
|
|
1917
|
+
expect(response.trace.graphContext.relationSummaries).toEqual(expect.arrayContaining([
|
|
1918
|
+
expect.objectContaining({
|
|
1919
|
+
relationKind: 'reference',
|
|
1920
|
+
}),
|
|
1921
|
+
]));
|
|
1922
|
+
expect(response.trace.graphContext.knowledgePointRelations).toEqual(expect.arrayContaining([
|
|
1923
|
+
expect.objectContaining({
|
|
1924
|
+
relationKind: 'reference',
|
|
1925
|
+
sourceTitle: 'Reflection',
|
|
1926
|
+
targetTitle: 'Transmission',
|
|
1927
|
+
}),
|
|
1928
|
+
]));
|
|
1929
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
1930
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
1931
|
+
: '').toContain('Reflection -> reference -> Transmission');
|
|
1932
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
1933
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
1934
|
+
: '').toContain('Follow the direct graph path between Reflection and Transmission');
|
|
1935
|
+
expect(response.knowledgePoints[0].relationKinds).toContain('reference');
|
|
1936
|
+
});
|
|
1937
|
+
test('agent conversation enriches graph context with explicit store path chains between returned knowledge points', async () => {
|
|
1938
|
+
const tempDir = fs.mkdtempSync(path.join(process.cwd(), 'tmp-agent-conversation-paths-'));
|
|
1939
|
+
const storePath = path.join(tempDir, 'knowledge_graph.snapshot.json');
|
|
1940
|
+
const opsStore = (0, store_1.createKnowledgeGraphStore)({
|
|
1941
|
+
backend: 'file',
|
|
1942
|
+
filePath: storePath,
|
|
1943
|
+
});
|
|
1944
|
+
const opsPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
1945
|
+
nowProvider: () => new Date('2026-06-13T00:00:00.000Z'),
|
|
1946
|
+
store: opsStore,
|
|
1947
|
+
autoPersist: true,
|
|
1948
|
+
});
|
|
1949
|
+
try {
|
|
1950
|
+
await opsPlatform.ingestKnowledge({
|
|
1951
|
+
incremental: true,
|
|
1952
|
+
documents: [
|
|
1953
|
+
{
|
|
1954
|
+
documentId: 'doc_foundation',
|
|
1955
|
+
sourcePath: 'Knowledge_Base/optics/foundation.md',
|
|
1956
|
+
language: 'en',
|
|
1957
|
+
content: '# Foundation Note\nFoundation note stabilizes the optics chain.',
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
documentId: 'doc_bridge',
|
|
1961
|
+
sourcePath: 'Knowledge_Base/optics/bridge.md',
|
|
1962
|
+
language: 'en',
|
|
1963
|
+
content: '# Bridge Layer\nBridge layer links the foundation into the target concept.\n\nSee [[Ground State]].',
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
documentId: 'doc_target',
|
|
1967
|
+
sourcePath: 'Knowledge_Base/optics/ground-state.md',
|
|
1968
|
+
language: 'en',
|
|
1969
|
+
content: '# Ground State\nGround state is the target optical state.\n\nSee [[Foundation Note]].',
|
|
1970
|
+
},
|
|
1971
|
+
],
|
|
1972
|
+
});
|
|
1973
|
+
const snapshot = await opsStore.loadSnapshot();
|
|
1974
|
+
expect(snapshot).not.toBeNull();
|
|
1975
|
+
if (!snapshot) {
|
|
1976
|
+
throw new Error('Expected persisted snapshot for path query test.');
|
|
1977
|
+
}
|
|
1978
|
+
const foundationAtom = snapshot.atoms.find((atom) => atom.title === 'Foundation Note');
|
|
1979
|
+
const bridgeAtom = snapshot.atoms.find((atom) => atom.title === 'Bridge Layer');
|
|
1980
|
+
const targetAtom = snapshot.atoms.find((atom) => atom.title === 'Ground State');
|
|
1981
|
+
expect(foundationAtom).toBeDefined();
|
|
1982
|
+
expect(bridgeAtom).toBeDefined();
|
|
1983
|
+
expect(targetAtom).toBeDefined();
|
|
1984
|
+
snapshot.relationEdges.push({
|
|
1985
|
+
id: 'edge_foundation_bridge',
|
|
1986
|
+
sourceAtomId: String(foundationAtom?.id || ''),
|
|
1987
|
+
targetAtomId: String(bridgeAtom?.id || ''),
|
|
1988
|
+
relationKind: 'prerequisite',
|
|
1989
|
+
provenance: 'fact',
|
|
1990
|
+
confidence: 0.91,
|
|
1991
|
+
evidenceSpanIds: [],
|
|
1992
|
+
temporal: {
|
|
1993
|
+
validFrom: '2026-06-13T00:00:00.000Z',
|
|
1994
|
+
},
|
|
1995
|
+
}, {
|
|
1996
|
+
id: 'edge_bridge_target',
|
|
1997
|
+
sourceAtomId: String(bridgeAtom?.id || ''),
|
|
1998
|
+
targetAtomId: String(targetAtom?.id || ''),
|
|
1999
|
+
relationKind: 'reference',
|
|
2000
|
+
provenance: 'fact',
|
|
2001
|
+
confidence: 0.88,
|
|
2002
|
+
evidenceSpanIds: [],
|
|
2003
|
+
temporal: {
|
|
2004
|
+
validFrom: '2026-06-13T00:00:00.000Z',
|
|
2005
|
+
},
|
|
2006
|
+
});
|
|
2007
|
+
await opsStore.saveSnapshot(snapshot);
|
|
2008
|
+
const response = await opsPlatform.agentConversation({
|
|
2009
|
+
userId: 'agent_path_user',
|
|
2010
|
+
sessionId: 'session_path_user',
|
|
2011
|
+
message: 'explain ground state foundation chain',
|
|
2012
|
+
scope: {
|
|
2013
|
+
corpusId: 'optics',
|
|
2014
|
+
sourcePathPrefixes: ['Knowledge_Base/optics'],
|
|
2015
|
+
documentIds: ['doc_foundation', 'doc_target'],
|
|
2016
|
+
},
|
|
2017
|
+
persistMemory: false,
|
|
2018
|
+
});
|
|
2019
|
+
expect(response.trace.graphContext?.connectionPaths).toEqual(expect.arrayContaining([
|
|
2020
|
+
expect.objectContaining({
|
|
2021
|
+
sourceTitle: 'Foundation Note',
|
|
2022
|
+
targetTitle: 'Ground State',
|
|
2023
|
+
pathTitles: ['Foundation Note', 'Bridge Layer', 'Ground State'],
|
|
2024
|
+
length: 2,
|
|
2025
|
+
pathEdges: expect.arrayContaining([
|
|
2026
|
+
expect.objectContaining({
|
|
2027
|
+
fromAtomId: String(foundationAtom?.id || ''),
|
|
2028
|
+
toAtomId: String(bridgeAtom?.id || ''),
|
|
2029
|
+
relationKind: 'prerequisite',
|
|
2030
|
+
}),
|
|
2031
|
+
expect.objectContaining({
|
|
2032
|
+
fromAtomId: String(bridgeAtom?.id || ''),
|
|
2033
|
+
toAtomId: String(targetAtom?.id || ''),
|
|
2034
|
+
relationKind: 'reference',
|
|
2035
|
+
}),
|
|
2036
|
+
]),
|
|
2037
|
+
}),
|
|
2038
|
+
]));
|
|
2039
|
+
expect(response.trace.graphContext?.predecessorWindow).toEqual(expect.arrayContaining([
|
|
2040
|
+
expect.objectContaining({
|
|
2041
|
+
atomId: String(bridgeAtom?.id || ''),
|
|
2042
|
+
title: 'Bridge Layer',
|
|
2043
|
+
relationKind: 'reference',
|
|
2044
|
+
}),
|
|
2045
|
+
]));
|
|
2046
|
+
expect(response.trace.graphContext?.diagnostics).toEqual(expect.objectContaining({
|
|
2047
|
+
graphOpsAvailable: true,
|
|
2048
|
+
usedFallback: false,
|
|
2049
|
+
candidateCount: expect.any(Number),
|
|
2050
|
+
pathDepthLimit: 6,
|
|
2051
|
+
}));
|
|
2052
|
+
expect(response.knowledgePoints[0]?.title).toBe('Ground State');
|
|
2053
|
+
const structuredBlock = (response.assistantBlocks || []).find((block) => block.type === 'structured_answer');
|
|
2054
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
2055
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
2056
|
+
: '').toContain('Foundation Note -> prerequisite -> Bridge Layer -> reference -> Ground State');
|
|
2057
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
2058
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
2059
|
+
: '').toContain('Immediate predecessor window: Bridge Layer');
|
|
2060
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
2061
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
2062
|
+
: '').toContain('Review the path order: Foundation Note -> Bridge Layer -> Ground State');
|
|
2063
|
+
}
|
|
2064
|
+
finally {
|
|
2065
|
+
opsStore.close?.();
|
|
2066
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
test('agent conversation explanation and next actions adapt to how-to queries', async () => {
|
|
2070
|
+
await platform.ingestKnowledge({
|
|
2071
|
+
incremental: true,
|
|
2072
|
+
documents: [
|
|
2073
|
+
{
|
|
2074
|
+
documentId: 'doc_howto_scope',
|
|
2075
|
+
sourcePath: 'Knowledge_Base/optics/calibration.md',
|
|
2076
|
+
language: 'en',
|
|
2077
|
+
content: '# Calibration\nCalibration sequence requires establishing a baseline measurement, then validating response drift.',
|
|
2078
|
+
},
|
|
2079
|
+
],
|
|
2080
|
+
});
|
|
2081
|
+
const response = await platform.agentConversation({
|
|
2082
|
+
userId: 'agent_howto_user',
|
|
2083
|
+
sessionId: 'session_howto_scope',
|
|
2084
|
+
message: 'how to calibrate optical response',
|
|
2085
|
+
scope: {
|
|
2086
|
+
corpusId: 'optics',
|
|
2087
|
+
languages: ['en'],
|
|
2088
|
+
},
|
|
2089
|
+
persistMemory: true,
|
|
2090
|
+
});
|
|
2091
|
+
const structuredBlock = (response.assistantBlocks || []).find((block) => block.type === 'structured_answer');
|
|
2092
|
+
expect(structuredBlock && 'explanationMarkdown' in structuredBlock
|
|
2093
|
+
? String(structuredBlock.explanationMarkdown || '')
|
|
2094
|
+
: '').toContain('starting anchor for the next concrete steps');
|
|
2095
|
+
expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock
|
|
2096
|
+
? String(structuredBlock.nextActionsMarkdown || '')
|
|
2097
|
+
: '').toContain('move from explanation into concrete guided-learning or focus-mode steps');
|
|
2098
|
+
});
|
|
2099
|
+
test('agent conversation assembles document-augmented RAG context for richer scoped answers', async () => {
|
|
2100
|
+
await platform.ingestKnowledge({
|
|
2101
|
+
incremental: true,
|
|
2102
|
+
documents: [
|
|
2103
|
+
{
|
|
2104
|
+
documentId: 'doc_agent_rag_water_glass',
|
|
2105
|
+
sourcePath: 'Knowledge_Base/waterglass/water-glass.md',
|
|
2106
|
+
language: 'en',
|
|
2107
|
+
workspaceId: 'waterglass',
|
|
2108
|
+
corpusId: 'waterglass',
|
|
2109
|
+
content: [
|
|
2110
|
+
'# Water Glass',
|
|
2111
|
+
'',
|
|
2112
|
+
'## Definition',
|
|
2113
|
+
'',
|
|
2114
|
+
'A water glass is a transparent drinking vessel that contains water.',
|
|
2115
|
+
'',
|
|
2116
|
+
'## Boundary',
|
|
2117
|
+
'',
|
|
2118
|
+
'The vessel boundary and the water surface jointly determine the observed optical behavior.',
|
|
2119
|
+
].join('\n'),
|
|
2120
|
+
},
|
|
2121
|
+
],
|
|
2122
|
+
});
|
|
2123
|
+
const response = await platform.agentConversation({
|
|
2124
|
+
userId: 'agent_rag_user',
|
|
2125
|
+
sessionId: 'session_rag_scope',
|
|
2126
|
+
message: 'what is water glass?',
|
|
2127
|
+
scope: {
|
|
2128
|
+
workspaceId: 'waterglass',
|
|
2129
|
+
corpusId: 'waterglass',
|
|
2130
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
2131
|
+
},
|
|
2132
|
+
persistMemory: false,
|
|
2133
|
+
});
|
|
2134
|
+
expect(response.trace.ragContextPack).toEqual(expect.objectContaining({
|
|
2135
|
+
sourceBoundary: 'full_document',
|
|
2136
|
+
}));
|
|
2137
|
+
expect(response.trace.ragContextPack?.fragments).toEqual(expect.arrayContaining([
|
|
2138
|
+
expect.objectContaining({
|
|
2139
|
+
role: 'direct_support',
|
|
2140
|
+
}),
|
|
2141
|
+
expect.objectContaining({
|
|
2142
|
+
role: 'parent_context',
|
|
2143
|
+
sourceBoundary: 'full_document',
|
|
2144
|
+
}),
|
|
2145
|
+
expect.objectContaining({
|
|
2146
|
+
role: 'graph_neighbor_support',
|
|
2147
|
+
sourceBoundary: 'direct_span_only',
|
|
2148
|
+
}),
|
|
2149
|
+
]));
|
|
2150
|
+
expect(response.trace.ragSufficiencyReview).toEqual(expect.objectContaining({
|
|
2151
|
+
status: 'sufficient',
|
|
2152
|
+
}));
|
|
2153
|
+
expect(response.trace.answerClaimCitations).toEqual(expect.arrayContaining([
|
|
2154
|
+
expect.objectContaining({
|
|
2155
|
+
supportStatus: 'supported',
|
|
2156
|
+
citationIds: expect.arrayContaining([
|
|
2157
|
+
expect.stringMatching(/^evidence_/),
|
|
2158
|
+
]),
|
|
2159
|
+
fragmentIds: expect.arrayContaining([
|
|
2160
|
+
expect.stringContaining('rag_direct_'),
|
|
2161
|
+
]),
|
|
2162
|
+
sourcePaths: expect.arrayContaining([
|
|
2163
|
+
'Knowledge_Base/waterglass/water-glass.md',
|
|
2164
|
+
]),
|
|
2165
|
+
}),
|
|
2166
|
+
]));
|
|
2167
|
+
expect(response.answer).toContain('transparent drinking vessel');
|
|
2168
|
+
expect(response.answer).toContain('vessel boundary');
|
|
2169
|
+
expect(response.answer).toContain('observed optical behavior');
|
|
2170
|
+
});
|
|
2171
|
+
test('agent conversation performs one bounded recovery pass when direct spans crowd out document augmentation', async () => {
|
|
2172
|
+
const crowdedSections = Array.from({ length: 18 }, (_value, index) => [
|
|
2173
|
+
`## Water Glass Evidence ${index + 1}`,
|
|
2174
|
+
'',
|
|
2175
|
+
`Water glass evidence ${index + 1}: a water glass remains a transparent drinking vessel, and this section repeats the query terms to force a direct retrieval span.`,
|
|
2176
|
+
].join('\n')).join('\n\n');
|
|
2177
|
+
await platform.ingestKnowledge({
|
|
2178
|
+
incremental: true,
|
|
2179
|
+
documents: [
|
|
2180
|
+
{
|
|
2181
|
+
documentId: 'doc_agent_rag_recovery_water_glass',
|
|
2182
|
+
sourcePath: 'Knowledge_Base/waterglass/recovery-water-glass.md',
|
|
2183
|
+
language: 'en',
|
|
2184
|
+
workspaceId: 'waterglass',
|
|
2185
|
+
corpusId: 'waterglass',
|
|
2186
|
+
content: [
|
|
2187
|
+
'# Water Glass Recovery Corpus',
|
|
2188
|
+
'',
|
|
2189
|
+
'## Definition',
|
|
2190
|
+
'',
|
|
2191
|
+
'A water glass is a transparent drinking vessel that contains water.',
|
|
2192
|
+
'',
|
|
2193
|
+
crowdedSections,
|
|
2194
|
+
'',
|
|
2195
|
+
'## Boundary',
|
|
2196
|
+
'',
|
|
2197
|
+
'The full source document also explains that the vessel boundary and water surface determine observed optical behavior.',
|
|
2198
|
+
].join('\n'),
|
|
2199
|
+
},
|
|
2200
|
+
],
|
|
2201
|
+
});
|
|
2202
|
+
const response = await platform.agentConversation({
|
|
2203
|
+
userId: 'agent_rag_recovery_user',
|
|
2204
|
+
sessionId: 'session_rag_recovery_scope',
|
|
2205
|
+
message: 'what is water glass?',
|
|
2206
|
+
topK: 18,
|
|
2207
|
+
scope: {
|
|
2208
|
+
workspaceId: 'waterglass',
|
|
2209
|
+
corpusId: 'waterglass',
|
|
2210
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
2211
|
+
},
|
|
2212
|
+
persistMemory: false,
|
|
2213
|
+
});
|
|
2214
|
+
expect(response.trace.ragRecovery).toEqual(expect.objectContaining({
|
|
2215
|
+
attempted: true,
|
|
2216
|
+
strategy: 'expanded_context_pack',
|
|
2217
|
+
beforeStatus: 'borderline',
|
|
2218
|
+
afterStatus: 'sufficient',
|
|
2219
|
+
beforeReasons: expect.arrayContaining(['document_augmentation_missing']),
|
|
2220
|
+
afterReasons: expect.any(Array),
|
|
2221
|
+
}));
|
|
2222
|
+
expect(response.trace.ragRecovery?.addedRoleCounts).toEqual(expect.objectContaining({
|
|
2223
|
+
parent_context: expect.any(Number),
|
|
2224
|
+
}));
|
|
2225
|
+
expect(response.trace.ragSufficiencyReview).toEqual(expect.objectContaining({
|
|
2226
|
+
status: 'sufficient',
|
|
2227
|
+
recoveryAttempted: true,
|
|
2228
|
+
}));
|
|
2229
|
+
expect(response.trace.ragContextPack).toEqual(expect.objectContaining({
|
|
2230
|
+
sourceBoundary: 'full_document',
|
|
2231
|
+
budget: expect.objectContaining({
|
|
2232
|
+
maxFragments: expect.any(Number),
|
|
2233
|
+
}),
|
|
2234
|
+
}));
|
|
2235
|
+
expect(response.trace.ragContextPack?.budget.maxFragments).toBeGreaterThan(14);
|
|
2236
|
+
expect(response.trace.ragContextPack?.fragments).toEqual(expect.arrayContaining([
|
|
2237
|
+
expect.objectContaining({
|
|
2238
|
+
role: 'parent_context',
|
|
2239
|
+
sourceBoundary: 'full_document',
|
|
2240
|
+
}),
|
|
2241
|
+
]));
|
|
2242
|
+
expect(response.trace.ragFailureClassifications).toEqual(expect.arrayContaining([
|
|
2243
|
+
expect.objectContaining({
|
|
2244
|
+
stage: 'context_assembly',
|
|
2245
|
+
code: 'context_budget_limited',
|
|
2246
|
+
severity: 'warning',
|
|
2247
|
+
}),
|
|
2248
|
+
]));
|
|
2249
|
+
expect(response.answer).toContain('transparent drinking vessel');
|
|
2250
|
+
});
|
|
2251
|
+
test('agent conversation widens the first-pass RAG budget for causal and explicit depth requests', async () => {
|
|
2252
|
+
await platform.ingestKnowledge({
|
|
2253
|
+
incremental: true,
|
|
2254
|
+
documents: [
|
|
2255
|
+
{
|
|
2256
|
+
documentId: 'doc_answer_profile_budget_probe',
|
|
2257
|
+
sourcePath: 'Knowledge_Base/answerprofile/answer profile budget probe.md',
|
|
2258
|
+
language: 'en',
|
|
2259
|
+
workspaceId: 'answerprofile',
|
|
2260
|
+
corpusId: 'answerprofile',
|
|
2261
|
+
content: [
|
|
2262
|
+
'# Answer Profile Budget Probe',
|
|
2263
|
+
'',
|
|
2264
|
+
'Answer profile budget probe defines a bounded RAG answer profile for ordinary and explicit deep requests.',
|
|
2265
|
+
'',
|
|
2266
|
+
'## Detail Layer',
|
|
2267
|
+
'',
|
|
2268
|
+
'The detail layer keeps full-document source reading available while the model-visible RAG pack remains capped.',
|
|
2269
|
+
].join('\n'),
|
|
2270
|
+
},
|
|
2271
|
+
],
|
|
2272
|
+
});
|
|
2273
|
+
const baseResponse = await platform.agentConversation({
|
|
2274
|
+
userId: 'agent_rag_profile_user',
|
|
2275
|
+
sessionId: 'session_rag_profile_base',
|
|
2276
|
+
message: 'what is answer profile budget probe?',
|
|
2277
|
+
topK: 3,
|
|
2278
|
+
scope: {
|
|
2279
|
+
workspaceId: 'answerprofile',
|
|
2280
|
+
corpusId: 'answerprofile',
|
|
2281
|
+
sourcePathPrefixes: ['Knowledge_Base/answerprofile'],
|
|
2282
|
+
},
|
|
2283
|
+
persistMemory: false,
|
|
2284
|
+
});
|
|
2285
|
+
const causalResponse = await platform.agentConversation({
|
|
2286
|
+
userId: 'agent_rag_profile_user',
|
|
2287
|
+
sessionId: 'session_rag_profile_causal',
|
|
2288
|
+
message: 'why does answer profile budget probe need bounded evidence?',
|
|
2289
|
+
topK: 3,
|
|
2290
|
+
scope: {
|
|
2291
|
+
workspaceId: 'answerprofile',
|
|
2292
|
+
corpusId: 'answerprofile',
|
|
2293
|
+
sourcePathPrefixes: ['Knowledge_Base/answerprofile'],
|
|
2294
|
+
},
|
|
2295
|
+
persistMemory: false,
|
|
2296
|
+
});
|
|
2297
|
+
const deepResponse = await platform.agentConversation({
|
|
2298
|
+
userId: 'agent_rag_profile_user',
|
|
2299
|
+
sessionId: 'session_rag_profile_deep',
|
|
2300
|
+
message: 'explain in detail answer profile budget probe',
|
|
2301
|
+
topK: 3,
|
|
2302
|
+
scope: {
|
|
2303
|
+
workspaceId: 'answerprofile',
|
|
2304
|
+
corpusId: 'answerprofile',
|
|
2305
|
+
sourcePathPrefixes: ['Knowledge_Base/answerprofile'],
|
|
2306
|
+
},
|
|
2307
|
+
persistMemory: false,
|
|
2308
|
+
});
|
|
2309
|
+
expect(baseResponse.trace.ragContextPack?.budget).toEqual({
|
|
2310
|
+
maxFragments: 14,
|
|
2311
|
+
maxCharsPerFragment: 1400,
|
|
2312
|
+
maxTotalChars: 5600,
|
|
2313
|
+
});
|
|
2314
|
+
expect(causalResponse.trace.ragContextPack?.budget).toEqual({
|
|
2315
|
+
maxFragments: 20,
|
|
2316
|
+
maxCharsPerFragment: 1500,
|
|
2317
|
+
maxTotalChars: 7600,
|
|
2318
|
+
});
|
|
2319
|
+
expect(deepResponse.trace.ragContextPack?.budget).toEqual({
|
|
2320
|
+
maxFragments: 24,
|
|
2321
|
+
maxCharsPerFragment: 1600,
|
|
2322
|
+
maxTotalChars: 9000,
|
|
2323
|
+
});
|
|
2324
|
+
expect(causalResponse.trace.ragContextPack?.sourceBoundary).toBe('full_document');
|
|
2325
|
+
expect(deepResponse.trace.ragContextPack?.sourceBoundary).toBe('full_document');
|
|
2326
|
+
});
|
|
2327
|
+
test('agent conversation uses optional LLM sufficiency judge for borderline legacy source windows', async () => {
|
|
2328
|
+
const savedAt = '2026-07-05T00:00:00.000Z';
|
|
2329
|
+
const sourcePath = 'tmp/missing-rag-legacy-source/water-glass.md';
|
|
2330
|
+
const atomContent = 'A water glass is a transparent drinking vessel that contains water.';
|
|
2331
|
+
const snapshot = {
|
|
2332
|
+
schemaVersion: 2,
|
|
2333
|
+
savedAt,
|
|
2334
|
+
idCounter: 1,
|
|
2335
|
+
atoms: [
|
|
2336
|
+
{
|
|
2337
|
+
id: 'atom_legacy_water_glass',
|
|
2338
|
+
stableKey: 'legacy-water-glass-definition',
|
|
2339
|
+
documentId: 'doc_legacy_water_glass',
|
|
2340
|
+
sourcePath,
|
|
2341
|
+
title: 'Water Glass',
|
|
2342
|
+
content: atomContent,
|
|
2343
|
+
representationType: 'text',
|
|
2344
|
+
keywords: ['water', 'glass', 'transparent', 'vessel'],
|
|
2345
|
+
evidenceSpanIds: ['evidence_legacy_water_glass'],
|
|
2346
|
+
createdAt: savedAt,
|
|
2347
|
+
updatedAt: savedAt,
|
|
2348
|
+
metadata: {
|
|
2349
|
+
sectionPath: ['Water Glass', 'Definition'],
|
|
2350
|
+
version: 1,
|
|
2351
|
+
sourceHash: 'hash_legacy_water_glass',
|
|
2352
|
+
language: 'en',
|
|
2353
|
+
},
|
|
2354
|
+
},
|
|
2355
|
+
],
|
|
2356
|
+
evidenceSpans: [
|
|
2357
|
+
{
|
|
2358
|
+
id: 'evidence_legacy_water_glass',
|
|
2359
|
+
documentId: 'doc_legacy_water_glass',
|
|
2360
|
+
sourcePath,
|
|
2361
|
+
language: 'en',
|
|
2362
|
+
startOffset: 0,
|
|
2363
|
+
endOffset: atomContent.length,
|
|
2364
|
+
startLine: 1,
|
|
2365
|
+
endLine: 1,
|
|
2366
|
+
snippet: atomContent,
|
|
2367
|
+
sourceHash: 'hash_legacy_water_glass',
|
|
2368
|
+
createdAt: savedAt,
|
|
2369
|
+
},
|
|
2370
|
+
],
|
|
2371
|
+
relationEdges: [],
|
|
2372
|
+
temporalEdges: [],
|
|
2373
|
+
documents: [
|
|
2374
|
+
{
|
|
2375
|
+
documentId: 'doc_legacy_water_glass',
|
|
2376
|
+
sourcePath,
|
|
2377
|
+
sourceHash: 'hash_legacy_water_glass',
|
|
2378
|
+
version: 1,
|
|
2379
|
+
updatedAt: savedAt,
|
|
2380
|
+
atomStableKeyToId: [['legacy-water-glass-definition', 'atom_legacy_water_glass']],
|
|
2381
|
+
atomIds: ['atom_legacy_water_glass'],
|
|
2382
|
+
evidenceSpanIds: ['evidence_legacy_water_glass'],
|
|
2383
|
+
relationEdgeIds: [],
|
|
2384
|
+
temporalEdgeIds: [],
|
|
2385
|
+
},
|
|
2386
|
+
],
|
|
2387
|
+
activeStableKeyToAtomId: [['legacy-water-glass-definition', 'atom_legacy_water_glass']],
|
|
2388
|
+
activeAtomIds: ['atom_legacy_water_glass'],
|
|
2389
|
+
learnerStates: [],
|
|
2390
|
+
tutorTraces: [],
|
|
2391
|
+
ingestLatencyHistoryMs: [],
|
|
2392
|
+
recomputeLatencyHistoryMs: [],
|
|
2393
|
+
queryLatencyHistoryMs: [],
|
|
2394
|
+
latestIngestSummary: {
|
|
2395
|
+
ingestedDocuments: 1,
|
|
2396
|
+
changedDocuments: 1,
|
|
2397
|
+
deletedDocuments: 0,
|
|
2398
|
+
activeAtoms: 1,
|
|
2399
|
+
activeRelationEdges: 0,
|
|
2400
|
+
recomputedDynamicRelations: false,
|
|
2401
|
+
invalidatedRelationEdges: 0,
|
|
2402
|
+
regeneratedRelationEdges: 0,
|
|
2403
|
+
resolvedRelationRecomputeMode: 'none',
|
|
2404
|
+
relationRecomputeLatencyMs: 0,
|
|
2405
|
+
},
|
|
2406
|
+
conversationSessions: [],
|
|
2407
|
+
conversationTurns: [],
|
|
2408
|
+
conversationInvocations: [],
|
|
2409
|
+
userMemory: {},
|
|
2410
|
+
relationEdgeSignatures: [],
|
|
2411
|
+
};
|
|
2412
|
+
const store = {
|
|
2413
|
+
async loadSnapshot() {
|
|
2414
|
+
return snapshot;
|
|
2415
|
+
},
|
|
2416
|
+
async saveSnapshot() {
|
|
2417
|
+
return undefined;
|
|
2418
|
+
},
|
|
2419
|
+
getDiagnostics() {
|
|
2420
|
+
return {
|
|
2421
|
+
storeType: 'memory',
|
|
2422
|
+
exists: true,
|
|
2423
|
+
loaded: true,
|
|
2424
|
+
};
|
|
2425
|
+
},
|
|
2426
|
+
};
|
|
2427
|
+
const ragSufficiencyLlmJudge = jest.fn().mockResolvedValue({
|
|
2428
|
+
status: 'sufficient',
|
|
2429
|
+
score: 0.81,
|
|
2430
|
+
reasons: ['legacy_source_span_answerable'],
|
|
2431
|
+
degradationState: 'none',
|
|
2432
|
+
});
|
|
2433
|
+
const legacyPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
2434
|
+
nowProvider: () => new Date(savedAt),
|
|
2435
|
+
store,
|
|
2436
|
+
autoPersist: false,
|
|
2437
|
+
ragSufficiencyLlmJudge,
|
|
2438
|
+
});
|
|
2439
|
+
const response = await legacyPlatform.agentConversation({
|
|
2440
|
+
userId: 'agent_rag_llm_user',
|
|
2441
|
+
sessionId: 'session_rag_llm_scope',
|
|
2442
|
+
message: 'what is water glass?',
|
|
2443
|
+
persistMemory: false,
|
|
2444
|
+
});
|
|
2445
|
+
expect(response.trace.ragContextPack?.sourceDecisions).toEqual(expect.arrayContaining([
|
|
2446
|
+
expect.objectContaining({
|
|
2447
|
+
status: 'source_window_unavailable',
|
|
2448
|
+
}),
|
|
2449
|
+
]));
|
|
2450
|
+
expect(ragSufficiencyLlmJudge).toHaveBeenCalledTimes(1);
|
|
2451
|
+
expect(response.trace.ragSufficiencyReview).toEqual(expect.objectContaining({
|
|
2452
|
+
status: 'sufficient',
|
|
2453
|
+
score: 0.81,
|
|
2454
|
+
deterministic: false,
|
|
2455
|
+
llmJudgeUsed: true,
|
|
2456
|
+
degradationState: 'none',
|
|
2457
|
+
}));
|
|
2458
|
+
expect(response.trace.ragFailureClassifications).toEqual(expect.arrayContaining([
|
|
2459
|
+
expect.objectContaining({
|
|
2460
|
+
stage: 'parsing_source',
|
|
2461
|
+
code: 'source_window_unavailable',
|
|
2462
|
+
severity: 'warning',
|
|
2463
|
+
evidence: expect.arrayContaining(['source_window_unavailable']),
|
|
2464
|
+
}),
|
|
2465
|
+
]));
|
|
2466
|
+
});
|
|
2467
|
+
test('agent conversation exposes readiness and miss diagnostics when scoped retrieval is empty', async () => {
|
|
2468
|
+
const response = await platform.agentConversation({
|
|
2469
|
+
userId: 'agent_miss_user',
|
|
2470
|
+
sessionId: 'session_miss_scope',
|
|
2471
|
+
message: '什么是water glass',
|
|
2472
|
+
scope: {
|
|
2473
|
+
workspaceId: 'waterglass',
|
|
2474
|
+
corpusId: 'waterglass',
|
|
2475
|
+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
|
|
2476
|
+
},
|
|
2477
|
+
persistMemory: false,
|
|
2478
|
+
});
|
|
2479
|
+
expect(response.trace.workspaceReadiness).toEqual(expect.objectContaining({
|
|
2480
|
+
status: 'empty_store',
|
|
2481
|
+
}));
|
|
2482
|
+
expect(response.trace.missDiagnostics).toEqual(expect.objectContaining({
|
|
2483
|
+
reason: 'empty_store',
|
|
2484
|
+
}));
|
|
2485
|
+
expect(response.answer).toContain('当前范围');
|
|
2486
|
+
expect(response.answer).toContain('什么是water glass');
|
|
2487
|
+
expect(response.answer).not.toContain('No scoped knowledge points matched');
|
|
2488
|
+
expect(response.answerReleaseReview).toEqual(expect.objectContaining({
|
|
2489
|
+
decision: 'abstain',
|
|
2490
|
+
publicAnswer: response.answer,
|
|
2491
|
+
}));
|
|
2492
|
+
expect(response.trace.answerReleaseReview).toEqual(expect.objectContaining({
|
|
2493
|
+
decision: 'abstain',
|
|
2494
|
+
}));
|
|
2495
|
+
});
|
|
2496
|
+
test('memory policy diagnostics records history and improving trend snapshots', async () => {
|
|
2497
|
+
await platform.applyMemoryPolicy({
|
|
2498
|
+
userId: 'memory_diag_user',
|
|
2499
|
+
layer: 'session',
|
|
2500
|
+
operation: 'write',
|
|
2501
|
+
now: '2026-04-01T00:00:00.000Z',
|
|
2502
|
+
entries: [
|
|
2503
|
+
{
|
|
2504
|
+
key: 'expired-entry',
|
|
2505
|
+
value: 'Old expired note',
|
|
2506
|
+
tags: ['diagnostic'],
|
|
2507
|
+
confidence: 0.2,
|
|
2508
|
+
references: [],
|
|
2509
|
+
createdAt: '2026-04-01T00:00:00.000Z',
|
|
2510
|
+
updatedAt: '2026-04-01T00:00:00.000Z',
|
|
2511
|
+
expiresAt: '2026-04-02T00:00:00.000Z',
|
|
2512
|
+
},
|
|
2513
|
+
{
|
|
2514
|
+
key: 'stale-entry',
|
|
2515
|
+
value: 'Still relevant but stale',
|
|
2516
|
+
tags: ['diagnostic'],
|
|
2517
|
+
confidence: 0.3,
|
|
2518
|
+
references: [],
|
|
2519
|
+
createdAt: '2026-04-01T00:00:00.000Z',
|
|
2520
|
+
updatedAt: '2026-04-01T00:00:00.000Z',
|
|
2521
|
+
},
|
|
2522
|
+
],
|
|
2523
|
+
});
|
|
2524
|
+
const firstDiagnostics = await platform.queryMemoryPolicyDiagnostics({
|
|
2525
|
+
now: '2026-04-03T08:00:00.000Z',
|
|
2526
|
+
staleAfterHours: 12,
|
|
2527
|
+
nearExpiryHours: 6,
|
|
2528
|
+
lowConfidenceThreshold: 0.5,
|
|
2529
|
+
sampleLimit: 5,
|
|
2530
|
+
});
|
|
2531
|
+
expect(firstDiagnostics.summary.expiredEntries).toBeGreaterThan(0);
|
|
2532
|
+
expect(firstDiagnostics.summary.lowConfidenceEntries).toBeGreaterThan(0);
|
|
2533
|
+
expect(firstDiagnostics.summary.status).toBe('risk');
|
|
2534
|
+
await platform.applyMemoryPolicy({
|
|
2535
|
+
userId: 'memory_diag_user',
|
|
2536
|
+
layer: 'session',
|
|
2537
|
+
operation: 'evict',
|
|
2538
|
+
now: '2026-04-03T09:00:00.000Z',
|
|
2539
|
+
});
|
|
2540
|
+
await platform.applyMemoryPolicy({
|
|
2541
|
+
userId: 'memory_diag_user',
|
|
2542
|
+
layer: 'session',
|
|
2543
|
+
operation: 'write',
|
|
2544
|
+
now: '2026-04-03T09:00:00.000Z',
|
|
2545
|
+
entries: [
|
|
2546
|
+
{
|
|
2547
|
+
key: 'stale-entry',
|
|
2548
|
+
value: 'Refreshed confidence-backed note',
|
|
2549
|
+
tags: ['diagnostic'],
|
|
2550
|
+
confidence: 0.92,
|
|
2551
|
+
references: [],
|
|
2552
|
+
createdAt: '2026-04-01T00:00:00.000Z',
|
|
2553
|
+
updatedAt: '2026-04-03T09:00:00.000Z',
|
|
2554
|
+
},
|
|
2555
|
+
],
|
|
2556
|
+
});
|
|
2557
|
+
const secondDiagnostics = await platform.queryMemoryPolicyDiagnostics({
|
|
2558
|
+
now: '2026-04-03T09:00:00.000Z',
|
|
2559
|
+
staleAfterHours: 12,
|
|
2560
|
+
nearExpiryHours: 6,
|
|
2561
|
+
lowConfidenceThreshold: 0.5,
|
|
2562
|
+
sampleLimit: 5,
|
|
2563
|
+
});
|
|
2564
|
+
expect(secondDiagnostics.summary.healthScore).toBeGreaterThan(firstDiagnostics.summary.healthScore);
|
|
2565
|
+
expect(secondDiagnostics.summary.expiredEntries).toBe(0);
|
|
2566
|
+
const history = await platform.queryMemoryPolicyDiagnosticsHistory({
|
|
2567
|
+
limit: 5,
|
|
2568
|
+
});
|
|
2569
|
+
expect(history.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2570
|
+
expect(history.records[0]?.recordedAt).toBe('2026-04-03T09:00:00.000Z');
|
|
2571
|
+
const trend = await platform.queryMemoryPolicyDiagnosticsTrend({
|
|
2572
|
+
limit: 4,
|
|
2573
|
+
windowSize: 1,
|
|
2574
|
+
minSamples: 1,
|
|
2575
|
+
});
|
|
2576
|
+
expect(trend.status).toBe('improving');
|
|
2577
|
+
expect(Number(trend.deltas.healthScoreDelta || 0)).toBeGreaterThan(0);
|
|
2578
|
+
});
|
|
2579
|
+
test('query backend config drives runtime queries and comparison diagnostics persist history', async () => {
|
|
2580
|
+
await platform.ingestKnowledge({
|
|
2581
|
+
incremental: true,
|
|
2582
|
+
documents: [
|
|
2583
|
+
{
|
|
2584
|
+
documentId: 'doc_query_backend_a',
|
|
2585
|
+
sourcePath: 'Knowledge_Base/doc_query_backend_a.md',
|
|
2586
|
+
language: 'en',
|
|
2587
|
+
content: '# Evidence Router\nUse evidence-backed graph retrieval and relation paths.',
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
documentId: 'doc_query_backend_b',
|
|
2591
|
+
sourcePath: 'Knowledge_Base/doc_query_backend_b.md',
|
|
2592
|
+
language: 'en',
|
|
2593
|
+
content: '# Vector Signals\nSemantic overlap should preserve temporal and evidence coverage.',
|
|
2594
|
+
},
|
|
2595
|
+
],
|
|
2596
|
+
});
|
|
2597
|
+
expect(platform.getQueryBackendConfig().configuredBackend).toBe('local_hybrid');
|
|
2598
|
+
await platform.updateQueryBackendConfig({
|
|
2599
|
+
configuredBackend: 'keyword_only',
|
|
2600
|
+
});
|
|
2601
|
+
expect(platform.getQueryBackendConfig().configuredBackend).toBe('keyword_only');
|
|
2602
|
+
const keywordQuery = await platform.queryKnowledge({
|
|
2603
|
+
query: 'evidence graph retrieval',
|
|
2604
|
+
topK: 3,
|
|
2605
|
+
asOf: '2026-04-10T02:00:00.000Z',
|
|
2606
|
+
});
|
|
2607
|
+
expect(keywordQuery.trace.retrievalModes).toContain('keyword');
|
|
2608
|
+
expect(keywordQuery.trace.retrievalModes).not.toContain('vector_similarity');
|
|
2609
|
+
await platform.updateQueryBackendConfig({
|
|
2610
|
+
configuredBackend: 'local_vector',
|
|
2611
|
+
});
|
|
2612
|
+
expect(platform.getQueryBackendConfig().configuredBackend).toBe('local_vector');
|
|
2613
|
+
const vectorQuery = await platform.queryKnowledge({
|
|
2614
|
+
query: 'semantic overlap evidence',
|
|
2615
|
+
topK: 3,
|
|
2616
|
+
asOf: '2026-04-10T02:05:00.000Z',
|
|
2617
|
+
});
|
|
2618
|
+
expect(vectorQuery.trace.retrievalModes).toContain('vector_similarity');
|
|
2619
|
+
expect(vectorQuery.trace.vectorAcceleration).toBeDefined();
|
|
2620
|
+
const firstComparison = await platform.compareQueryBackends({
|
|
2621
|
+
query: 'evidence graph retrieval',
|
|
2622
|
+
topK: 3,
|
|
2623
|
+
leftBackend: 'local_hybrid',
|
|
2624
|
+
rightBackend: 'keyword_only',
|
|
2625
|
+
comparedAt: '2026-04-10T03:00:00.000Z',
|
|
2626
|
+
});
|
|
2627
|
+
expect(firstComparison.left.backend).toBe('local_hybrid');
|
|
2628
|
+
expect(firstComparison.right.backend).toBe('keyword_only');
|
|
2629
|
+
expect(typeof firstComparison.summary.overlapRatioPct).toBe('number');
|
|
2630
|
+
const secondComparison = await platform.compareQueryBackends({
|
|
2631
|
+
query: 'semantic overlap evidence',
|
|
2632
|
+
topK: 3,
|
|
2633
|
+
leftBackend: 'local_vector',
|
|
2634
|
+
rightBackend: 'local_hybrid',
|
|
2635
|
+
comparedAt: '2026-04-10T04:00:00.000Z',
|
|
2636
|
+
});
|
|
2637
|
+
expect(secondComparison.left.backend).toBe('local_vector');
|
|
2638
|
+
expect(secondComparison.right.backend).toBe('local_hybrid');
|
|
2639
|
+
const history = await platform.queryKnowledgeQueryBackendComparisonHistory({
|
|
2640
|
+
limit: 5,
|
|
2641
|
+
});
|
|
2642
|
+
expect(history.summary.totalRecords).toBe(2);
|
|
2643
|
+
expect(history.summary.returnedRecords).toBe(2);
|
|
2644
|
+
expect(history.records[0]?.comparedAt).toBe('2026-04-10T04:00:00.000Z');
|
|
2645
|
+
const trend = await platform.queryKnowledgeQueryBackendComparisonTrend({
|
|
2646
|
+
limit: 4,
|
|
2647
|
+
windowSize: 1,
|
|
2648
|
+
minSamples: 1,
|
|
2649
|
+
});
|
|
2650
|
+
expect(['improving', 'stable', 'regressing', 'insufficient_data']).toContain(trend.status);
|
|
2651
|
+
expect(trend.summary.totalRecords).toBe(2);
|
|
2652
|
+
expect(trend.summary.latestComparedAt).toBe('2026-04-10T04:00:00.000Z');
|
|
2653
|
+
const diagnostics = platform.getQueryBackendDiagnostics();
|
|
2654
|
+
expect(diagnostics.configuredBackend).toBe('local_vector');
|
|
2655
|
+
expect(diagnostics.comparisonTelemetry.totalComparisons).toBe(2);
|
|
2656
|
+
expect(diagnostics.runtime.ready).toBe(true);
|
|
2657
|
+
});
|
|
2658
|
+
test('knowledge staleness diagnostics and rebuild planning detect source drift', async () => {
|
|
2659
|
+
const tmpRoot = path.join(process.cwd(), 'tmp');
|
|
2660
|
+
fs.mkdirSync(tmpRoot, { recursive: true });
|
|
2661
|
+
const tempDir = fs.mkdtempSync(path.join(tmpRoot, 'klp-staleness-'));
|
|
2662
|
+
const filePath = path.join(tempDir, 'doc_staleness.md');
|
|
2663
|
+
const originalContent = '# Staleness\nFresh source content for diagnostics.\n';
|
|
2664
|
+
fs.writeFileSync(filePath, originalContent, 'utf8');
|
|
2665
|
+
try {
|
|
2666
|
+
await platform.ingestKnowledge({
|
|
2667
|
+
incremental: true,
|
|
2668
|
+
documents: [
|
|
2669
|
+
{
|
|
2670
|
+
documentId: 'doc_staleness_runtime',
|
|
2671
|
+
sourcePath: filePath,
|
|
2672
|
+
language: 'en',
|
|
2673
|
+
content: originalContent,
|
|
2674
|
+
},
|
|
2675
|
+
],
|
|
2676
|
+
});
|
|
2677
|
+
const freshDiagnostics = await platform.queryKnowledgeStalenessDiagnostics({
|
|
2678
|
+
limit: 5,
|
|
2679
|
+
});
|
|
2680
|
+
expect(freshDiagnostics.summary.upToDateDocuments).toBe(1);
|
|
2681
|
+
expect(freshDiagnostics.summary.staleDocuments).toBe(0);
|
|
2682
|
+
fs.writeFileSync(filePath, `${originalContent}\nChanged downstream source.\n`, 'utf8');
|
|
2683
|
+
const staleDiagnostics = await platform.queryKnowledgeStalenessDiagnostics({
|
|
2684
|
+
limit: 5,
|
|
2685
|
+
});
|
|
2686
|
+
expect(staleDiagnostics.summary.hashMismatchDocuments).toBe(1);
|
|
2687
|
+
expect(staleDiagnostics.summary.staleDocuments).toBe(1);
|
|
2688
|
+
expect(staleDiagnostics.records[0]?.status).toBe('hash_mismatch');
|
|
2689
|
+
const rebuild = await platform.rebuildKnowledgeFromStalenessDiagnostics({
|
|
2690
|
+
limit: 5,
|
|
2691
|
+
});
|
|
2692
|
+
expect(rebuild.mode).toBe('plan_only');
|
|
2693
|
+
expect(rebuild.rebuilt).toBe(0);
|
|
2694
|
+
expect(rebuild.plannedDocuments).toBe(1);
|
|
2695
|
+
}
|
|
2696
|
+
finally {
|
|
2697
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2700
|
+
test('learning quality and session plan quality diagnostics use execution-backed history', async () => {
|
|
2701
|
+
const ingest = await platform.ingestKnowledge({
|
|
2702
|
+
incremental: true,
|
|
2703
|
+
documents: [
|
|
2704
|
+
{
|
|
2705
|
+
documentId: 'doc_quality_a',
|
|
2706
|
+
sourcePath: 'Knowledge_Base/doc_quality_a.md',
|
|
2707
|
+
language: 'en',
|
|
2708
|
+
content: '# Quality A\nRetrieval practice with evidence-backed review loops.',
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
documentId: 'doc_quality_b',
|
|
2712
|
+
sourcePath: 'Knowledge_Base/doc_quality_b.md',
|
|
2713
|
+
language: 'en',
|
|
2714
|
+
content: '# Quality B\nDivergence tasks should stay budget-aware and evidence-bound.',
|
|
2715
|
+
},
|
|
2716
|
+
],
|
|
2717
|
+
});
|
|
2718
|
+
const atomA = ingest.atoms[0]?.id;
|
|
2719
|
+
const atomB = ingest.atoms[1]?.id;
|
|
2720
|
+
const evidenceA = ingest.atoms[0]?.evidenceSpanIds?.[0];
|
|
2721
|
+
const evidenceB = ingest.atoms[1]?.evidenceSpanIds?.[0];
|
|
2722
|
+
await platform.executeStudySessionPlan({
|
|
2723
|
+
userId: 'quality_runtime_user',
|
|
2724
|
+
executedAt: '2026-04-11T08:05:00.000Z',
|
|
2725
|
+
actionLimit: 3,
|
|
2726
|
+
sessionPlan: {
|
|
2727
|
+
userId: 'quality_runtime_user',
|
|
2728
|
+
generatedAt: '2026-04-11T08:00:00.000Z',
|
|
2729
|
+
actions: [
|
|
2730
|
+
{
|
|
2731
|
+
id: 'strong-action-1',
|
|
2732
|
+
atomId: atomA,
|
|
2733
|
+
kind: 'review',
|
|
2734
|
+
source: 'mastery_path',
|
|
2735
|
+
priority: 98,
|
|
2736
|
+
expectedGain: 0.18,
|
|
2737
|
+
rationale: 'Repair retrieval precision with cited evidence.',
|
|
2738
|
+
evidenceSpanIds: [evidenceA],
|
|
2739
|
+
relationPathAtomIds: [atomA],
|
|
2740
|
+
estimatedMinutes: 5,
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
id: 'strong-action-2',
|
|
2744
|
+
atomId: atomA,
|
|
2745
|
+
kind: 'quiz',
|
|
2746
|
+
source: 'misconception_remediation',
|
|
2747
|
+
priority: 94,
|
|
2748
|
+
expectedGain: 0.16,
|
|
2749
|
+
rationale: 'Verify misconception remediation with a targeted quiz.',
|
|
2750
|
+
evidenceSpanIds: [evidenceA],
|
|
2751
|
+
relationPathAtomIds: [atomA],
|
|
2752
|
+
estimatedMinutes: 4,
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
id: 'strong-action-3',
|
|
2756
|
+
atomId: atomB,
|
|
2757
|
+
kind: 'review',
|
|
2758
|
+
source: 'retrain_plan',
|
|
2759
|
+
priority: 88,
|
|
2760
|
+
expectedGain: 0.14,
|
|
2761
|
+
rationale: 'Stage a recovery-oriented follow-up before divergence.',
|
|
2762
|
+
evidenceSpanIds: [evidenceB],
|
|
2763
|
+
relationPathAtomIds: [atomB],
|
|
2764
|
+
estimatedMinutes: 4,
|
|
2765
|
+
},
|
|
2766
|
+
],
|
|
2767
|
+
signals: {
|
|
2768
|
+
misconceptions: [],
|
|
2769
|
+
dueRetrainAtoms: [atomB],
|
|
2770
|
+
masteryPathTargets: [atomA],
|
|
2771
|
+
divergenceTargets: [],
|
|
2772
|
+
},
|
|
2773
|
+
summary: {
|
|
2774
|
+
totalActions: 3,
|
|
2775
|
+
totalEstimatedMinutes: 13,
|
|
2776
|
+
evidenceCoverageRatio: 1,
|
|
2777
|
+
},
|
|
2778
|
+
},
|
|
2779
|
+
});
|
|
2780
|
+
await platform.executeStudySessionPlan({
|
|
2781
|
+
userId: 'quality_runtime_user',
|
|
2782
|
+
executedAt: '2026-04-12T08:05:00.000Z',
|
|
2783
|
+
actionLimit: 4,
|
|
2784
|
+
maxActions: 4,
|
|
2785
|
+
sessionPlan: {
|
|
2786
|
+
userId: 'quality_runtime_user',
|
|
2787
|
+
generatedAt: '2026-04-12T08:00:00.000Z',
|
|
2788
|
+
actions: [
|
|
2789
|
+
{
|
|
2790
|
+
id: 'weak-action-1',
|
|
2791
|
+
atomId: atomB,
|
|
2792
|
+
kind: 'transfer',
|
|
2793
|
+
source: 'divergence_path',
|
|
2794
|
+
priority: 36,
|
|
2795
|
+
expectedGain: 0.06,
|
|
2796
|
+
rationale: 'Push divergence without recovery context.',
|
|
2797
|
+
evidenceSpanIds: [],
|
|
2798
|
+
relationPathAtomIds: [atomB],
|
|
2799
|
+
estimatedMinutes: 4,
|
|
2800
|
+
},
|
|
2801
|
+
],
|
|
2802
|
+
signals: {
|
|
2803
|
+
misconceptions: [],
|
|
2804
|
+
dueRetrainAtoms: [],
|
|
2805
|
+
masteryPathTargets: [],
|
|
2806
|
+
divergenceTargets: [atomB],
|
|
2807
|
+
},
|
|
2808
|
+
summary: {
|
|
2809
|
+
totalActions: 1,
|
|
2810
|
+
totalEstimatedMinutes: 4,
|
|
2811
|
+
evidenceCoverageRatio: 0,
|
|
2812
|
+
},
|
|
2813
|
+
},
|
|
2814
|
+
});
|
|
2815
|
+
const learningHistory = await platform.queryLearningQualityHistory({
|
|
2816
|
+
userId: 'quality_runtime_user',
|
|
2817
|
+
limit: 5,
|
|
2818
|
+
});
|
|
2819
|
+
expect(learningHistory.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2820
|
+
expect(learningHistory.summary.returnedRecords).toBeGreaterThanOrEqual(2);
|
|
2821
|
+
expect(learningHistory.records[0]?.snapshot).toBeDefined();
|
|
2822
|
+
const learningTrend = await platform.queryLearningQualityTrend({
|
|
2823
|
+
userId: 'quality_runtime_user',
|
|
2824
|
+
limit: 4,
|
|
2825
|
+
windowSize: 1,
|
|
2826
|
+
minSamples: 1,
|
|
2827
|
+
});
|
|
2828
|
+
expect(['improving', 'stable', 'regressing', 'insufficient_data']).toContain(learningTrend.status);
|
|
2829
|
+
expect(learningTrend.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2830
|
+
const qualityThresholds = platform.getLearningQualityThresholds();
|
|
2831
|
+
expect(qualityThresholds.queryP95Ms).toBeGreaterThan(0);
|
|
2832
|
+
expect(qualityThresholds.evidenceBackedSuggestionRatioPct).toBeGreaterThan(0);
|
|
2833
|
+
const sessionQualityHistory = await platform.queryStudySessionPlanQualityHistory({
|
|
2834
|
+
userId: 'quality_runtime_user',
|
|
2835
|
+
limit: 5,
|
|
2836
|
+
});
|
|
2837
|
+
expect(sessionQualityHistory.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2838
|
+
expect(sessionQualityHistory.summary.overallPassRatePct).toBeLessThan(100);
|
|
2839
|
+
expect(Array.isArray(sessionQualityHistory.summary.commonFailedGates)).toBe(true);
|
|
2840
|
+
const sessionQualityTrend = await platform.queryStudySessionPlanQualityTrend({
|
|
2841
|
+
userId: 'quality_runtime_user',
|
|
2842
|
+
limit: 4,
|
|
2843
|
+
windowSize: 1,
|
|
2844
|
+
minSamples: 1,
|
|
2845
|
+
});
|
|
2846
|
+
expect(sessionQualityTrend.status).toBe('regressing');
|
|
2847
|
+
expect(sessionQualityTrend.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2848
|
+
const runtimeThresholds = await platform.queryStudySessionPlanQualityRuntimeThresholds({
|
|
2849
|
+
userId: 'quality_runtime_user',
|
|
2850
|
+
adaptiveThresholdsEnabled: true,
|
|
2851
|
+
historyLimit: 5,
|
|
2852
|
+
trendLimit: 4,
|
|
2853
|
+
trendWindowSize: 1,
|
|
2854
|
+
trendMinSamples: 1,
|
|
2855
|
+
});
|
|
2856
|
+
expect(runtimeThresholds.thresholds.minTotalActions).toBeGreaterThan(0);
|
|
2857
|
+
expect(runtimeThresholds.summary.totalRecords).toBeGreaterThanOrEqual(2);
|
|
2858
|
+
expect(runtimeThresholds.summary.latestEvaluatedAt).toBe('2026-04-12T08:05:00.000Z');
|
|
2859
|
+
});
|
|
2860
|
+
test('foundation readiness and backend baseline sufficiency reflect embedded graph and ann signals', async () => {
|
|
2861
|
+
const tmpRoot = path.join(process.cwd(), 'tmp');
|
|
2862
|
+
fs.mkdirSync(tmpRoot, { recursive: true });
|
|
2863
|
+
const tempDir = fs.mkdtempSync(path.join(tmpRoot, 'klp-foundation-'));
|
|
2864
|
+
const sqlitePath = path.join(tempDir, 'knowledge_graph_store.graphdb.v1.sqlite');
|
|
2865
|
+
const fallbackPath = path.join(tempDir, 'knowledge_graph_store.v1.json');
|
|
2866
|
+
let adapter = null;
|
|
2867
|
+
try {
|
|
2868
|
+
adapter = (0, store_1.createGraphDbSnapshotAdapter)({
|
|
2869
|
+
provider: 'sqlite',
|
|
2870
|
+
sqlitePath,
|
|
2871
|
+
adapterId: 'embedded-sqlite-graphdb',
|
|
2872
|
+
});
|
|
2873
|
+
expect(adapter).not.toBeNull();
|
|
2874
|
+
const readinessStore = (0, store_1.createKnowledgeGraphStore)({
|
|
2875
|
+
backend: 'graphdb',
|
|
2876
|
+
filePath: fallbackPath,
|
|
2877
|
+
graphdb: {
|
|
2878
|
+
adapter,
|
|
2879
|
+
},
|
|
2880
|
+
graphDbFallbackEnabled: false,
|
|
2881
|
+
graphDbOperationMode: 'ops_preferred',
|
|
2882
|
+
});
|
|
2883
|
+
const readinessPlatform = new KnowledgeLearningPlatform_1.KnowledgeLearningPlatform({
|
|
2884
|
+
nowProvider: () => new Date(nowIso),
|
|
2885
|
+
store: readinessStore,
|
|
2886
|
+
});
|
|
2887
|
+
await readinessPlatform.ingestKnowledge({
|
|
2888
|
+
incremental: true,
|
|
2889
|
+
documents: [
|
|
2890
|
+
{
|
|
2891
|
+
documentId: 'doc_foundation_readiness',
|
|
2892
|
+
sourcePath: 'Knowledge_Base/doc_foundation_readiness.md',
|
|
2893
|
+
language: 'en',
|
|
2894
|
+
content: '# Foundation Readiness\nEmbedded graph and ANN readiness should be measurable.',
|
|
2895
|
+
},
|
|
2896
|
+
],
|
|
2897
|
+
});
|
|
2898
|
+
const readiness = await readinessPlatform.getFoundationReadiness();
|
|
2899
|
+
expect(readiness.status).toBe('integrated');
|
|
2900
|
+
expect(readiness.decision).toBe('go');
|
|
2901
|
+
expect(readiness.baseline.storeType).toBe('sqlite');
|
|
2902
|
+
expect(readiness.baseline.graphBackendStatus).toBe('independent');
|
|
2903
|
+
expect(readiness.baseline.graphBackendSignalKind).toBe('embedded_graphdb');
|
|
2904
|
+
expect(readiness.baseline.graphBackendIndependent).toBe(true);
|
|
2905
|
+
expect(readiness.baseline.queryBackendDefaultMode).toBe('local_hybrid');
|
|
2906
|
+
expect(readiness.baseline.vectorAdapterStatus).toBe('independent');
|
|
2907
|
+
expect(readiness.baseline.vectorAdapterSignalKind).toBe('embedding_ann');
|
|
2908
|
+
expect(readiness.promotionCriteriaPassed).toBe(readiness.promotionCriteriaTotal);
|
|
2909
|
+
expect(readiness.promotionCriteriaSatisfiedIds).toEqual(expect.arrayContaining([
|
|
2910
|
+
'store_backend_evidence_present',
|
|
2911
|
+
'graph_backend_independent',
|
|
2912
|
+
'query_backend_boundary_present',
|
|
2913
|
+
'vector_backend_present',
|
|
2914
|
+
'vector_backend_independent',
|
|
2915
|
+
'docs_aligned',
|
|
2916
|
+
'readiness_verifier_present',
|
|
2917
|
+
]));
|
|
2918
|
+
expect(readiness.mandatoryChecks).toEqual(expect.arrayContaining([
|
|
2919
|
+
expect.objectContaining({
|
|
2920
|
+
gateId: 'foundation_runtime_proof',
|
|
2921
|
+
command: 'npm run verify:foundation:sqlite-runtime',
|
|
2922
|
+
}),
|
|
2923
|
+
expect.objectContaining({
|
|
2924
|
+
gateId: 'foundation_runtime_heavy_proof',
|
|
2925
|
+
command: 'npm run verify:foundation:sqlite-runtime:heavy',
|
|
2926
|
+
}),
|
|
2927
|
+
expect.objectContaining({
|
|
2928
|
+
gateId: 'foundation_runtime_matrix_proof',
|
|
2929
|
+
command: 'npm run verify:foundation:sqlite-runtime:matrix',
|
|
2930
|
+
}),
|
|
2931
|
+
expect.objectContaining({
|
|
2932
|
+
gateId: 'foundation_runtime_release_proof',
|
|
2933
|
+
command: 'npm run verify:foundation:sqlite-runtime:release',
|
|
2934
|
+
}),
|
|
2935
|
+
expect.objectContaining({
|
|
2936
|
+
gateId: 'vector_runtime_proof',
|
|
2937
|
+
command: 'npm run verify:foundation:ann-runtime',
|
|
2938
|
+
}),
|
|
2939
|
+
expect.objectContaining({
|
|
2940
|
+
gateId: 'vector_runtime_matrix_proof',
|
|
2941
|
+
command: 'npm run verify:foundation:ann-runtime:matrix',
|
|
2942
|
+
}),
|
|
2943
|
+
expect.objectContaining({
|
|
2944
|
+
gateId: 'vector_runtime_release_proof',
|
|
2945
|
+
command: 'npm run verify:foundation:ann-runtime:release',
|
|
2946
|
+
}),
|
|
2947
|
+
expect.objectContaining({
|
|
2948
|
+
gateId: 'foundation_release_evidence_freshness',
|
|
2949
|
+
command: 'npm run verify:foundation:release-evidence',
|
|
2950
|
+
}),
|
|
2951
|
+
expect.objectContaining({
|
|
2952
|
+
gateId: 'foundation_release_evidence_history',
|
|
2953
|
+
command: 'npm run verify:foundation:release-evidence:strict',
|
|
2954
|
+
}),
|
|
2955
|
+
]));
|
|
2956
|
+
const sufficiency = await readinessPlatform.getBackendBaselineSufficiency();
|
|
2957
|
+
expect(sufficiency.sufficient).toBe(true);
|
|
2958
|
+
expect(sufficiency.checks.knowledgeGraph.passed).toBe(true);
|
|
2959
|
+
expect(sufficiency.checks.queryBackend.passed).toBe(true);
|
|
2960
|
+
expect(sufficiency.checks.vectorIndex.passed).toBe(true);
|
|
2961
|
+
}
|
|
2962
|
+
finally {
|
|
2963
|
+
try {
|
|
2964
|
+
adapter?.close?.();
|
|
2965
|
+
}
|
|
2966
|
+
catch {
|
|
2967
|
+
}
|
|
2968
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
});
|