feat(page-data): add private and public dataset API (phase 1-3)

Extract UserDataSpaceService for shared SQLite access, wire logged-in Page
Data routes, and add public insert plus password-token read/update/delete with
policy storage, rate limits, and regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-08 12:44:23 +08:00
parent 1c8e0db256
commit 8d01553469
17 changed files with 2587 additions and 189 deletions
+60
View File
@@ -129,6 +129,66 @@ test('sandbox MCP exposes and protects the user private data space', async (t) =
assert.match(dotCommand.result.content[0].text, /dot command/);
});
test('sandbox MCP registers datasets and page policies for Page Data API', async (t) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mindspace-sandbox-page-data-'));
const server = startSandbox(root, {
ALLOWED_TOOLS: 'private_data_execute,private_data_register_dataset,private_data_set_page_policy',
PRIVATE_DATA_USER_ID: 'user-1',
});
t.after(() => server.child.kill());
await server.request('initialize');
const create = await server.request('tools/call', {
name: 'private_data_execute',
arguments: {
sql: `CREATE TABLE signups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT
);`,
},
});
assert.equal(create.result.isError, false);
const register = await server.request('tools/call', {
name: 'private_data_register_dataset',
arguments: {
name: 'signups',
table: 'signups',
actions: ['read', 'insert'],
columns: {
read: ['id', 'name', 'phone'],
insert: ['name', 'phone'],
},
},
});
assert.equal(register.result.isError, false);
assert.match(register.result.content[0].text, /"name": "signups"/);
const policy = await server.request('tools/call', {
name: 'private_data_set_page_policy',
arguments: {
pageId: 'page-1',
accessMode: 'public',
datasets: {
signups: {
insert: true,
columns: { insert: ['name', 'phone'] },
},
},
},
});
assert.equal(policy.result.isError, false);
assert.equal(
fs.existsSync(path.join(root, '.mindspace', 'page-data-policies', 'page-1.json')),
true,
);
const saved = JSON.parse(fs.readFileSync(path.join(root, '.mindspace', 'page-data-policies', 'page-1.json'), 'utf8'));
assert.equal(saved.pageId, 'page-1');
assert.equal(saved.datasets.signups.insert, true);
});
test('sandbox MCP exposes schedule tools only when schedule env is configured', async (t) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mindspace-sandbox-schedule-'));
const server = startSandbox(root, {