section-source-mcp-spec.test.mjs
130 lines 5.6 KB
Raw
sha256:c2dbf04d56308f3bbf2d06e6d2eb022b8948b1e827195fe525a44e5e18d5f9c0 feat(auth): Phase B Connect cloud agent (RFC 8628) + Hermes… Human minor ⚠ breaking 11 days 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:93bcf8f9bd56d8c5b9339f4ec73b9ebd66571398d56262d38eedc2cfa9db9882 fix(test): align Band B landing assertion with desktop MCP … Human 11 days ago