2026-07-02 21:03:14 +08:00
|
|
|
|
# MindSpace Service Contract
|
|
|
|
|
|
|
|
|
|
|
|
日期: 2026-07-02
|
|
|
|
|
|
|
|
|
|
|
|
状态: Draft for P1/P2
|
|
|
|
|
|
|
2026-07-03 16:34:02 +08:00
|
|
|
|
生产拓扑更新(2026-07-03):
|
|
|
|
|
|
|
|
|
|
|
|
- 103 的 MindSpace Service 已独立部署在 `/Users/john/MindSpace`,服务为 `cn.tkmind.mindspace-service`,端口 `8082`。
|
|
|
|
|
|
- Portal live 目录仍是 `/Users/john/Project/Memind`,但它不再是 MindSpace Service 的根目录。
|
|
|
|
|
|
- `/Users/john/Project/Memind/MindSpace` 只能当旧链路兼容/存量目录处理;新排障和新配置必须优先看 [103 runtime topology](./103-runtime-topology.md)。
|
|
|
|
|
|
|
2026-07-02 21:03:14 +08:00
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 定义 MindSpace 从 Memind Portal 单体拆出前必须稳定下来的 API、存储、URL、权限和 package 契约。
|
|
|
|
|
|
- 允许先在单体内实现,再独立成进程。
|
|
|
|
|
|
- 保证 Memind App 和 Goose Runtime 不依赖 MindSpace 的物理部署位置。
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 边界原则
|
|
|
|
|
|
|
|
|
|
|
|
MindSpace Service 是以下对象的唯一权威:
|
|
|
|
|
|
|
|
|
|
|
|
- Space
|
|
|
|
|
|
- Category
|
|
|
|
|
|
- Asset
|
|
|
|
|
|
- Asset Version
|
|
|
|
|
|
- Page
|
|
|
|
|
|
- Page Version
|
|
|
|
|
|
- Publication
|
|
|
|
|
|
- Conversation Package
|
|
|
|
|
|
- Package Manifest
|
|
|
|
|
|
- Canonical Public URL
|
|
|
|
|
|
- Storage Key 到 backing object 的校验
|
|
|
|
|
|
|
|
|
|
|
|
Memind App 只调用 API,不直接读写 `MindSpace/` 或 `data/mindspace/`。
|
|
|
|
|
|
|
|
|
|
|
|
Goose Runtime 只通过 MindSpace API/MCP 获得 scoped workspace capability,不直接拥有 MindSpace 业务状态。
|
|
|
|
|
|
|
|
|
|
|
|
## 2. API Surface
|
|
|
|
|
|
|
|
|
|
|
|
建议从当前 `/api/mindspace/v1/*` 延续,先在单体内实现兼容 facade:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
GET /api/mindspace/v1/spaces/current
|
|
|
|
|
|
GET /api/mindspace/v1/assets
|
|
|
|
|
|
POST /api/mindspace/v1/assets/uploads
|
|
|
|
|
|
GET /api/mindspace/v1/assets/:assetId/download
|
|
|
|
|
|
GET /api/mindspace/v1/pages
|
|
|
|
|
|
POST /api/mindspace/v1/pages
|
|
|
|
|
|
GET /api/mindspace/v1/pages/:pageId
|
|
|
|
|
|
POST /api/mindspace/v1/pages/:pageId/publish
|
|
|
|
|
|
GET /api/mindspace/v1/publications/:publicationId
|
|
|
|
|
|
GET /api/mindspace/v1/conversation-packages
|
|
|
|
|
|
POST /api/mindspace/v1/conversation-packages/ensure
|
|
|
|
|
|
GET /api/mindspace/v1/conversation-packages/:packageId
|
|
|
|
|
|
GET /api/mindspace/v1/conversation-packages/:packageId/manifest
|
|
|
|
|
|
POST /api/mindspace/v1/conversation-packages/:packageId/artifacts
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
所有响应必须包含稳定 ID,不把物理路径作为前端契约。
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Storage Adapter
|
|
|
|
|
|
|
|
|
|
|
|
MindSpace Service 内部存储接口:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
interface MindSpaceStorageAdapter {
|
|
|
|
|
|
putObject(key, body, options)
|
|
|
|
|
|
getObject(key)
|
|
|
|
|
|
statObject(key)
|
|
|
|
|
|
deleteObject(key)
|
|
|
|
|
|
listObjects(prefix, options)
|
|
|
|
|
|
copyObject(sourceKey, targetKey, options)
|
|
|
|
|
|
createReadStream(key)
|
|
|
|
|
|
createWriteStream(key, options)
|
|
|
|
|
|
getSignedUrl(key, options)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Adapter 要求:
|
|
|
|
|
|
|
|
|
|
|
|
- `key` 是相对 storage root 的逻辑 object key,禁止绝对路径。
|
|
|
|
|
|
- local fs adapter 可以把 key 映射到磁盘。
|
|
|
|
|
|
- NAS adapter 可以把 key 映射到共享挂载。
|
|
|
|
|
|
- S3 adapter 可以把 key 映射到 object prefix。
|
|
|
|
|
|
- 业务层不得依赖 adapter 的物理实现。
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Canonical URL
|
|
|
|
|
|
|
|
|
|
|
|
URL 生成入口:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
createPublicPageUrl({ userId, publicationId, pageId, slug })
|
|
|
|
|
|
createPublicAssetUrl({ userId, assetId, variant })
|
|
|
|
|
|
canonicalizeMindSpaceUrl(inputUrl)
|
|
|
|
|
|
validatePublicBackingObject({ publicationId })
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
规则:
|
|
|
|
|
|
|
|
|
|
|
|
- 新代码不得直接拼 `/MindSpace/<userId>/...`。
|
2026-07-02 21:04:49 +08:00
|
|
|
|
- 当前分支新增的 `mindspace-canonical-url.mjs` 是 P1 facade 起点;后续旧 helper 迁移到它后,再接入 backing object 校验。
|
2026-07-02 21:06:03 +08:00
|
|
|
|
- 当前分支新增的 `mindspace-service.mjs` 是组合门面起点;它先组合 package manifest、storage adapter、canonical URL,不接入现有运行链路。
|
2026-07-02 21:03:14 +08:00
|
|
|
|
- 旧 URL helper 暂时保留为 compatibility layer。
|
|
|
|
|
|
- public URL 必须能反查到 publication 或 asset。
|
|
|
|
|
|
- URL 返回前应校验 backing object 存在,或明确返回 pending 状态。
|
|
|
|
|
|
|
|
|
|
|
|
## 5. Conversation Package
|
|
|
|
|
|
|
|
|
|
|
|
Conversation package 是“每次对话文件夹包”的业务抽象。
|
|
|
|
|
|
|
|
|
|
|
|
逻辑 URI:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
mindspace://users/<userId>/conversations/<sessionId>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
推荐 manifest:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"schemaVersion": 1,
|
|
|
|
|
|
"packageId": "cp_xxx",
|
|
|
|
|
|
"userId": "u_xxx",
|
|
|
|
|
|
"sessionId": "s_xxx",
|
|
|
|
|
|
"title": "对话标题",
|
|
|
|
|
|
"storagePrefix": "users/u_xxx/conversations/s_xxx",
|
|
|
|
|
|
"artifacts": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"artifactId": "ca_xxx",
|
|
|
|
|
|
"kind": "public_html",
|
|
|
|
|
|
"assetId": "asset_xxx",
|
|
|
|
|
|
"pageId": "page_xxx",
|
|
|
|
|
|
"publicationId": "pub_xxx",
|
|
|
|
|
|
"messageId": "msg_xxx",
|
|
|
|
|
|
"agentRunId": "run_xxx",
|
|
|
|
|
|
"displayName": "report.html",
|
|
|
|
|
|
"mimeType": "text/html",
|
|
|
|
|
|
"sizeBytes": 12345,
|
|
|
|
|
|
"storageKey": "users/u_xxx/conversations/s_xxx/public/report.html",
|
|
|
|
|
|
"canonicalUrl": "https://...",
|
|
|
|
|
|
"createdAt": 1783000000000
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Artifact kind:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
input_image
|
|
|
|
|
|
input_file
|
|
|
|
|
|
generated_image
|
|
|
|
|
|
generated_file
|
|
|
|
|
|
page
|
|
|
|
|
|
public_html
|
|
|
|
|
|
long_image
|
|
|
|
|
|
thumbnail
|
|
|
|
|
|
docx
|
|
|
|
|
|
pdf
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Metadata Tables
|
|
|
|
|
|
|
|
|
|
|
|
首选新增:
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
CREATE TABLE h5_conversation_packages (
|
|
|
|
|
|
id VARCHAR(64) PRIMARY KEY,
|
|
|
|
|
|
user_id CHAR(36) NOT NULL,
|
|
|
|
|
|
session_id VARCHAR(128) NOT NULL,
|
|
|
|
|
|
title VARCHAR(255) DEFAULT NULL,
|
|
|
|
|
|
status ENUM('active', 'archived', 'deleted') NOT NULL DEFAULT 'active',
|
|
|
|
|
|
storage_prefix VARCHAR(512) DEFAULT NULL,
|
|
|
|
|
|
manifest_asset_id CHAR(36) DEFAULT NULL,
|
|
|
|
|
|
created_at BIGINT NOT NULL,
|
|
|
|
|
|
updated_at BIGINT NOT NULL,
|
|
|
|
|
|
UNIQUE KEY uniq_conversation_package_session (user_id, session_id)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE TABLE h5_conversation_artifacts (
|
|
|
|
|
|
id VARCHAR(64) PRIMARY KEY,
|
|
|
|
|
|
package_id VARCHAR(64) NOT NULL,
|
|
|
|
|
|
asset_id CHAR(36) DEFAULT NULL,
|
|
|
|
|
|
page_id CHAR(36) DEFAULT NULL,
|
|
|
|
|
|
publication_id CHAR(36) DEFAULT NULL,
|
|
|
|
|
|
agent_run_id CHAR(36) DEFAULT NULL,
|
|
|
|
|
|
message_id VARCHAR(128) DEFAULT NULL,
|
|
|
|
|
|
role VARCHAR(32) DEFAULT NULL,
|
|
|
|
|
|
artifact_kind VARCHAR(64) NOT NULL,
|
|
|
|
|
|
display_name VARCHAR(255) DEFAULT NULL,
|
|
|
|
|
|
storage_key VARCHAR(512) DEFAULT NULL,
|
|
|
|
|
|
canonical_url VARCHAR(512) DEFAULT NULL,
|
|
|
|
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
|
|
|
|
created_at BIGINT NOT NULL,
|
|
|
|
|
|
KEY idx_conversation_artifacts_package (package_id),
|
|
|
|
|
|
KEY idx_conversation_artifacts_asset (asset_id),
|
|
|
|
|
|
KEY idx_conversation_artifacts_page (page_id),
|
|
|
|
|
|
KEY idx_conversation_artifacts_message (message_id)
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
迁移原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 先新增表,不改旧表语义。
|
|
|
|
|
|
- 新写入链路双写 artifact 归属。
|
|
|
|
|
|
- 历史数据 best-effort 回填。
|
|
|
|
|
|
- 回填失败不影响旧页面访问。
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Goose Capability
|
|
|
|
|
|
|
|
|
|
|
|
短期兼容:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"sandboxRoot": "/absolute/local/MindSpace/<userId>",
|
|
|
|
|
|
"serverPath": "/absolute/local/mindspace-sandbox-mcp.mjs"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
长期目标:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"workspaceRef": "mindspace://users/<userId>/conversations/<sessionId>",
|
|
|
|
|
|
"packageId": "cp_xxx",
|
|
|
|
|
|
"apiBaseUrl": "https://mindspace.example.com",
|
|
|
|
|
|
"accessToken": "short-lived-scoped-token",
|
|
|
|
|
|
"allowedTools": ["read_file", "write_file", "edit_file", "publish_page"]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
权限要求:
|
|
|
|
|
|
|
|
|
|
|
|
- Token 必须绑定 user、session、package。
|
|
|
|
|
|
- Token 必须有过期时间。
|
|
|
|
|
|
- Tool allowlist 必须由 Memind/MindSpace policy 共同决定。
|
|
|
|
|
|
- Goose 不得凭 token 访问其它用户 package。
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Acceptance Tests
|
|
|
|
|
|
|
|
|
|
|
|
P1:
|
|
|
|
|
|
|
|
|
|
|
|
- local fs adapter 拒绝绝对 key 和 `..` traversal。
|
|
|
|
|
|
- canonical URL 只通过 facade 生成。
|
|
|
|
|
|
- 旧 `MindSpace/<userId>/public/*.html` 链路不变。
|
|
|
|
|
|
- `npm run verify:mindspace-publish-guards` 通过。
|
|
|
|
|
|
|
|
|
|
|
|
P2:
|
|
|
|
|
|
|
|
|
|
|
|
- 上传图片后 package manifest 出现 `input_image`。
|
|
|
|
|
|
- 生成 HTML 后 package manifest 出现 `public_html`。
|
|
|
|
|
|
- 发布页面后 artifact 关联 `pageId` 和 `publicationId`。
|
|
|
|
|
|
- 同一 session 多次生成按 `sortOrder` 或 `createdAt` 稳定排序。
|
|
|
|
|
|
|
|
|
|
|
|
P3:
|
|
|
|
|
|
|
|
|
|
|
|
- MindSpace Service 停止时,Memind App 明确返回服务不可用。
|
|
|
|
|
|
- 替换 local storage root 不需要改 Memind App / Goose 业务逻辑。
|
|
|
|
|
|
|
|
|
|
|
|
## 9. Non-Goals
|
|
|
|
|
|
|
|
|
|
|
|
当前阶段不做:
|
|
|
|
|
|
|
|
|
|
|
|
- 不迁移生产 103。
|
|
|
|
|
|
- 不切换 NAS/S3。
|
|
|
|
|
|
- 不删除旧 URL helper。
|
|
|
|
|
|
- 不改变现有 `/MindSpace/*` 公开访问行为。
|
|
|
|
|
|
- 不改变 Goose 当前可用的 `sandboxRoot` 兼容路径。
|