# MindSpace Service Contract 日期: 2026-07-02 状态: Draft for P1/P2 生产拓扑更新(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)。 目标: - 定义 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//...`。 - 当前分支新增的 `mindspace-canonical-url.mjs` 是 P1 facade 起点;后续旧 helper 迁移到它后,再接入 backing object 校验。 - 当前分支新增的 `mindspace-service.mjs` 是组合门面起点;它先组合 package manifest、storage adapter、canonical URL,不接入现有运行链路。 - 旧 URL helper 暂时保留为 compatibility layer。 - public URL 必须能反查到 publication 或 asset。 - URL 返回前应校验 backing object 存在,或明确返回 pending 状态。 ## 5. Conversation Package Conversation package 是“每次对话文件夹包”的业务抽象。 逻辑 URI: ```text mindspace://users//conversations/ ``` 推荐 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/", "serverPath": "/absolute/local/mindspace-sandbox-mcp.mjs" } ``` 长期目标: ```json { "workspaceRef": "mindspace://users//conversations/", "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//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` 兼容路径。