884e819f70
Align publication validation, portal gate HTML, H5 publish UI, MCP docs, and page-data skill guidance so passwords like 888888 are accepted while 3-character values such as 888 remain rejected. Co-authored-by: Cursor <cursoragent@cursor.com>
362 lines
20 KiB
Markdown
362 lines
20 KiB
Markdown
---
|
||
name: page-data-collect
|
||
description: 在 MindSpace 页面中收集、存储并管理结构化数据(问卷、报名、台账、后台查看),基于 Page Data API 与用户隔离的 PostgreSQL 空间
|
||
---
|
||
|
||
# 页面数据收集(Page Data Collect)
|
||
|
||
在 MindSpace **公开 HTML 页面**中嵌入表单、问卷、报名等交互,并将提交持久化到当前用户隔离的 PostgreSQL schema,通过平台 **Page Data API** 受控读写。运行时禁止创建、读取或回退到用户 SQLite。
|
||
|
||
详细 API 说明见工作区外文档 `docs/page-data-api-usage.md`(Memind 仓库)。
|
||
|
||
## 何时使用
|
||
|
||
- 用户要在页面里**收集并保存**数据:问卷、投票、报名、签到、意见反馈
|
||
- 用户要**后台查看提交记录**,可能带口令/密码
|
||
- 用户提到「数据交互」「sqlite」「存数据库」「提交记录」
|
||
- 在已有页面上**追加**可提交、可统计的表单区块
|
||
|
||
**不要**用于:
|
||
- 只在聊天里弹表单、不落库 → 用 `form-builder`
|
||
- 纯静态展示页、无数据读写 → 用 `static-page-publish`
|
||
|
||
## 核心原则
|
||
|
||
```text
|
||
Agent 可以建模 SQL 并注册 dataset;
|
||
HTML 页面只能调用 Page Data API(page-data-client.js);
|
||
禁止自建 Express / 独立端口 / 直接暴露数据库连接;禁止 SQLite / localStorage 数据回退。
|
||
```
|
||
|
||
## 方案选择:默认快车道 + 能力分支(必做)
|
||
|
||
**平台配置不由 LLM 发明**;**页面内容可由 LLM 自由生成**。
|
||
|
||
```text
|
||
用户描述需求
|
||
→ 匹配能力分支(下表 A/B/C/D,默认 A)
|
||
→ 仅冲突或无法匹配时,用 1 题让用户选分支或改口令
|
||
→ 输出简短「方案摘要」;用户已明确要求直接创建/完成/发布时视为已确认并立即执行
|
||
→ 再 load_skill / 建表 / write_file / bind
|
||
```
|
||
|
||
### 默认方案 A(无特殊说明时直接采用,不必逐条追问)
|
||
|
||
| 项 | 默认值 |
|
||
|----|--------|
|
||
| 访客 | 匿名提交(`public`,仅 `insert`) |
|
||
| 后台 | **独立** HTML 页(`password`,仅 `read`) |
|
||
| 后台口令 | **`88888888`**(平台要求 6~128 位;用户可指定其它合法口令覆盖,如 `888888`) |
|
||
| 提交后修改 | 不支持(一次性) |
|
||
| 页面 | `public/*-survey.html` + `public/*-admin.html`(两文件各 bind 一次) |
|
||
|
||
用户只说「问卷/报名/收集数据 + 后台查看」且未提登录、协作改单、同页后台时 → **用方案 A**。若用户已说「直接做」「完整创建并发布」「不用询问」「持续推进」等终态指令,摘要后必须在同一轮立即调用工具开工,禁止停下来等待再次确认。
|
||
|
||
口令规则:
|
||
|
||
- **禁止**接受或使用 <6 位口令(如 `888`);若用户坚持更短口令,说明平台限制并代用 `888888` / `88888888` 或请其给出 ≥6 位。
|
||
- bind 时 **`password` 必须传入**且会写入发布记录;禁止只改 `access_mode` 不写 `password_hash`。
|
||
- 交付说明写明后台口令;用户可在 MindSpace「页面数据」面板重置。
|
||
|
||
### 能力分支(非默认需求时切换,仍用结构化选项)
|
||
|
||
| 分支 | 适用用户表述 | 前台 | 后台/协作 | 页面数 |
|
||
|------|-------------|------|-----------|--------|
|
||
| **A 匿名问卷+口令后台**(默认) | 匿名填、管理员看统计 | `public` insert | 独立页 `password` read | 2 |
|
||
| **B 登录后各自提交/查看** | 要登录、只看自己的、销售上报 | `login_required` insert+read | 同页或独立;`own_rows` | 1~2 |
|
||
| **C 共口令协作台账** | 团队共用一个密码、一起改 | `password` insert+read+update | 同页;先 `authenticate` | 1 |
|
||
| **D 仅公开提交无后台** | 只要收集、不要后台 | `public` insert only | 无 | 1 |
|
||
| **E 登录提交+口令管理** | 登录填报 + 管理员口令看全量 | `login_required` insert | 独立页 `password` read | 2 |
|
||
|
||
**LLM 只做分支匹配**:从用户原话判断 A~E;能确定则写入方案摘要,**不要**机械念 5 题问卷。
|
||
|
||
**仅以下情况才问用户(每次最多 1~2 点)**:
|
||
|
||
1. 表述同时命中两个互斥分支(如「匿名提交」+「同页内嵌口令看全量」)→ 给 A/E 选项说明须拆页
|
||
2. 用户明确要的口令 <6 位 → 请改口令或确认用默认 `88888888`
|
||
3. 需要 B/C 但表结构是否要 `created_by_user_id` 等列尚不清楚 → 确认登录隔离
|
||
|
||
**禁止**连续多轮只输出「我先检查工作区/加载技能」而不调用工具;**第一轮工具**应是 `load_skill` 或 `list_dir` / `private_data_execute`,不是空计划。
|
||
|
||
### 平台硬约束(分支菜单边界,不可绕过)
|
||
|
||
```text
|
||
- public:可匿名 insert;服务端禁止 update / softDelete
|
||
- password:所有 API 须先 authenticate;口令 ≥6 位且须写入发布记录
|
||
- `public` 策略中的 `insert: true` 等价于任何访客都能直接调用 API 写入;前端口令、隐藏按钮和 JavaScript 判断都不是权限控制
|
||
- password 页面必须调用 `client.authenticate(password)` 获取服务端 token;禁止在 HTML 中硬编码口令或只做 `password === "..."` 比较
|
||
- 同一公开页若要求「所有人可评价、只有所有者可写正文」:正文 dataset 在公开页必须 `insert: false`,评价 dataset 才能 `insert: true`;所有者写正文必须使用独立 `password` 作者页或 `login_required` 页面
|
||
- 同页不能同时「匿名 insert」+「口令 read 全量」→ 须拆前台 + 后台(方案 A/E)
|
||
- password 无法区分访客身份;「只改自己的」须 login_required + own_rows(方案 B)
|
||
- 匿名提交后「凭链接改自己的」无内置能力,须改 B 或接受一次性提交
|
||
```
|
||
|
||
### 方案摘要模板(开工前展示;默认填 A,用户可改口令或换分支)
|
||
|
||
```text
|
||
## 页面数据方案(请确认或只改口令)
|
||
|
||
- 分支:A 匿名提交 + 独立口令后台
|
||
- 访客:匿名提交(public,仅 insert)
|
||
- 管理员:独立后台页(password,仅 read)
|
||
- 后台口令:88888888(可改为你的 ≥6 位口令,如 888888)
|
||
- 提交后修改:不支持
|
||
- 页面:public/xxx.html + public/xxx-admin.html
|
||
- 内容:(由你的描述生成题目/字段/报表)
|
||
|
||
若用户尚未授权创建,确认后开始建表与写页面;若用户已明确要求创建/完成/发布,此摘要仅用于告知,后面必须紧接工具调用,不得结束回复等待确认。
|
||
```
|
||
|
||
### 分支切换示例
|
||
|
||
**用户**:「销售要登录后才能上报,管理员用 88888888 看全部。」
|
||
→ **方案 E**:前台 `login_required` insert;后台独立页 `password` read;口令 `88888888`。
|
||
|
||
**用户**:「小团队共用一个密码,一起维护台账。」
|
||
→ **方案 C**:单页 `password`;bind 传 `password`;页内先 `authenticate` 再读写。
|
||
|
||
**用户**:「做个投票,不用后台。」
|
||
→ **方案 D**:单页 `public` insert only。
|
||
|
||
## 标准工作流
|
||
|
||
### 1. 加载本技能 + 选定分支
|
||
|
||
```text
|
||
load_skill → page-data-collect
|
||
→ 匹配分支(默认 A)→ 方案摘要 → 用户确认,或从明确的创建/完成/发布指令判定已确认
|
||
→ 禁止未获创建授权就 bind;已获授权时禁止空转计划、重复确认或不调用工具
|
||
```
|
||
|
||
### 2. 数据层:建表 + 注册 dataset
|
||
|
||
**每个独立交付任务必须独立建表、独立 dataset,禁止复用已有表。**
|
||
|
||
| 场景 | 正确做法 |
|
||
|------|----------|
|
||
| 用户要做一个**新页面 / 新功能**(即使主题相似,如第二个日记本、另一个问卷) | 新建专用表 + 新 dataset 名,例如 `safe_diary_entries`、`children_hobby_survey_20260729` |
|
||
| 用户明确说「在**这个已有页面**里加一块表单」 | 才可复用该页已 bind 的 dataset |
|
||
| Agent 在 registry 里看到同名/同主题旧表 | **不得**直接拿来给新 HTML 用;必须新建 |
|
||
|
||
命名建议:`{页面slug}_entries` / `{页面slug}_responses`,表名与 dataset 名一致。两个页面即使业务相似(日记、台账、问卷),也必须是**两套** `{table, dataset, policy}`,避免字段约束、口令策略、软删除配置互相污染。
|
||
|
||
用 `private_data_execute` 建表(示例):
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS survey_responses (
|
||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||
q1_feature TEXT NOT NULL,
|
||
q2_usage TEXT NOT NULL,
|
||
q3_suggestion TEXT NOT NULL,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
用 `private_data_register_dataset` 注册(示例):
|
||
|
||
```json
|
||
{
|
||
"name": "survey_responses",
|
||
"table": "survey_responses",
|
||
"description": "TKMind 功能偏好问卷",
|
||
"actions": ["read", "insert"],
|
||
"columns": {
|
||
"read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"],
|
||
"insert": ["q1_feature", "q2_usage", "q3_suggestion"]
|
||
}
|
||
}
|
||
```
|
||
|
||
**数据层失败必须立即停止(fail closed)**:
|
||
|
||
- `private_data_execute`、`private_data_register_dataset`、`private_data_schema/query` 任一返回连接错误、权限错误或 `isError: true` 时,禁止继续写 HTML、bind 或发布。
|
||
- 禁止声称“先准备 HTML,PG 恢复后会自动生效”;平台没有延迟补执行队列。数据库恢复后必须重新执行建表、注册、bind 和交付前自检。
|
||
- PostgreSQL 连接错误应原样报告,不得把 `/tmp/.s.PGSQL.*`、`ECONNREFUSED` 或 `permission denied` 解释成“稍后会自动恢复”。
|
||
- dataset 配置了 `soft_delete` 时,表必须包含 `deleted_at TIMESTAMPTZ`;配置了 `own_rows` 时,表必须包含 policy 指定的所有者字段。
|
||
|
||
### 用户空间配额(页面写入 grace 策略)
|
||
|
||
MindSpace **页面 HTML**(`public/*.html`、`draft/*.html`)写入采用 **「本次写完、下次再拦」**:
|
||
|
||
| 状态 | 平台行为 |
|
||
|------|----------|
|
||
| 尚未超配额,但剩余空间 < 本次页面大小 | ✅ **允许写完**(grace write;本次 bind 必须继续完成) |
|
||
| 已经超配额(used ≥ quota) | ❌ **直接拦截** `write_file` / `edit_file` / bind / 发布 |
|
||
| 空间充足 | ✅ 正常写入 |
|
||
|
||
**Agent 必须遵守:**
|
||
|
||
- grace 只保证**当次**页面 HTML 能落盘并完成 bind;**不是**可以跳过 bind 或只写 policy 文件。
|
||
- bind 失败(含 `quota_exceeded` 且已超配额、`dataset_schema_mismatch`、PG 连接错误)时,**禁止**向用户交付「已就绪」链接;必须如实报告失败原因。
|
||
- 用户空间已满时,提示其在 MindSpace「我的空间」清理旧页面/资产,或购买/扩容后再继续写新页面。
|
||
- grace **不覆盖**图片/附件等非页面 HTML 资产上传;那些仍按原有配额硬拦截。
|
||
|
||
**禁止**只写 `.mindspace/page-data-policies/<slug>.json` 或使用非 UUID 的 `pageId` 冒充 bind 成功;policy 的 `pageId` 必须来自 `private_data_bind_workspace_page` 返回的真实 UUID,且数据库中存在对应 `h5_page_records`。
|
||
|
||
### 3. 页面层:写 HTML
|
||
|
||
- 用 `write_file` / `edit_file` 写入或更新 `public/*.html`
|
||
- 页面视觉、封面、`mindspace-cover`、页脚等规范**参照 `static-page-publish`**
|
||
- 必须引入平台脚本:
|
||
|
||
```html
|
||
<script src="/assets/page-data-client.js"></script>
|
||
```
|
||
|
||
客户端只允许调用 `page-data-client.js` 已公开的方法:`listRows`、`getSchema`、`getStats`、`insertRow`、`updateRow`、`deleteRow`、`authenticate`。删除单行使用:
|
||
|
||
```js
|
||
await client.deleteRow('dataset_name', rowId);
|
||
```
|
||
|
||
禁止发明 `softDeleteRows`、`deleteRows` 等不存在的方法;服务端会根据 dataset 的 `soft_delete` 授权把 `deleteRow` 转换为软删除。
|
||
|
||
- **第三方 JS 库**(Chart.js、Leaflet、ECharts 等)禁止写 CDN `https://...`;发布页 CSP 只允许同源脚本。优先用平台预置路径,或下载到 `public/assets/` 后用相对路径引用:
|
||
|
||
```html
|
||
<!-- 推荐:平台已预置 Chart.js -->
|
||
<script src="/assets/chart.umd.min.js"></script>
|
||
<!-- 地图页:平台已预置 Leaflet -->
|
||
<link rel="stylesheet" href="/assets/leaflet/leaflet.css">
|
||
<script src="/assets/leaflet/leaflet.js"></script>
|
||
<!-- 或页面自有副本 -->
|
||
<script src="assets/chart.umd.min.js"></script>
|
||
```
|
||
|
||
平台会在发布/访问时自动把常见 Chart.js、Leaflet CDN 改写为 `/assets/chart.umd.min.js`、`/assets/leaflet/*`;其它未知 CDN 会在发布检查中被拦截。
|
||
|
||
### 4. 发布并绑定页面(推荐)
|
||
|
||
用 `private_data_bind_workspace_page` 一步完成:**创建/更新页面记录 → 发布 → 写入 Page Data 策略**。
|
||
|
||
**问卷页(匿名提交)示例:**
|
||
|
||
```json
|
||
{
|
||
"relativePath": "public/tkmind-survey.html",
|
||
"accessMode": "public",
|
||
"datasets": {
|
||
"survey_responses": {
|
||
"insert": true,
|
||
"read": false,
|
||
"columns": {
|
||
"insert": ["q1_feature", "q2_usage", "q3_suggestion"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**后台页(口令查看)示例:**
|
||
|
||
```json
|
||
{
|
||
"relativePath": "public/tkmind-survey-admin.html",
|
||
"accessMode": "password",
|
||
"password": "88888888",
|
||
"datasets": {
|
||
"survey_responses": {
|
||
"insert": false,
|
||
"read": true,
|
||
"columns": {
|
||
"read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
返回 `pageId`(UUID)与 **`workspaceUrl` / `deliveryUrl`**(`/MindSpace/<用户ID>/public/xxx.html`)。
|
||
|
||
**交付时必须优先给用户真实可点击 URL**:
|
||
|
||
| 环境 | 链接前缀示例 |
|
||
|------|-------------|
|
||
| **本地开发** | `http://127.0.0.1:8081/MindSpace/<用户ID>/public/xxx.html`(Page Data API 走 Portal 8081;5173 仅 UI 预览) |
|
||
| **生产** | `https://m.tkmind.cn/MindSpace/<用户ID>/public/xxx.html` |
|
||
|
||
- **禁止**交付占位 host(如 `http://本地服务/...`);聊天展示层不得把 `127.0.0.1:8081` 替换成不可点击文字。
|
||
- `/u/用户名/pages/...` 仅作补充,用户可见主链接必须是 MindSpace `/public/` 路径。
|
||
- bind 会同步工作区 HTML 到发布快照,但禁止先发布占位内容再补文件。
|
||
|
||
**dataset 名称必须与 HTML 一致**:`private_data_register_dataset` 的 `name`、`private_data_bind_workspace_page` 的 `datasets` 键名、以及 HTML 里 `insertRow('...')` / `listRows('...')` 的字符串必须完全相同(例如都用 `tkmind_exp_survey`)。若不一致,提交会报「dataset 未授权 insert/read」。
|
||
|
||
若问卷与后台是多个 HTML 文件,对每个 `public/*.html` 各调用一次 bind:
|
||
- **问卷页**:`accessMode: "public"` + dataset 仅 `insert`
|
||
- **后台页**:`accessMode: "password"` + dataset 仅 `read`(发布口令至少 6 位)
|
||
|
||
**顺序**:先 `write_file` 完整 HTML → 再 `bind`;禁止只写「问卷页面」占位文字就发布。
|
||
|
||
也可手动发布后调用 `private_data_set_page_policy`(需已知 `pageId`)。
|
||
|
||
### 5. 配置 Page Access Policy(手动路径)
|
||
|
||
多页场景请**按页**分别配置,勿把 `public` 问卷与 `password` 后台混在同一 `pageId` 策略里。
|
||
|
||
### 6. 前端读写
|
||
|
||
```html
|
||
<script src="/assets/page-data-client.js"></script>
|
||
<script>
|
||
// pageId 可省略:平台访问时注入 __MINDSPACE_PAGE_DATA__
|
||
const client = MindSpacePageData.createClient({ apiBase: '/api' });
|
||
|
||
// 口令页后台:await client.authenticate('88888888'); // 口令 ≥6 位,与发布时一致
|
||
// 提交:await client.insertRow('survey_responses', { q1_feature: '...', ... });
|
||
// 列表:const { rows } = await client.listRows('survey_responses', { limit: 50 });
|
||
</script>
|
||
```
|
||
|
||
## 常见场景策略
|
||
|
||
| 分支 | 场景 | 发布模式 | Page Data 能力 |
|
||
|------|------|------------|----------------|
|
||
| A(默认) | 匿名问卷 + 口令后台 | 前台 `public`;后台 `password` | insert / read(分页) |
|
||
| B | 登录用户各自提交/查看/改自己的 | `login_required` | insert/read/update + `own_rows` |
|
||
| C | 共口令协作台账 | `password` | insert/read/update(先 authenticate) |
|
||
| D | 仅公开收集 | `public` | 仅 `insert` |
|
||
| E | 登录提交 + 口令管理全量 | 前台 `login_required`;后台 `password` | insert / read(分页) |
|
||
|
||
**需要 update/delete**:`public` 不支持;匿名无「改自己的」→ 用 B 或 C。详见上文分支表。
|
||
|
||
## 严格禁止
|
||
|
||
1. **禁止**在尚未获得创建授权时建表 / bind / 发布;用户明确要求「创建、完成、发布、直接做、不用询问、持续推进」均视为确认,禁止再次停下来询问
|
||
2. **禁止**让 LLM 自行发明 accessMode/拆页/口令;必须落在分支 A~E 与默认口令规则内
|
||
3. **禁止**连续两轮仅输出计划、不调用 `load_skill` / `list_dir` / `write_file` / `private_data_execute` / `bind`
|
||
4. **禁止**接受 <6 位发布口令;用户未指定口令时后台默认 **`88888888`**
|
||
5. **禁止**创建 `scripts/*-api.mjs`、Express 服务、或监听独立端口(如 `8899`)
|
||
6. **禁止** HTML 中硬编码 `http://127.0.0.1:端口` 或自定义 `/api/survey/*`
|
||
7. **禁止**在 HTML 中使用 `onclick` / `oninput` 等内联事件属性(MindSpace 发布页 CSP 不允许);改用 `addEventListener`
|
||
8. **禁止**用 CDN `https://...` 引用 `<script src>`(发布页 CSP 仅允许同源;Chart.js 用 `/assets/chart.umd.min.js` 或 `public/assets/` 本地文件)
|
||
9. **禁止**在页面 JS 中直接使用 `better-sqlite3` 或读取 `.sqlite` 文件路径
|
||
10. **禁止**只 `CREATE TABLE` 而不 `private_data_register_dataset`
|
||
11. **禁止**未配置 `private_data_set_page_policy` 就让页面调用公开 API
|
||
12. **禁止**先发布占位页(如 `<p>问卷页面</p>`)再让用户访问 `/u/.../pages/...`
|
||
13. **禁止**在 Agent 生成的 HTML/JavaScript 中使用 `localStorage` / `sessionStorage` / `IndexedDB` 保存任何数据或做 API fallback;所有需持久化的数据必须进入当前用户专属 PostgreSQL schema,禁止 SQLite、静态 JSON/JS 文件和内存 fallback
|
||
14. **禁止**在空间已满(已超配额)时继续 `write_file` / bind 新页面;必须先提示清理或扩容
|
||
15. **禁止**bind 未完成或 `pageId` 非 UUID 时声称 Page Data 页面已就绪;控制台出现 `pageId 未配置` 说明 bind/发布链路未闭环
|
||
16. **禁止**为新的独立页面复用已有 dataset / 表(例如第二个日记页继续用 `diary_entries`);除非用户明确要求改同一页面上的表单
|
||
|
||
## 交付前自检
|
||
|
||
0. 已选定分支 A~E,方案摘要已确认;`password` 页口令 ≥6 位且 bind 已传入
|
||
1. `private_data_register_dataset` 已注册;PostgreSQL 表字段与 policy 一致(含 `deleted_at` 等)
|
||
2. `private_data_bind_workspace_page` **已成功**,返回 UUID 形式 `pageId` 与 `deliveryUrl`
|
||
3. `.mindspace/page-data-policies/<pageId>.json` 已写入(文件名必须是 UUID,不是 slug)
|
||
4. 通过 Portal 打开 `deliveryUrl` 时,页面源码含 `__MINDSPACE_PAGE_DATA__`
|
||
5. HTML 含 `page-data-client.js`;已 bind 或发布后平台会注入 pageId
|
||
6. HTML **不含**硬编码 `127.0.0.1:` 作为 API 基址;不含 `/api/survey/`、`PLACEHOLDER_PAGE_ID`
|
||
7. 本地交付链接使用 `http://127.0.0.1:8081/MindSpace/...`;生产使用 `https://m.tkmind.cn/MindSpace/...`;禁止 `http://本地服务/...`
|
||
8. 向用户说明:访客如何提交、管理员如何用口令查看记录
|
||
9. HTML 未调用 `softDeleteRows` / `deleteRows` 等客户端不存在的方法;删除使用 `deleteRow(dataset, rowId)`
|
||
10. 若 bind 报 `quota_exceeded` 且 `overQuota: true`,停止交付并提示清理空间;grace 仅适用于「当次写完、尚未超配额」场景
|
||
|
||
## 回复格式
|
||
|
||
除数据能力外,优先返回 **deliveryUrl / workspaceUrl** 的 Markdown 链接 `[标题](完整URL)`,并简要说明后台入口与口令(如有)。
|
||
|
||
- 本地:`http://127.0.0.1:8081/MindSpace/<用户ID>/public/xxx.html`
|
||
- 生产:`https://m.tkmind.cn/MindSpace/<用户ID>/public/xxx.html`
|
||
- 必须附带 bind 返回的 `pageId`(UUID),便于用户在「页面数据」面板排查
|