Files
match-guide-track-default/references/SCHEMA.md
chenfuqiang 7c1f40b0e6
All checks were successful
技能自动化发布 / release (push) Successful in 6s
feat: 申报赛道推荐助手首版 v1.0.1(match-guide-track-default)
2026-09-13 15:50:33 +08:00

41 KiB
Raw Blame History

数据存储与数据管理展示规范

本文是 skill 本地 SQLite 与匠厂宿主「数据管理」页面展示契约的权威说明。

产品定位: 数据管理既是业务数据与过程的可见面,也是与对话平级的 UI 执行面(表上 Action 走同一 CLI 闭环)。何时挂执行入口见 ../development/PRODUCT_SURFACES.md;本文管表结构与展示元数据。

实现上的 DDL 与默认中文元数据种子见 scripts/db/display_metadata.py;scripts/db/connection.py 的 init_db() 会一并初始化。

职责边界

技能负责

  • 定义真实 SQLite 表结构
  • 使用英文 snake_case 表名和字段名
  • 使用 _jiangchang_tables 提供表级展示元数据
  • 使用 _jiangchang_columns 提供字段级展示元数据(含可选 description 表单说明、可选 grid_help 表格说明)
  • 需要「新增表单预填」时,在物理列上声明 SQLite DEFAULT(勿把默认值只写在文案里)
  • 定义哪些字段可见、可搜索、可创建、可编辑
  • 定义字段展示类型和枚举选项
  • (可选)按业务规则维护单元格呈现旁路数据(语义 tone;见 CELL_PRESENTATION.md)
  • 保证元数据与真实数据库结构一致
  • 保证元数据初始化幂等

宿主负责

  • 读取 _jiangchang_* 元数据
  • 读取 SQLite PRAGMA table_info
  • 数据管理侧栏/表列表:当库中存在 _jiangchang_tables 时,仅展示在该表注册且 visible=1 的业务表;孤儿物理表(有表、无注册元数据)不得出现在侧栏,直接访问 schema API 应返回「表不存在」。极老库无 _jiangchang_tables 时,宿主可保留物理表枚举 fallback(兼容 _schema_meta 时代)
  • 根据 schema 渲染数据管理页面
  • 根据 editable 推导字段是否可创建或编辑
  • 在新增 / 编辑表单中,若列有 _jiangchang_columns.description,在标签下方展示该说明(灰字;空则不显示)
  • 在数据管理表格中,若表有 _jiangchang_tables.grid_help 则在表上方展示可折叠说明;若列有 _jiangchang_columns.grid_help 则在列头显示「?」;空/未写则不渲染。不把 description 当作表格说明
  • 打开新增表单时,对可创建字段用 SQLite 列 DEFAULT(PRAGMA table_info.dflt_value)预填;无 DEFAULT 则留空
  • 执行通用查询、新增、编辑、删除、导入、导出
  • 对字段名、表名、类型和权限进行最终校验
  • 不猜测技能业务含义
  • 不根据具体技能名称写特殊逻辑
  • 可在技能安装/更新成功后静默调用 python scripts/main.py init-db 做幂等 ensure(内部即 init_db());失败不影响安装结果
  • 若技能启用了单元格呈现(见 CELL_PRESENTATION.md),读取旁路呈现数据并为单元格映射语义样式(tone);不猜测业务规则

初始化入口

幂等建库/迁移:python scripts/main.py init-db。业务仓储也可懒触发 init_db(),与 CLI 结果一致。

SQLite 没有可靠的 COMMENT ON TABLE/COLUMN;SQL 文件里的 -- 注释 也不会成为可查询结构。 因此中文展示名称必须写入元数据表;字段顺序必须写在 CREATE TABLE 的列定义顺序中(PRAGMA table_info 的 cid)。

禁止新技能使用宿主的旧版 _schema_meta 表。仅 _jiangchang_tables / _jiangchang_columns 为正式契约。

技能名称(SKILL.md)

---
name: 中文业务名称
description: 中文技能说明
metadata:
  openclaw:
    slug: english-kebab-slug
---
  • name:用户可见的中文业务名称。
  • description:能力说明,不是技能名称。
  • metadata.openclaw.slug:机器标识,kebab-case 英文;目录名与默认数据库文件名使用 slug。slug 语义规范见 ../development/NAMING.md。
  • 复制模板后不得将 your-skill-slug、My Skill 等占位内容作为正式技能发布名称。

数据库路径与命名

{JIANGCHANG_DATA_ROOT}/{JIANGCHANG_USER_ID}/{skill-slug}/{skill-slug}.db
  • 数据库文件:英文 slug,例如 your-skill-slug.db(默认 {skill-slug}.db)。
  • 业务表、字段:英文 snake_case 物理名,例如 demo_items、created_at。
  • 禁止用中文作为 SQLite 真实表名或字段名。

外部来源只读与本地数据可写

某些技能会从外部系统读取数据并写入技能自己的本地数据库。需要区分:

外部来源数据库:
  可能只读,不应被修改

技能本地数据库:
  可以根据业务语义允许新增、编辑或删除

不要把「外部来源只读」错误理解成「技能本地数据库一定只读」。技能通过 _jiangchang_tables.readonly 与 _jiangchang_columns.editable 声明本地表的实际写权限。

若创建/更新/删除/导入带有领域副作用(自动建目录、加密、级联清理、归一化字段),不要依赖宿主默认直写:在 assets/actions.json 用可选字段 replaces: create|update|delete|import 接管默认按钮。其中 import 时用户仍使用宿主「导入数据」面板,确认后由技能写库。详见 ACTIONS.md 与 assets/examples/replaces-*.action.example.json。

表与表之间:禁止 SQLite 外键

业务表之间的「属于哪条父记录 / 引用哪条来源」只存业务 id(整数或文本标量),禁止在 DDL 中写 SQLite FOREIGN KEY / REFERENCES,也不要指望 PRAGMA foreign_keys = ON 替你校验或级联。

要做的事 正确做法 禁止
子表指向父表 子表列如 case_id INTEGER NOT NULL;写入前在 repository/service 查父表是否存在 FOREIGN KEY (case_id) REFERENCES parent(id)
父表记「最新一版」 父表列如 latest_draft_id INTEGER NOT NULL DEFAULT 0;由 service 更新指针 同上
删除 / 覆盖 / 级联清理 repository/service 内显式 DELETE / UPDATE;需要时用 Action steps 的 deleteByFk 依赖 DB 级联或宿主 CRUD 被 FK 拦住
数据管理里显示名称 _jiangchang_columns.display_type = lookup + options_json 对象(见下文「跨表引用 lookup」) 把 lookup 当成 DDL 外键

为什么: 宿主数据管理对业务表执行通用增删改,会直接写 SQLite。一旦 DDL 带外键,删除、迁移 rename 父表、或父表 id 变更时极易出现「本地数据操作失败」且难排查。关联语义属于技能业务,必须在代码里写清。

迁移注意: 避免「ALTER TABLE parent RENAME TO parent_old → 建新 parent → 删 old」这类会隐性改写子表 FK 指向的套路;优先 ADD COLUMN + 拷列,或重建子表且不带 FK。

display_metadata_validator 与 validate_template_database 会扫描业务表 DDL;含 FOREIGN KEY / REFERENCES 时报错(见 development/CHECKLIST.md A6-6)。

标准数据同步语义(外源 → 本地库)

适用于「同步 / refresh / collect」类能力。使用通用名称表述,勿把具体平台表名写进模板实现。

要求 说明
外部来源默认只读 技能不得修改外源
本地 SQLite 可事务写入 写入走 repository + 事务
幂等 同一业务键重复同步结果稳定
稳定唯一业务键 明确 upsert / 匹配字段
全量或增量 在实现与文档中显式声明
完整快照才提交 完整性校验通过后再 commit;调用方必须显式提供完整性状态(如 keyword-only 必填的 snapshot_complete),禁止默认视为完整,禁止靠数据条数猜测
空结果 / 读取失败保护 读取失败、分页不完整或不可信空结果(snapshot_complete=False)不得清库或误删;仅当完整快照显式确认为空(snapshot_complete=True 且结果为空)时,才可按全量失效策略处理旧数据
全量失效策略 物理删除、removed/is_active 标记、或保留历史——必须三选一并写清
部分失败 不得伪装成整体成功
半成品 事务失败不得留下可见不一致状态

建议统一返回统计:

{
  "total": 100,
  "inserted": 10,
  "updated": 20,
  "unchanged": 65,
  "removed": 5
}

同步数据 vs 导出当前数据表 vs 特殊业务报告 vs HTML 页面

能力 语义 负责方
同步数据 外部来源 → 技能本地数据库 技能 Action / domain service
导出当前数据表 本地库 → CSV/Excel 等 数据管理页宿主统一导出
生成特殊业务报告(文件) 多表合并、特殊排版或附加计算的文件 仅当系统导出无法满足时,才提供独立 export-*;落 output/
HTML 页面报告 侧栏可预览的整页 HTML 写入 html/ + Action id render-html(见 ACTIONS.md)

同步型 Action 默认只写库、不生成导出文件。兼容旧 export 命令需要刷新时,必须调用同一 sync 内核。可复用测试范式见 tests/samples/test_sync_contract.py.sample。

元数据表(宿主兼容)

宿主读取字段(与匠厂 metadata-resolver 一致):

_jiangchang_tables 表级标准

字段 必填 说明
table_name 是 真实英文表名
display_name 是 用户可见的中文业务名称
description 否 表说明(表单/备注用途);不用于表格上方横幅
grid_help 否 数据管理表格上方说明;空/未写则不出横幅。与 description 独立,禁止互拷(见下节)
sort_order 否 兼容保留字段,不用于替代物理字段顺序
visible 是 1 在宿主数据管理中展示 / 0 隐藏
readonly 是 1 整张表只读;0 允许宿主根据字段权限执行写操作
default_sort_json 否 数据管理默认行排序(见下节);空则宿主用内置默认
default_visible_columns_json 否 数据管理默认展示列(见下节);空则全显

表级默认排序 default_sort_json(宿主支持)

技能可声明该表在数据管理中的默认排序;宿主优先采用,未声明或声明无效时回退:

updated_at DESC → created_at DESC → 主键 DESC。

JSON 数组(可多字段,按顺序):

[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]

规则:

  • column 必须是该表真实列名;敏感列会被宿主忽略
  • direction 仅 asc / desc(缺省按 desc)
  • 用户在 UI 上手动改排序后,以用户选择为准;声明只影响未选择时的默认
  • 通过 upsert_table_metadata(..., default_sort_json=...) 写入;旧库由 init_display_metadata_tables 幂等 ALTER 补列

示例(消息时间线):

upsert_table_metadata(
    cursor,
    table_name="messages",
    display_name="消息",
    readonly=0,
    default_sort_json='[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]',
)

表级默认展示列 default_visible_columns_json(宿主支持)

技能可声明该表在数据管理表格中的默认展示列(软默认,不是硬隐藏):

  • 已配置:首次打开(用户尚未改过「列」)只显示名单中的列;其余列仍在「列」面板中,用户可勾回
  • 未配置 / 空 / 无效:显示全部 schema 可见列(与今日行为一致)
  • 用户改过「列」或表头「隐藏此列」后:以本机偏好为准;「恢复默认列布局」后重新回到技能默认(若有)

JSON 数组(物理列名;也可写 {"column":"col"} 对象,宿主同样接受):

["stat_type","period","stage","ops_count","timeout_ratio"]

规则:

  • 列名必须是该表真实列,且 _jiangchang_columns.visible=1(硬隐藏列本来就不进表格)
  • 与 visible=0 不同:visible=0 = 列从数据管理 schema /「列」面板移除;本字段只影响默认勾选状态
  • 名单为空或全部无效时,宿主 fail-open:全显
  • 通过 upsert_table_metadata(..., default_visible_columns_json=...) 写入;旧库由 init_display_metadata_tables 幂等 ALTER 补列

示例(时效统计:默认不展示编号与时间戳列):

upsert_table_metadata(
    cursor,
    table_name="timeliness_stats",
    display_name="时效统计",
    readonly=1,
    default_visible_columns_json=(
        '["stat_type","period","stage","ops_count",'
        '"timeout_open","timeout_done","timeout_total","timeout_ratio"]'
    ),
)

表格说明 grid_help(宿主支持)

技能可给数据管理表格写说明,与新增/编辑表单的 description 完全独立:

写在哪 宿主做什么 空/未写
_jiangchang_tables.grid_help 表格上方可折叠横幅(首行摘要;有后续行才出展开) 不出横幅
_jiangchang_columns.grid_help 列头「?」,点击查看该列说明 不出「?」

规则:

  • opt-in:大多数表/列不需要写;不写则行为与今日一致
  • 禁止把 description 拷进 grid_help,也禁止把表格规则写进 description(description 只给表单灰字)
  • 多行文本时:第一行是摘要,其余行点「展开」后可见
  • 通过 upsert_table_metadata(..., grid_help=...) / upsert_column_metadata(..., grid_help=...) 写入;旧库由 init_display_metadata_tables 幂等 ALTER 补列
  • 需已支持该字段的匠厂宿主;更旧版本会忽略未知列,不写则无影响

示例(仅在确有表格规则时写;模板默认业务表不要预填):

upsert_table_metadata(
    cursor,
    table_name="timeliness_records",
    display_name="时效登记",
    readonly=1,
    grid_help="超时按环节规则判定。\n交单:预计到港日前 3 天须交单",
)
upsert_column_metadata(
    cursor,
    table_name="timeliness_records",
    column_name="submit_date",
    display_name="交单日期",
    grid_help="预计到港日前 3 天须交单",
    editable=0,
)

禁止:

  • 使用中文真实表名
  • 使用 _schema_meta 作为新的正式契约
  • 只写英文物理名而不写中文 display_name
  • 让元数据引用不存在的表
  • 用 visible=0 假装「默认不展示但用户还能勾出来」(那是硬隐藏,勾不回来)

_jiangchang_columns 字段级标准

字段 必填 说明
table_name 是 所属物理表
column_name 是 真实英文字段名
display_name 是 用户可见的中文字段名(表头与表单标签);保持简短
description 否 字段说明(给人看的短文案);宿主在新增/编辑表单标签下方展示(见下节);不要塞进 display_name
grid_help 否 数据管理表头「?」;空/未写则不出。与表单 description 独立,禁止互拷
display_order 否 兼容字段,默认保持 NULL
visible 是 是否进入数据管理 schema /「列」面板(0 = 硬隐藏,用户无法勾回;默认不展示但可勾回请用表级 default_visible_columns_json)
searchable 是 是否参与列筛选与顶栏全局搜索(0 = 两者均不参与);详见下节「数据管理:列筛选与全局搜索」
editable 是 1 业务字段允许宿主用于新增和编辑;0 系统/只读字段
display_type 否 字段渲染类型;稳定取值见下节「表单与展示:稳定字段类型」
options_json 否 枚举选项 JSON

这两张表是数据字典,不是业务数据;宿主不会把它们当作普通业务表展示。

表单与展示:稳定字段类型(宿主支持,须与 Action 对称)

数据管理「新增 / 编辑」表单与 Skill Action 参数表单应使用同一套控件语义(配置入口不同,见 ACTIONS.md)。新技能只使用下表;宿主须两边都实现,避免「Action 有、CRUD 没有」。

本版稳定支持(须齐):

display_type(CRUD) 物理类型建议 列表展示 新增/编辑控件 存库约定
text(或缺省) TEXT / 一般标量 原文 单行文本 原文
textarea TEXT 原文(可截断) 多行文本 原文
json TEXT 原文 多行文本(JSON 字符串) 原文;提交时宿主可做基础 JSON 合法性校验
number INTEGER / REAL / NUMERIC 原文 数字输入(type=number) 数字;无 display_type 时,物理类型为 INT/REAL/NUMERIC 也应走数字控件
boolean INTEGER 0/1 或 BOOL 开关语义展示 开关 0 / 1
(枚举) 任意 + options_json 数组 标签映射 下拉选择 选项 value
lookup 跨表引用 id;options_json 对象;可选 multiple 单选:标签映射;多选:多标签(如「豆包、元宝」) 单选下拉;多选见下文 单选:业务 id 标量;多选:TEXT 逗号分隔 id(非 SQLite 外键)
(敏感) 任意;列 sensitive=1 掩码/隐藏策略由宿主定 新建可用密码框;编辑策略与既有宿主一致 原文(勿日志明文)
date TEXT 推荐 YYYY-MM-DD,或可安全归一的值 日期 日期选择(日) YYYY-MM-DD
datetime_unix_seconds INTEGER YYYY-MM-DD HH:mm:ss 日期时间选择;提交写回 Unix 秒 Unix 秒整数;禁止改成 ISO 字符串
url TEXT 原文(可作链接) 单行输入 type=url(薄层;校验从宽) 原文
email TEXT 原文 单行输入 type=email(薄层;校验从宽) 原文

说明灰字与创建默认值见下一小节;与 Action 的对照写在 ACTIONS.md「表单字段类型对照」。

明确不在本版稳定范围(勿在新技能宣传为已支持):

  • 文件 / 图片上传
  • 复杂 oneOf / nested object 表单(任意 JSON 数组当业务多选控件)
  • 把业务可编辑时间存成毫秒、ISO 文本或 SQLite datetime 文本(审计字段规范见「标准时间字段」)

Lookup 多选已纳入本版契约(见下文 multiple),与「任意多选/标签数组」不是同一能力。

datetime_unix_milliseconds:仅兼容列表展示旧数据;新技能不要用;可编辑时间一律 datetime_unix_seconds。

新增/编辑表单:字段说明与默认值(宿主支持)

数据管理「新增记录 / 编辑记录」与 Skill Action 弹窗是两套表单;CRUD 侧约定如下(交互对齐 Action 的 inputSchema.description / default,但配置入口不同)。

能力 技能写什么 宿主做什么
字段说明 _jiangchang_columns.description(可选) 新增/编辑表单中,在标签下方以灰字展示;空则不显示
创建默认值 物理列 SQLite DEFAULT(如 limit_n INTEGER NOT NULL DEFAULT 0) 打开「新增」时,对可创建字段预填该默认值;无 DEFAULT 则留空
编辑已有行 — 不用 DEFAULT 覆盖已有行数据
表格列头标题 display_name 短名;不要把长说明塞进列头文字
表格列头「?」 _jiangchang_columns.grid_help(可选) 有内容才显示「?」;不回退 description
表格上方说明 _jiangchang_tables.grid_help(可选) 有内容才显示横幅;不回退表级 description

规则与禁止:

  • 推荐对易填错、语义特殊的可编辑字段写 description(示例链接、0 表示不限制等);非强制每个字段都有
  • 禁止把说明硬塞进 display_name(如 评论上限(0不限));表头会变脏,也无法与 Action 说明对齐
  • 业务语义(如「0 = 不限制」)写在 description 里;宿主只展示,不解读业务含义
  • 第一期不新增元数据「表单默认值」列;CRUD 默认值 = DDL DEFAULT,与 Action 的 inputSchema.default 分开配置
  • 同一业务字段若同时出现在 Action 与新增表单,文案应对齐(短标题 + 说明),但分别写在 actions.json 与 _jiangchang_columns / DDL
  • 数字类可编辑列推荐 display_type: "number"(或依赖 INT/REAL/NUMERIC 推断)并在 DDL 写清 DEFAULT(如条数上限默认 0)

示例(可编辑列种子 + DDL 默认):

-- 物理列:创建默认 0
comment_limit INTEGER NOT NULL DEFAULT 0
seed_column_metadata_batch(
    cursor,
    table_name="videos",
    columns=(
        {
            "column_name": "video_url",
            "display_name": "视频链接",
            "editable": 1,
            "display_type": "url",
            "description": "填平台视频页链接;需包含可识别的视频路径片段。",
        },
        {
            "column_name": "comment_limit",
            "display_name": "采集评论条数",
            "editable": 1,
            "display_type": "number",
            "description": "最多采多少条。填 0 = 不限制;填正整数则按该数量封顶。",
        },
    ),
    valid_column_names=physical,
)

Action 参数表单的 title / description / default / display_type 见 ACTIONS.md;勿与本节 CRUD 配置入口混用。

数据管理:列筛选与全局搜索(宿主支持)

数据管理表格提供两类与 searchable 相关的检索能力,技能通过 _jiangchang_columns.searchable 声明是否参与:

能力 用户入口 searchable = 1 时 searchable = 0 时
列筛选 表格「筛选」→ 字段 / 操作符 / 值 出现在「字段」下拉,可按类型选值与操作符 不出现在列筛选字段列表
全局搜索 顶栏搜索框 参与 OR 模糊匹配(宿主对参与列数有上限,如最多 8 列) 不参与全局搜索

存库 vs 展示(核心契约)

  • 列表:有 options_json 时展示 label(如「启用」「停用」)
  • 筛选 / 查询 / 全局搜索:比较与匹配 value(如 0/1、active、toutiaohao)
  • 宿主负责 label ↔ value 映射;禁止为迁就 UI 把存库改成中文枚举,也禁止在 description 里写「筛选请填 0/1」

筛选栏控件 ≠ 新增/编辑表单(勿混用)

列筛选「值」与表单使用同一套类型语义,但控件形态不同:筛选栏须与「字段」「操作符」同一行工具栏对齐(如统一 h-9 下拉),枚举 / boolean / lookup 在筛选栏一律用下拉 Select,不用新增/编辑表单里的开关或横向分段单选。表单侧仍按上表「新增/编辑控件」配置。

列类型 筛选「值」控件 常用操作符(宿主按类型裁剪) 技能须配置
文本 / url / email 文本框 等于 / 包含 / 开头 / 结尾 / 为空… 通常 searchable: 1
数字 数字框 等于 / 大于 / 小于… display_type: number 或 INT/REAL
枚举(options_json 数组) 下拉(展示 label) 等于 / 不等于 / 为空 options_json 必填 + searchable: 1
boolean 下拉(有 options_json 用其 label;否则宿主默认可选「是/否」) 等于 / 不等于 / 为空 display_type: boolean;推荐同时写 options_json 指定中文 label
lookup 单选 下拉 等于 / 不等于 / 为空 display_type: lookup + lookup 对象
lookup + multiple: true 列筛选 UI 演进中;全局搜索已支持 等于时按逗号分隔 id 的 token 匹配 多选存 TEXT 逗号分隔 id
date / datetime_unix_seconds 日期 / 日期时间 比较类 时间列通常 searchable: 0
敏感列 不可筛 — 宿主强制排除

全局搜索扩展(用户可搜中文)

宿主在 searchable = 1 的列上除原文 LIKE 外,还应:

  • 有 options_json 枚举:label 或 value 均可命中
  • boolean 无 options:认「是/否」等常见别名
  • lookup 多选:按逗号分隔 id 的 token 边界 匹配;选项 label 亦可参与
  • datetime_unix_seconds:搜 YYYY-MM-DD 形态可匹配当天 Unix 秒区间

技能配置建议

推荐:

  • 用户常筛的状态列(启用/停用、待生成/已生成、status 等)→ searchable: 1 + options_json(value 与 DDL 存库一致)
  • boolean 业务列:display_type: boolean 且 options_json 写中文 label(示例见下)

禁止:

  • 仅为筛选把存库 value 改成中文
  • 在文档或 description 要求用户记内部 magic number / 英文 code

示例(启用开关 + 中文筛选):

{
    "column_name": "enabled",
    "display_name": "是否启用",
    "visible": 1,
    "searchable": 1,
    "editable": 1,
    "display_type": "boolean",
    "options_json": json.dumps(
        [{"value": 0, "label": "停用"}, {"value": 1, "label": "启用"}],
        ensure_ascii=False,
    ),
}

Action 参数表单无列筛选对称能力;筛选语义以本节为准,表单对照见 ACTIONS.md「表单字段类型对照」。

editable 与 createable 的关系

技能模板只维护 editable 这一份字段权限声明:

skill metadata:
  editable

host runtime:
  createable / editable

宿主根据以下因素计算字段是否能够出现在新增或编辑表单中:

  • 表级 readonly
  • 字段 editable
  • 主键
  • generated 字段
  • sensitive 字段
  • BLOB 类型
  • not-null 约束
  • default value

通用规则:

新增表单:
  editable = 1
  且不是主键
  且不是 generated
  且不是敏感字段
  且不是 BLOB

编辑表单:
  editable = 1
  且不是主键
  且不是 generated
  且不是敏感字段
  且不是 BLOB

若一张表:

readonly = 0
但所有字段 editable = 0

则:

  • 这张表技术上可写,但不适合手工新增或编辑
  • 宿主不应打开空白新增表单
  • 技能应通过自身业务流程产生数据
  • 这不是宿主猜测业务,而是技能通过元数据声明的结果

系统字段标准

通常保持 editable = 0 的字段类型包括:

  • 自增主键
  • generated 字段
  • created_at / updated_at
  • 同步时间戳
  • 内容哈希
  • 计算结果
  • 内部状态
  • 删除标记
  • 审计字段
  • 敏感凭据或密钥字段

不要把具体业务字段名写死;按字段类型和职责设置 editable。

表结构标准

  • 业务表和字段使用英文 snake_case
  • 可写表优先使用单字段主键
  • 自增主键优先放在 CREATE TABLE 第一列
  • created_at 和 updated_at 放在业务字段末尾
  • 字段展示顺序以 CREATE TABLE 和 PRAGMA table_info(cid) 为准
  • 不依赖 display_order 重新排列字段
  • 不要让宿主通过英文字段名推断展示顺序
  • 复杂字段需要通过 display_type 或 visible 明确处理

推荐顺序(可按业务裁剪,但已有字段应遵守相对位置):

1. 主键 id
2. 关联标识与核心业务字段
3. 普通业务字段
4. 状态、结果、错误等辅助字段
5. created_at(如有)
6. updated_at(如有)

标准时间字段(新技能统一规范)

新技能业务表若需要时间审计字段,统一采用 Unix 秒级时间戳,不要使用毫秒时间戳、ISO 字符串或 SQLite 文本 datetime。

项目 规范
物理字段 created_at INTEGER、updated_at INTEGER
存储格式 Unix 秒级整数
展示元数据 display_type datetime_unix_seconds
中文名 created_at → 创建时间;updated_at → 更新时间
用户编辑 审计字段 editable = 0(系统维护,列表只展示格式化结果,不进可编辑表单)
字段顺序 位于业务字段末尾:… → created_at → updated_at
排序 不使用 display_order;顺序仅来自 PRAGMA table_info

业务可编辑时间 vs 审计时间

场景 editable 列表 新增/编辑表单
created_at / updated_at 等审计列 0 格式化为 YYYY-MM-DD HH:mm:ss 不出现(或只读,由宿主既有规则)
业务时间列(预约、截止、活动开始等) 1 同上格式化 日期时间控件;提交写入 Unix 秒整数

宿主对 display_type = datetime_unix_seconds:

  • 列表:格式化为 YYYY-MM-DD HH:mm:ss 展示,不改库内原始整数
  • 可编辑表单:提供日期时间选择;保存时写回 Unix 秒(不要写 ISO 字符串、不要写毫秒)

自动生成与维护

  • created_at:INTEGER NOT NULL DEFAULT (unixepoch());插入时可省略,由数据库生成。
  • updated_at:INTEGER NOT NULL DEFAULT (unixepoch());插入时同样可省略。
  • updated_at 更新:业务表通过 SQLite trigger {table}_set_updated_at 在业务字段更新后自动刷新(init_updated_at_trigger)。

实现参考:

  • DDL 与 trigger:scripts/db/connection.py、scripts/db/timestamp_columns.py
  • 中文元数据种子:scripts/db/display_metadata.py
  • 自检:scripts/db/display_metadata_validator.py

系统自动写入表的只读约定

技能内部系统自动写入、不供用户在宿主数据管理中手工维护的表,应声明:

层级 值 说明
表级 readonly 1 宿主数据管理只读展示,不提供新增/编辑/删除表单
相关业务字段 editable 0 元数据权限只约束宿主 UI,不限制技能代码写库

模板默认不建此类审计表。过程看 jiangchang.log,进度/终态看任务中心,业务结果看业务表。

业务行生命周期(处理队列)

若技能对某一业务行做「生成 / 触达 / 私信 / 发布」等有副作用处理,状态字段须遵守 角色模型(可操作 / 进行中 / 成功·失败·跳过类终态),不要把某一技能的状态名当成全平台唯一枚举。默认只处理可操作态;重做靠人工改回可操作。完整规则见 WORK_ITEM_STATUS.md。

建议:

项 说明
状态列 options_json 列出本技能全部业务状态(含进行中与各类终态)
失败原因 失败类终态应有用户可读的原因字段(勿写密钥)
可编辑 终态/进行中建议可改回可操作,或提供等价 Action
grid_help 可一句话说明「仅某某状态可处理」

表级 readonly = 1 时,宿主数据管理只读展示;技能 repository 仍可写入。

通用示例:demo_items(测试 fixture 参考)

以下表结构用于模板测试演示数据管理契约,不是模板默认业务表:

CREATE TABLE demo_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT,
    status TEXT NOT NULL DEFAULT 'draft',
    generated_value TEXT,
    created_at INTEGER NOT NULL DEFAULT (unixepoch()),
    updated_at INTEGER NOT NULL DEFAULT (unixepoch())
);

对应元数据要点:

物理字段 editable 说明
id 0 自增主键
title 1 业务字段
description 1 业务字段
status 1 枚举,options_json 演示
generated_value 0 系统/计算字段
created_at 0 系统时间
updated_at 0 系统时间

枚举字段 options_json

options_json 支持两种形态:

1)静态枚举(数组)

每项包含 value 与 label:

[
  {"value": "draft", "label": "草稿"},
  {"value": "published", "label": "已发布"},
  {"value": "archived", "label": "已归档"}
]

宿主据此渲染选择框;技能侧负责保证 JSON 合法且与物理字段取值一致。

2)跨表引用 lookup(对象,用于关联 id 字段)

当字段取值依赖同库另一张业务表时,display_type 设为 lookup,options_json 写对象(不是数组)。这是展示与表单选择契约,不是 SQLite 外键。

单选(默认,与既有行为一致):

{
  "ref_table": "platforms",
  "value_column": "platform_key",
  "label_column": "display_name",
  "extra_column": "category",
  "filter_column": "enabled",
  "filter_value": 1
}

多选(平行扩展,仅多一个开关):

{
  "ref_table": "engines",
  "value_column": "id",
  "label_column": "display_name",
  "filter_column": "enabled",
  "filter_value": 1,
  "multiple": true
}
字段 必填 说明
ref_table 是 同库被引用表名(snake_case 标识符)
value_column 是 写入当前字段的值列(业务 id / 编号)
label_column 是 选项主文案列(名称)
extra_column 否 第三展示列(不必叫「备注」);有值时拼进括号
filter_column / filter_value 否 等值过滤(如只列出 enabled=1)
multiple 否 true = 多选 Lookup;省略或 false = 单选(默认)

宿主在加载表 schema 时查询 ref_table,把结果展开成 {value,label} 选项列表。

存库约定:

模式 物理列建议 存什么
单选 整数或文本标量 一个业务 id(如 2、douyin)
多选 TEXT 逗号分隔 id,如 1,2,5(与 $selection.ids 风格一致);不要建联接子表当 UI 多选

统一展示文案(宿主固定一种,技能只配列):

  • 单选默认:{value} · {label},例如 2 · 通用私信-留资、douyin · 抖音
  • 配置了 extra_column 且该行有非空值:{value} · {label}({extra})
  • 未配置 extra_column、或额外列为空:不加 ()
  • label 为空或与 value 相同时:只显示 value(可再带额外括号)
  • 多选列表格:按 id 拆开后逐个映射为 label,再用顿号/逗号拼接(如 豆包、元宝);过长可截断并提示「+N」

控件呈现(宿主策略,非第二套契约):

  • 单选:下拉 Select(现状)
  • 多选且选项较少(建议 ≤6):勾选列表
  • 多选且选项较多:字段内 Chip + 可搜索下拉(不是再开一层业务弹窗)

Action 弹窗内联 Lookup 与上表 同构(字段名一致,含 multiple),见 ACTIONS.md「跨表引用:Lookup」。

示例:账号表的平台字段 → 平台信息表;凭据表的账号编号 → 账号信息表;私信 Action 选话术 → message_templates;抽检 Action 多选引擎 → engines + multiple: true。

幂等初始化标准

  • _jiangchang_tables 和 _jiangchang_columns 必须通过 upsert 初始化
  • 多次执行初始化不能重复记录
  • 重新初始化可以更新展示名、描述、grid_help、visible、searchable、editable、display_type、options_json
  • 元数据不能残留旧字段(删除或重命名字段时必须同步清理)
  • 元数据不能引用不存在的表或字段
  • 新增字段或表时必须同步增加元数据
  • 不要直接修改用户真实数据库作为迁移方式

实现 helper:

  • upsert_table_metadata / upsert_column_metadata — 幂等写入
  • seed_column_metadata_batch — 批量 upsert;可选传入 valid_column_names 时在 upsert 后自动 prune
  • prune_stale_column_metadata — 清理已删除物理字段的元数据

新增业务表时如何初始化

  1. 在 connection.py(或迁移脚本)中 CREATE TABLE 真实物理表。
  2. 用 PRAGMA table_info 或 list_physical_column_names 取得真实物理字段列表(顺序以 cid 为准)。
  3. 调用 upsert_table_metadata 写入表级元数据。
  4. 调用 seed_column_metadata_batch,并传入 valid_column_names=physical_column_names:
from db.display_metadata import list_physical_column_names, seed_column_metadata_batch, upsert_table_metadata

physical = list_physical_column_names(cursor, "demo_items")
upsert_table_metadata(cursor, table_name="demo_items", display_name="示例记录", readonly=0)
seed_column_metadata_batch(
    cursor,
    table_name="demo_items",
    columns=DEMO_ITEMS_COLUMN_METADATA,
    valid_column_names=physical,
)

传入 valid_column_names 时:先 upsert,再删除不在物理表中的陈旧 _jiangchang_columns 行。

字段删除或重命名时如何清理

  1. 在 DDL / 迁移中完成物理字段删除或重命名。
  2. 重新执行 seed_column_metadata_batch(..., valid_column_names=当前物理字段列表),自动 prune 旧字段元数据。
  3. 或单独调用 prune_stale_column_metadata(cursor, table_name, valid_column_names)。

不传入 valid_column_names 时不会自动 prune——模板不会猜测真实物理字段,避免误删;文档与实现均以此为准。

不要直接修改用户真实数据库作为迁移方式;元数据初始化必须使用真实物理字段列表。

表增删改与退役(技能 schema 迁移)

技能拥有 SQLite 物理 schema;宿主只渲染已注册的元数据。增删改表时技能须在 init_db() 中做幂等迁移,并与 _jiangchang_* 同步。

操作 技能必须做
新增用户可见表 CREATE TABLE + upsert_table_metadata + 列元数据种子
修改表/列 物理迁移(ALTER / 重建)+ 更新或 prune_stale_column_metadata
临时隐藏(表仍保留) 元数据 visible=0(表与元数据行都保留)
永久废弃用户可见表 drop_deprecated_user_table(...)(见下)

永久废弃用户可见表

使用 scripts/db/display_metadata.py 的 drop_deprecated_user_table(幂等):

  1. DROP TRIGGER(若有 {table}_set_updated_at 等)
  2. DROP TABLE IF EXISTS
  3. DELETE 对应 _jiangchang_columns / _jiangchang_tables 行
from db.display_metadata import drop_deprecated_user_table

def init_db() -> None:
    ...
    drop_deprecated_user_table(cur, "task_logs")
    init_display_metadata(cur)

反模式(禁止): 只删 _jiangchang_* 元数据、保留物理表。宿主虽可能不展示孤儿表,但库内残留、自检失败,且违背「元数据与物理结构一致」。

新技能禁止创建或写入 task_logs 一类通用任务审计表。过程 → jiangchang.log / 运行日志;进度/终态 → 任务中心;业务结果 → 业务表(见 WORK_ITEM_STATUS.md、../development/LOGGING.md)。

旧库迁移测试(废弃表时必做)

复制 tests/samples/test_table_deprecation_migration.py.sample,构造「旧库含废弃表 + 元数据 + 历史行」→ init_db() 后断言:

  • sqlite_master 无该表
  • _jiangchang_* 无残留行
  • iter_user_tables 不含该表

卸载技能不删用户数据目录;重装会复用旧库,迁移必须在 init_db() 中完成(见 ../development/DATA_PATHS.md)。

中文命名质量

  • 使用普通用户能理解的业务用语:error_msg → 错误信息,created_at → 创建时间。
  • 禁止:Task Logs、input编号、errormsg、直接把物理名当 display_name。
  • 不宜向用户暴露的实现细节(如 payload_json、selector)应设 visible = 0 或给出可理解中文名。

元数据同步规则

  1. 新增用户可见业务表 → 同步 _jiangchang_tables(visible=1 方可在数据管理展示)。
  2. 新增用户可见字段 → 同步 _jiangchang_columns。
  3. 删除/重命名表或字段 → 物理迁移 + 同步清理元数据;废弃表用 drop_deprecated_user_table,禁止只删元数据留物理表。
  4. 初始化脚本必须幂等。
  5. 元数据中的表、字段必须在库中真实存在;可见项不得缺少中文 display_name。
  6. init_db() 后:iter_user_tables 中每个物理业务表都应有对应 _jiangchang_tables.display_name,或已被 drop_deprecated_user_table 物理删除。

开发自检

  • init_db() 后运行 tests/test_display_metadata.py 与 tests/test_data_management_contract.py。
  • 若提供宿主 Action 入口,运行 tests/test_actions_manifest.py。
  • 复制为真实技能后,使用 scripts/db/display_metadata_validator.py 中的校验函数检查 SKILL 名称、slug 与元数据完整性。

模板原则

  • 模板不做复杂历史迁移框架;新 skill 从当前 schema 起步。
  • 元数据 DDL 与种子 helper 以 scripts/db/display_metadata.py 为单一权威来源。
  • 模板定义「如何声明能力」;具体技能定义「声明什么业务能力」;宿主负责「如何通用渲染和执行」。