section-source-hub-ui-spec.test.mjs
235 lines 9.1 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 /**
2 * SectionSource Hub UI implementation spec tests.
3 *
4 * Phase 1O accepted Hub UI planning only. The later runtime phase may add the
5 * intended Hub UI surface while still blocking canister routes, search,
6 * persistence, Scooling runtime behavior, section bodies, snippets, providers,
7 * resource URIs, PageIndex, OCR, or LLM calls.
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-HUB-UI-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-ui-spec',
32 role: 'viewer',
33 token: 'tok-section-source-ui-spec',
34 canisterUrl: 'http://canister.test:4322',
35 bridgeUrl: 'http://bridge.test:4321',
36 canisterAuthSecret: 'gw-secret-section-source-ui-spec',
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: 'section-source-hub-ui-spec', 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 hubUiSource() {
66 return [
67 readRepoFile('web/hub/index.html'),
68 readRepoFile('web/hub/hub.js'),
69 readRepoFile('web/hub/hub.css'),
70 readRepoFile('web/hub/onboarding-wizard.mjs'),
71 readRepoFile('web/hub/consolidation-ui-logic.mjs'),
72 readRepoFile('web/hub/hub-list-sort.mjs'),
73 ].join('\n');
74 }
75
76 describe('SectionSource Hub UI implementation spec', () => {
77 let mock;
78 let client;
79
80 afterEach(async () => {
81 try {
82 await client?.close();
83 } catch (_) {}
84 mock?.restore?.();
85 });
86
87 it('unit: spec covers required Hub UI decision areas', () => {
88 const spec = fs.readFileSync(specPath, 'utf8');
89 const requiredSections = [
90 '## Planning Decision',
91 '## Future UI Entry Point',
92 '## UI Auth And Session Expectations',
93 '## Active Vault Boundary',
94 '## API Call Shape',
95 '## Rendering Allowlist',
96 '## Explicitly Excluded UI Display',
97 '## Loading, Empty, And Error States',
98 '## Prompt-Injection Handling',
99 '## Logging And Telemetry Exclusions',
100 '## Deletion, Export, And Staleness',
101 '## Accessibility Expectations',
102 '## No Write-Back Behavior',
103 '## Scooling Consumption Boundary',
104 '## Seven-Tier Test Requirements',
105 '## Contract Guards',
106 '## Stop Conditions',
107 '## Acceptance Criteria',
108 ];
109 const requiredPhrases = [
110 'Phase 1O accepts the Hub UI implementation specification only.',
111 'GET /api/v1/section-source?path=<encodeURIComponent(path)>',
112 'Heading text and heading paths must be rendered as escaped text.',
113 'Loading: show generic progress without note body previews.',
114 'Prompt-like headings that ask a model, browser, extension, or user to reveal secrets',
115 'The future UI must not log or send telemetry containing:',
116 'Section hierarchy must be navigable by keyboard.',
117 'SectionSource UI consumption must not grant write permissions.',
118 'Scooling remains a downstream consumer behind its adapter boundary.',
119 ];
120
121 for (const section of requiredSections) {
122 assert.equal(spec.includes(section), true, `${section} is documented`);
123 }
124 for (const phrase of requiredPhrases) {
125 assert.equal(spec.includes(phrase), true, `${phrase} is documented`);
126 }
127 });
128
129 it('integration: Hub UI runtime is present while REST and OpenAPI stay available', () => {
130 const ui = hubUiSource();
131 const gateway = readRepoFile('hub/gateway/server.mjs');
132 const openapi = readRepoFile('docs/openapi.yaml');
133
134 assert.equal(ui.includes('/api/v1/section-source'), true);
135 assert.equal(ui.includes('section-source'), true);
136 assert.equal(ui.includes('SectionSource'), true);
137 assert.equal(ui.includes('section_source/v0'), true);
138 assert.equal(gateway.includes('/api/v1/section-source'), true);
139 assert.equal(openapi.includes('/section-source'), true);
140 assert.equal(openapi.includes('SectionSource'), true);
141 });
142
143 it('end-to-end: hosted MCP get_section_source remains available while Hub UI is present', async () => {
144 mock = installFetchMock(() => ({
145 ok: true,
146 status: 200,
147 json: async () => ({
148 path: 'ignored.md',
149 frontmatter: '{"title":"Hosted MCP Still Available"}',
150 body: '# A\n\nPrivate body must not leak.',
151 }),
152 text: async () => '{}',
153 }));
154 ({ client } = await connectPair());
155
156 const { tools } = await client.listTools();
157 const result = await client.callTool({
158 name: 'get_section_source',
159 arguments: { path: 'inbox/one.md' },
160 });
161 const data = JSON.parse(result.content[0].text);
162
163 assert.equal(tools.some((tool) => tool.name === 'get_section_source'), true);
164 assert.equal(result.isError, undefined);
165 assert.equal(data.schema, 'knowtation.section_source/v0');
166 assert.equal(JSON.stringify(data).includes('Private body must not leak'), false);
167 assert.equal(hubUiSource().includes('/api/v1/section-source'), true);
168 });
169
170 it('stress: planning checks stay bounded to UI, REST, OpenAPI, and contract files', () => {
171 const started = Date.now();
172 const files = [
173 'docs/SECTION-SOURCE-HUB-UI-SPEC.md',
174 'docs/SECTION-SOURCE-HUB-REST-OPENAPI-SPEC.md',
175 'docs/SECTION-SOURCE-V0-SPEC.md',
176 'docs/openapi.yaml',
177 'hub/gateway/server.mjs',
178 'web/hub/index.html',
179 'web/hub/hub.js',
180 'web/hub/hub.css',
181 ].map((relativePath) => readRepoFile(relativePath));
182 const elapsedMs = Date.now() - started;
183
184 assert.equal(files.length, 8);
185 assert.ok(elapsedMs < 300, `expected bounded Hub UI spec check under 300ms, got ${elapsedMs}ms`);
186 });
187
188 it('data-integrity: runtime adds no UI state stores, writes, sidecars, indexes, vectors, or summaries', () => {
189 const ui = hubUiSource();
190 const js = readRepoFile('web/hub/hub.js');
191 const start = js.indexOf('const SECTION_SOURCE_SCHEMA');
192 const end = js.indexOf('function switchNoteToReadMode', start);
193 assert.notEqual(start, -1);
194 assert.notEqual(end, -1);
195 const runtime = js.slice(start, end);
196
197 assert.equal(ui.includes('/api/v1/section-source'), true);
198 assert.doesNotMatch(runtime, /\b(localStorage|sessionStorage|write|post|put|delete|index|vector|memory|sidecar)\b/i);
199 assert.doesNotMatch(runtime, /\b(summarize|summarization|summaries)\b/i);
200 });
201
202 it('performance: spec requires one-note UI calls and no scans or providers', () => {
203 const spec = fs.readFileSync(specPath, 'utf8');
204
205 assert.match(spec, /Future UI calls must request one note only/);
206 assert.match(spec, /Future UI calls must not scan the whole vault/);
207 assert.match(spec, /Future UI calls must not call bridge search/);
208 assert.match(spec, /Future UI calls must not call external providers/);
209 assert.match(spec, /Rendering size must remain bounded by accepted SectionSource caps/);
210 });
211
212 it('security: runtime blocks body, snippet, provider, resource, Scooling, and write-back exposure', () => {
213 const spec = fs.readFileSync(specPath, 'utf8');
214 const ui = hubUiSource();
215 const blockedPhrases = [
216 'Hub UI exposure remains blocked in this phase.',
217 'No note body text appears in future SectionSource UI output.',
218 'No section body text appears in future SectionSource UI output.',
219 'No snippets appear in future SectionSource UI output.',
220 'No full frontmatter appears in future SectionSource UI output.',
221 'No absolute filesystem paths appear in future SectionSource UI output or errors.',
222 'No raw canister payload appears in future SectionSource UI output or errors.',
223 'No provider payload appears in future SectionSource UI output or errors.',
224 'No MCP resource URI appears for SectionSource UI content.',
225 'Search, persistence, Scooling, PageIndex, OCR, LLM, provider, and write-back exposure',
226 ];
227
228 for (const phrase of blockedPhrases) {
229 assert.equal(spec.includes(phrase), true, `${phrase} is documented`);
230 }
231 assert.equal(ui.includes('/api/v1/section-source'), true);
232 assert.equal(ui.includes('SectionSource'), true);
233 assert.equal(ui.includes('data-section-source-panel'), true);
234 });
235 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 3 days ago