path-list-performance.test.mjs
103 lines 3.2 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago
1 /**
2 * Tier 6 — PERFORMANCE: list 200 + get 1 within local budget; no unbounded note scan.
3 *
4 * @see docs/KN-WORK-PATH-LIST-FREEZE.md §7
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11 import { saveFlowStore } from '../lib/flow/flow-store.mjs';
12 import { listLearningPaths, getLearningPath } from '../lib/path/path-store.mjs';
13
14 const __dirname = path.dirname(fileURLToPath(import.meta.url));
15 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-path-list-perf');
16 const P95_BUDGET_MS = 80;
17
18 function p95(samples) {
19 const sorted = [...samples].sort((a, b) => a - b);
20 const idx = Math.ceil(sorted.length * 0.95) - 1;
21 return sorted[Math.max(0, idx)];
22 }
23
24 beforeEach(() => {
25 fs.rmSync(tmpRoot, { recursive: true, force: true });
26 fs.mkdirSync(tmpRoot, { recursive: true });
27 });
28
29 afterEach(() => {
30 fs.rmSync(tmpRoot, { recursive: true, force: true });
31 });
32
33 describe('path-list performance', () => {
34 it('list 200 + get 1 stay within p95 budget and do not scan notes', () => {
35 const dataDir = path.join(tmpRoot, 'perf');
36 fs.mkdirSync(dataDir, { recursive: true });
37 const rows = [];
38 for (let i = 0; i < 200; i += 1) {
39 rows.push({
40 schema: 'knowtation.learning_path/v0',
41 path_id: `path_${String(i).padStart(16, '0')}`,
42 scope: 'personal',
43 status: 'active',
44 title: `P${i}`,
45 summary: `S${i}`,
46 goal: `G${i}`,
47 steps: [{ title: 'S', objective: 'O', source_document_ids: [] }],
48 current_step_index: 0,
49 step_count: 1,
50 next_step_title: 'S',
51 active_decisions: '',
52 workspace_id: 'ws-personal',
53 note_path: null,
54 created: '2026-08-18T00:00:00Z',
55 updated: '2026-08-18T00:00:00Z',
56 });
57 }
58 saveFlowStore(dataDir, {
59 vaults: {
60 v: {
61 flows: [],
62 steps: [],
63 runs: [],
64 candidates: [],
65 projections: [],
66 tasks: [],
67 task_loops: [],
68 orchestrator_graphs: [],
69 learning_paths: rows,
70 },
71 },
72 });
73
74 const listSamples = [];
75 const getSamples = [];
76 for (let i = 0; i < 40; i += 1) {
77 const t0 = performance.now();
78 const listed = listLearningPaths(dataDir, 'v', {
79 visibleScopes: new Set(['personal']),
80 filterScopes: new Set(['personal']),
81 effectiveScope: 'personal',
82 });
83 listSamples.push(performance.now() - t0);
84 assert.equal(listed.paths.length, 200);
85 const t1 = performance.now();
86 const got = getLearningPath(dataDir, 'v', 'path_0000000000000000', {
87 visibleScopes: new Set(['personal']),
88 });
89 getSamples.push(performance.now() - t1);
90 assert.ok(got);
91 }
92
93 assert.ok(p95(listSamples) < P95_BUDGET_MS, `list p95 ${p95(listSamples)} >= ${P95_BUDGET_MS}`);
94 assert.ok(p95(getSamples) < P95_BUDGET_MS, `get p95 ${p95(getSamples)} >= ${P95_BUDGET_MS}`);
95
96 const storeSrc = fs.readFileSync(
97 path.join(__dirname, '..', 'lib/path/path-store.mjs'),
98 'utf8',
99 );
100 assert.equal(storeSrc.includes('readdirSync'), false);
101 assert.equal(storeSrc.includes('listNotes'), false);
102 });
103 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago