section-source-mcp-spec.test.mjs
130 lines 5.6 KB
Raw
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 14 hours ago
1 /**
2 * SectionSource self-hosted MCP implementation tests.
3 *
4 * Phase 1G accepts the self-hosted MCP implementation only. It must not add
5 * hosted MCP, Hub, search, persistence, Scooling runtime behavior, section
6 * bodies, snippets, summaries, PageIndex, OCR, LLM calls, or provider routing.
7 */
8 import { describe, it } from 'node:test';
9 import assert from 'node:assert/strict';
10 import fs from 'fs';
11 import path from 'path';
12 import { fileURLToPath } from 'url';
13
14 import { readSectionSource } from '../lib/section-source-note.mjs';
15
16 const __dirname = path.dirname(fileURLToPath(import.meta.url));
17 const repoRoot = path.dirname(__dirname);
18 const fixtureVault = path.join(__dirname, 'fixtures', 'vault-fs');
19 const specPath = path.join(repoRoot, 'docs', 'SECTION-SOURCE-MCP-IMPLEMENTATION-SPEC.md');
20
21 function readRepoFile(relativePath) {
22 return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
23 }
24
25 describe('SectionSource self-hosted MCP implementation spec', () => {
26 it('unit: spec covers the required self-hosted MCP decision areas', () => {
27 const spec = fs.readFileSync(specPath, 'utf8');
28 const requiredSections = [
29 '## Proposed Tool',
30 '## Input Schema',
31 '## Accepted Output',
32 '## Explicitly Excluded Output',
33 '## Error Behavior',
34 '## Authorization And Path Safety',
35 '## Transport Boundaries',
36 '## Resource Boundary',
37 '## Seven-Tier Test Requirements',
38 '## Stop Conditions',
39 '## Acceptance Criteria',
40 ];
41
42 for (const section of requiredSections) {
43 assert.equal(spec.includes(section), true, `${section} is documented`);
44 }
45 assert.match(spec, /get_section_source/);
46 assert.match(spec, /This phase implements only the self-hosted MCP tool/);
47 });
48
49 it('integration: current SectionSource local integration remains the expected MCP target', () => {
50 const source = readSectionSource(fixtureVault, 'inbox/one.md');
51 const serialized = JSON.stringify(source);
52
53 assert.equal(source.schema, 'knowtation.section_source/v0');
54 assert.equal(source.path, 'inbox/one.md');
55 assert.equal(source.sections.every((section) => section.body_returned === false), true);
56 assert.equal(source.sections.every((section) => section.snippet_returned === false), true);
57 assert.equal(Object.hasOwn(source, 'body'), false);
58 assert.equal(Object.hasOwn(source, 'frontmatter'), false);
59 assert.equal(Object.hasOwn(source, 'snippet'), false);
60 assert.equal(serialized.includes('Body of inbox one'), false);
61 });
62
63 it('end-to-end: self-hosted and hosted MCP are wired while Hub and Scooling remain blocked', () => {
64 const selfHostedMcp = readRepoFile('mcp/create-server.mjs');
65 const hostedMcp = readRepoFile('hub/gateway/mcp-hosted-server.mjs');
66 const hostedAcl = readRepoFile('hub/gateway/mcp-tool-acl.mjs');
67 const hubServer = readRepoFile('hub/gateway/server.mjs');
68 assert.equal(selfHostedMcp.includes('get_section_source'), true);
69 assert.equal(selfHostedMcp.includes('readSectionSource'), true);
70 assert.equal(hostedMcp.includes('get_section_source'), true);
71 assert.equal(hostedMcp.includes('buildSectionSource'), true);
72 assert.equal(hostedAcl.includes('get_section_source'), true);
73 assert.equal(hostedMcp.includes('readSectionSource'), false);
74 assert.equal(hubServer.includes('get_section_source'), false);
75 assert.equal(hubServer.includes('section_source/v0'), false);
76 });
77
78 it('stress: MCP spec checks remain bounded to contract files', () => {
79 const started = Date.now();
80 const files = [
81 'docs/SECTION-SOURCE-MCP-IMPLEMENTATION-SPEC.md',
82 'docs/SECTION-SOURCE-CLI-IMPLEMENTATION-SPEC.md',
83 'docs/SECTION-SOURCE-TRANSPORT-PLAN.md',
84 'docs/SECTION-SOURCE-V0-SPEC.md',
85 'lib/section-source-note.mjs',
86 ].map((relativePath) => readRepoFile(relativePath));
87 const elapsedMs = Date.now() - started;
88
89 assert.equal(files.length, 5);
90 assert.ok(elapsedMs < 200, `expected bounded MCP spec check under 200ms, got ${elapsedMs}ms`);
91 });
92
93 it('data-integrity: MCP implementation adds no writes, sidecars, indexes, vectors, or summaries', () => {
94 const implementation = [
95 readRepoFile('lib/section-source.mjs'),
96 readRepoFile('lib/section-source-note.mjs'),
97 readRepoFile('mcp/create-server.mjs'),
98 ].join('\n');
99
100 assert.doesNotMatch(implementation, /\bwriteFile(Sync)?\s*\(/);
101 assert.doesNotMatch(implementation, /\bappendFile(Sync)?\s*\(/);
102 assert.doesNotMatch(implementation, /\bsidecar[A-Za-z0-9_]*\s*=/);
103 assert.doesNotMatch(implementation, /\bvector[A-Za-z0-9_]*\s*=/);
104 assert.doesNotMatch(implementation, /\bsummary[A-Za-z0-9_]*\s*=/);
105 });
106
107 it('performance: spec requires one-note reads and no external providers', () => {
108 const spec = fs.readFileSync(specPath, 'utf8');
109
110 assert.match(spec, /The tool reads one note only/);
111 assert.match(spec, /The tool does not scan the whole vault/);
112 assert.match(spec, /The tool does not call external providers/);
113 });
114
115 it('security: implementation spec blocks body, snippet, hosted, search, persistence, and Scooling exposure', () => {
116 const spec = fs.readFileSync(specPath, 'utf8');
117 const blockedPhrases = [
118 'No note body text appears in output.',
119 'No section body text appears in output.',
120 'No snippets appear in output.',
121 'No full frontmatter appears in output.',
122 'No MCP resource URI appears in output.',
123 'Hosted, Scooling, classroom, search, and persistence exposure remain blocked.',
124 ];
125
126 for (const phrase of blockedPhrases) {
127 assert.equal(spec.includes(phrase), true, `${phrase} is documented`);
128 }
129 });
130 });
File History 1 commit
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 14 hours ago