section-source-hosted-auth-spec.test.mjs file-level

at sha256:8 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:4 fix(security): pin patched transitive deps to clear Dependabot moderate… · aaronrene · Jun 11, 2026
1 /**
2 * SectionSource hosted authorization review spec tests.
3 *
4 * Phase 1H accepts hosted authorization review only. It must not register
5 * hosted get_section_source, add hosted ACLs, add Hub routes, add Scooling
6 * runtime behavior, return section bodies, snippets, summaries, PageIndex, OCR,
7 * LLM calls, or provider routing.
8 */
9 import { describe, it, afterEach } from 'node:test';
10 import assert from 'node:assert/strict';
11 import fs from 'fs';
12 import path from 'path';
13 import { fileURLToPath } from 'url';
14 import { Client } from '@modelcontextprotocol/sdk/client/index.js';
15 import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
16
17 import { createHostedMcpServer } from '../hub/gateway/mcp-hosted-server.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const repoRoot = path.dirname(__dirname);
21 const specPath = path.join(repoRoot, 'docs', 'SECTION-SOURCE-HOSTED-AUTHORIZATION-REVIEW-SPEC.md');
22
23 function readRepoFile(relativePath) {
24 return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8');
25 }
26
27 function makeCtx(overrides = {}) {
28 return {
29 userId: 'google:actor',
30 canisterUserId: 'google:owner',
31 vaultId: 'vault-section-source',
32 role: 'viewer',
33 token: 'tok-section-source',
34 canisterUrl: 'http://canister.test:4322',
35 bridgeUrl: 'http://bridge.test:4321',
36 canisterAuthSecret: 'gw-secret-section-source',
37 ...overrides,
38 };
39 }
40
41 function installFetchMock(handler) {
42 const calls = [];
43 const origFetch = globalThis.fetch;
44 globalThis.fetch = async (url, init) => {
45 calls.push({ url: String(url), init });
46 return handler(String(url), init, calls);
47 };
48 return {
49 calls,
50 restore() {
51 globalThis.fetch = origFetch;
52 },
53 };
54 }
55
56 async function connectPair(ctx = makeCtx()) {
57 const mcpServer = createHostedMcpServer(ctx);
58 const client = new Client({ name: 'hosted-section-source-auth-review', version: '0.0.1' });
59 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
60 await mcpServer.connect(serverTransport);
61 await client.connect(clientTransport);
62 return { client };
63 }
64
65 function hostedRuntimeSource() {
66 return [
67 readRepoFile('hub/gateway/mcp-hosted-server.mjs'),
68 readRepoFile('hub/gateway/mcp-tool-acl.mjs'),
69 readRepoFile('hub/gateway/server.mjs'),
70 ].join('\n');
71 }
72
73 describe('SectionSource hosted authorization review spec', () => {
74 let mock;
75 let client;
76
77 afterEach(async () => {
78 try {
79 await client?.close();
80 } catch (_) {}
81 mock?.restore?.();
82 });
83
84 it('unit: spec covers required hosted authorization decision areas', () => {
85 const spec = fs.readFileSync(specPath, 'utf8');
86 const requiredSections = [
87 '## Review Decision',
88 '## Future Hosted Tool Shape',
89 '## Hosted Authorization Requirements',
90 '## Accepted Future Output',
91 '## Explicitly Excluded Output',
92 '## Logging And Error Boundary',
93 '## Deletion, Export, And Staleness',
94 '## Prompt-Injection Handling',
95 '## Scooling Boundary',
96 '## Seven-Tier Test Requirements',
97 '## Stop Conditions',
98 '## Acceptance Criteria',
99 ];
100
101 for (const section of requiredSections) {
102 assert.equal(spec.includes(section), true, `${section} is documented`);
103 }
104 assert.match(spec, /Phase 1H accepts the hosted authorization review only/);
105 assert.match(spec, /active hosted vault id/);
106 assert.match(spec, /effective canister user id/);
107 });
108
109 it('integration: later hosted runtime exposes get_section_source through ACL only', () => {
110 const acl = readRepoFile('hub/gateway/mcp-tool-acl.mjs');
111 const hosted = readRepoFile('hub/gateway/mcp-hosted-server.mjs');
112
113 assert.match(readRepoFile('docs/SECTION-SOURCE-V0-SPEC.md'), /### Phase 1L: Hosted MCP Implementation/);
114 assert.equal(acl.includes('get_section_source'), true);
115 assert.equal(hosted.includes('get_section_source'), true);
116 assert.equal(hosted.includes('buildSectionSource'), true);
117 assert.equal(hosted.includes('readSectionSource'), false);
118 });
119
120 it('end-to-end: hosted MCP roles can list get_section_source after the later runtime phase', async () => {
121 mock = installFetchMock(() => ({
122 ok: true,
123 status: 200,
124 json: async () => ({}),
125 text: async () => '{}',
126 }));
127
128 for (const role of ['viewer', 'editor', 'evaluator', 'admin']) {
129 ({ client } = await connectPair(makeCtx({ role })));
130 const { tools } = await client.listTools();
131 assert.equal(tools.some((tool) => tool.name === 'get_section_source'), true, `${role} can list get_section_source`);
132 await client.close();
133 client = undefined;
134 }
135 assert.equal(mock.calls.length, 0);
136 });
137
138 it('stress: hosted authorization review checks remain bounded to contract files', () => {
139 const started = Date.now();
140 const files = [
141 'docs/SECTION-SOURCE-HOSTED-AUTHORIZATION-REVIEW-SPEC.md',
142 'docs/SECTION-SOURCE-V0-SPEC.md',
143 'docs/SECTION-SOURCE-MCP-IMPLEMENTATION-SPEC.md',
144 'hub/gateway/mcp-hosted-server.mjs',
145 'hub/gateway/mcp-tool-acl.mjs',
146 ].map((relativePath) => readRepoFile(relativePath));
147 const elapsedMs = Date.now() - started;
148
149 assert.equal(files.length, 5);
150 assert.ok(elapsedMs < 200, `expected bounded hosted review check under 200ms, got ${elapsedMs}ms`);
151 });
152
153 it('data-integrity: hosted review adds no writes, sidecars, indexes, vectors, or summaries', () => {
154 const implementation = [
155 readRepoFile('lib/section-source.mjs'),
156 readRepoFile('lib/section-source-note.mjs'),
157 ].join('\n');
158 const runtime = hostedRuntimeSource();
159
160 assert.equal(readRepoFile('hub/gateway/server.mjs').includes('get_section_source'), false);
161 assert.equal(runtime.includes('get_section_source'), true);
162 assert.equal(runtime.includes('buildSectionSource'), true);
163 assert.equal(runtime.includes('readSectionSource'), false);
164 assert.doesNotMatch(implementation, /\bwriteFile(Sync)?\s*\(/);
165 assert.doesNotMatch(implementation, /\bappendFile(Sync)?\s*\(/);
166 assert.doesNotMatch(implementation, /\bsidecar[A-Za-z0-9_]*\s*=/);
167 assert.doesNotMatch(implementation, /\bvector[A-Za-z0-9_]*\s*=/);
168 assert.doesNotMatch(implementation, /\bsummary[A-Za-z0-9_]*\s*=/);
169 });
170
171 it('performance: spec requires one-note reads and no external providers', () => {
172 const spec = fs.readFileSync(specPath, 'utf8');
173
174 assert.match(spec, /The future hosted tool must read one note only/);
175 assert.match(spec, /The future hosted tool must not scan the whole vault/);
176 assert.match(spec, /The future hosted tool must not call external providers/);
177 });
178
179 it('security: review blocks hosted runtime, body, snippet, search, persistence, and Scooling exposure', () => {
180 const spec = fs.readFileSync(specPath, 'utf8');
181 const runtime = hostedRuntimeSource();
182 const blockedPhrases = [
183 'Hosted runtime exposure remains blocked in this phase.',
184 'No note body text appears in hosted SectionSource output.',
185 'No section body text appears in hosted SectionSource output.',
186 'No snippets appear in hosted SectionSource output.',
187 'No full frontmatter appears in hosted SectionSource output.',
188 'No absolute filesystem paths appear in hosted SectionSource output.',
189 'Hosted, Scooling, classroom, search, persistence, and provider exposure remain blocked.',
190 ];
191
192 for (const phrase of blockedPhrases) {
193 assert.equal(spec.includes(phrase), true, `${phrase} is documented`);
194 }
195 assert.equal(readRepoFile('hub/gateway/server.mjs').includes('get_section_source'), false);
196 assert.equal(runtime.includes('readSectionSource'), false);
197 });
198 });