Files
memind/skills/page-data-collect/SKILL.md
T
john 884e819f70 fix(mindspace): lower page access password minimum to 6 characters
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>
2026-08-27 09:54:46 +08:00

362 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 APIpage-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` | 12 |
| **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 80815173 仅 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. 已选定分支 AE,方案摘要已确认;`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),便于用户在「页面数据」面板排查