@antprofuse/saddle-db-design 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md ADDED
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: saddle-db-design
3
+ description: 在 Saddle 后端研发前,从 Islands Spec 与已确认的前后端 API 契约设计可追溯的数据库表结构 YAML。适用于判定内部持久化实体、定义字段类型与约束、设计索引和关联,并报告局部输入缺口。不用于 API 设计、外部 RPC 设计、生成 SQL 或编写数据库访问代码。
4
+ ---
5
+
6
+ # Saddle DB 设计
7
+
8
+ 为 Saddle 后端需要持久化的内部业务实体产出完整、自包含、可追溯的表结构 YAML。数据库是 API 后端实现与持久化之间的边界,不能反向决定前后端 API。
9
+
10
+ 执行前必须阅读 [references/db-table-v1.md](references/db-table-v1.md)。遇到实体归属、生命周期、字段来源、唯一性、类型、关联或索引无法唯一确定时,阅读 [references/findings.md](references/findings.md)。
11
+
12
+ ## 输入
13
+
14
+ - 用户明确提供的交付目录;不得自行改用固定仓库路径。
15
+ - 已编译 Islands Spec 中相关 Structure、Logic、Rule、Visual 与 Test Responsibility。
16
+ - 已确认的前后端 API 契约,尤其是字段来源、派生依赖和调用责任。
17
+ - 当前 Saddle 数据库平台已经确认的物理能力;不得从旧 Harness 规范继承未经确认的策略。
18
+
19
+ ## 工作流程
20
+
21
+ 1. 先按 API 后端责任识别需要持久化的业务事实,再回到 Structure 判断实体归属与生命周期。不能看到实体就机械建表。
22
+ 2. 将实体分类为内部持久化、外部事实或瞬时/派生事实。只有内部持久化实体进入表设计。
23
+ 3. 每个内部持久化实体产出一个 v1 表结构 YAML;逐字段关联 Structure,并明确物理类型、nullable、主键、唯一约束、关联和索引。
24
+ 4. 使用 API access pattern 证明表和索引服务于哪些读写责任。不能用“以后可能查询”增加字段或索引。
25
+ 5. 跨文件检查表名、列名、约束名全局唯一,引用目标存在且类型兼容。
26
+ 6. 只交付完整表文件。局部不确定项写入 findings,并继续设计不受影响的实体。
27
+
28
+ ## 产出
29
+
30
+ 在用户提供的交付目录中写入:
31
+
32
+ - 每个内部持久化实体一个 `<table_name>.table.yaml`;
33
+ - 存在问题时写一个 `db-design-findings.yaml`。
34
+
35
+ 使用 [assets/table.template.yaml](assets/table.template.yaml) 作为起始结构。交付文件不得包含 TODO、占位符、阻塞标记或诊断节点。
36
+
37
+ 不生成、拼接、维护或要求 `structure.sql`。DDL 与迁移脚本属于后续确定性生成和部署工作,不是本 Skill 的设计产物。
38
+
39
+ ## 边界
40
+
41
+ - API 先于 DB;表结构必须链接其支持的 API 后端责任,但 API 字段不必机械等同于数据库列。
42
+ - 标记为外部、外部共享、外部只读或由 external 能力维护的实体不建本地表。
43
+ - 查询响应、页面模型、统计值、当次外部快照和可重新计算派生值默认不建表;只有 Spec 明确要求跨请求持久保存且生命周期闭合时才可进入表。
44
+ - 不默认增加代理主键、审计时间、软删除、版本号、租户字段或状态列。每个字段都必须有明确来源或已确认的工程必要性。
45
+ - 不默认禁用或启用外键、CHECK、JSON、生成列、触发器等能力;v1 中未定义的物理能力产出 `unsupported-shape` finding,等待后续版本扩展。
46
+ - 一个不完整实体只阻塞对应表,不阻塞其他完整表的交付。
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: "Saddle DB 设计"
3
+ short_description: "从 Islands Spec 与 API 契约设计可追溯的表结构"
@@ -0,0 +1,19 @@
1
+ schemaVersion: saddle-db-table/v1
2
+ entity:
3
+ source: {entity: 替换为内部持久化实体}
4
+ persistenceReason: 替换为可追溯的持久化原因。
5
+ table:
6
+ name: replace_with_table_name
7
+ comment: 替换为表的业务语义。
8
+ columns:
9
+ replace_with_identity_column:
10
+ type: {kind: varchar, length: 64}
11
+ nullable: false
12
+ comment: 替换为字段语义。
13
+ source: {entity: 替换为内部持久化实体, field: 替换为稳定身份字段}
14
+ primaryKey:
15
+ columns: [replace_with_identity_column]
16
+ uniqueConstraints: []
17
+ indexes: []
18
+ references: []
19
+ apiResponsibilities: []
package/package.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@antprofuse/saddle-db-design",
3
+ "version": "0.1.0",
4
+ "description": "从 Islands Spec 与 API 契约设计可追溯的 Saddle 数据库表结构。",
5
+ "license": "MIT OR Apache-2.0",
6
+ "files": ["SKILL.md", "agents", "assets", "references"],
7
+ "publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"}
8
+ }
@@ -0,0 +1,196 @@
1
+ # Saddle DB 表结构 v1
2
+
3
+ ## 设计单位与文件身份
4
+
5
+ 一个 `*.table.yaml` 只定义一个内部持久化实体的一张表。v1 不支持一个实体拆多表或多实体合表;遇到该场景产出 `unsupported-shape` finding。
6
+
7
+ 文件固定命名为 `<table_name>.table.yaml`。`table.name` 必须是全局唯一的 snake_case 标识符,并与文件名去掉 `.table.yaml` 后原样一致。
8
+
9
+ ## 完整结构
10
+
11
+ ```yaml
12
+ schemaVersion: saddle-db-table/v1
13
+ entity:
14
+ source: {entity: 用户账单}
15
+ persistenceReason: 用户账单由本系统创建并跨请求保留,供查询与状态更新使用。
16
+ table:
17
+ name: user_bill
18
+ comment: 用户账单持久化事实
19
+ columns:
20
+ bill_id:
21
+ type: {kind: varchar, length: 64}
22
+ nullable: false
23
+ comment: 用户账单稳定标识
24
+ source: {entity: 用户账单, field: 账单ID}
25
+ user_id:
26
+ type: {kind: varchar, length: 64}
27
+ nullable: false
28
+ comment: 账单所属用户标识
29
+ source: {entity: 用户账单, field: 用户ID}
30
+ amount:
31
+ type: {kind: decimal, precision: 18, scale: 2}
32
+ nullable: false
33
+ comment: 应缴金额,单位为人民币元,按业务金额规则舍入。
34
+ source: {entity: 用户账单, field: 应缴金额}
35
+ bill_status:
36
+ type: {kind: varchar, length: 32}
37
+ nullable: false
38
+ comment: 账单状态
39
+ source: {entity: 用户账单, field: 账单状态}
40
+ primaryKey:
41
+ columns: [bill_id]
42
+ uniqueConstraints: []
43
+ indexes:
44
+ - name: idx_user_bill_user_status
45
+ columns:
46
+ - {column: user_id, order: asc}
47
+ - {column: bill_status, order: asc}
48
+ purpose: 支持按当前用户和账单状态查询账单。
49
+ supports:
50
+ - {apiId: query_user_bill, accessPattern: 按用户与状态过滤}
51
+ references: []
52
+ apiResponsibilities:
53
+ - apiId: query_user_bill
54
+ operations:
55
+ - kind: read
56
+ columns: [bill_id, user_id, amount, bill_status]
57
+ predicateColumns: [user_id, bill_status]
58
+ - apiId: update_user_bill_status
59
+ operations:
60
+ - kind: update
61
+ columns: [bill_status]
62
+ predicateColumns: [bill_id]
63
+ ```
64
+
65
+ 顶层字段 `schemaVersion / entity / table / columns / primaryKey / uniqueConstraints / indexes / references / apiResponsibilities` 全部必填。没有内容的集合显式写 `[]`,禁止未知顶层字段。
66
+
67
+ ## Entity 与持久化判定
68
+
69
+ `entity.source` 使用 Islands Structure 的稳定实体身份。`persistenceReason` 说明为什么该业务事实必须由 Saddle 跨请求保存,并必须能从 Logic 生命周期和 API 后端责任得到证明。
70
+
71
+ 以下内容不得产出表文件:
72
+
73
+ - Spec 标记为 external、外部共享或外部只读的实体;
74
+ - 由真实外部 SOFA RPC 负责维护的业务事实;
75
+ - 页面状态、API response model、统计结果与可重新计算派生字段;
76
+ - 只在一次请求或一次外部查询中成立的快照;
77
+ - 生命周期、唯一身份或所有权尚未闭合的候选实体。
78
+
79
+ “页面需要展示”或“API 需要返回”本身不构成持久化理由。
80
+
81
+ ## 表与列命名
82
+
83
+ 表名、列名、约束名和索引名均使用完整、无歧义的英文 snake_case,不使用缩写。表名和列名分别在其作用域内唯一。
84
+
85
+ 表名、列名及命名对象格式为 `^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$`。名称必须稳定,实现层不得再次重命名。
86
+
87
+ ## 列来源
88
+
89
+ 每个业务列必须具有且仅具有一个直接 Structure 来源:
90
+
91
+ ```yaml
92
+ source: {entity: 用户账单, field: 账单状态}
93
+ ```
94
+
95
+ v1 不把派生值作为持久化列。若业务明确要求保存派生结果,但其刷新时机、一致性和来源依赖已经闭合,产出 `unsupported-shape` finding 以驱动后续格式扩展,不能先用自然语言列绕过。
96
+
97
+ 不得默认增加 `id`、`created_at`、`updated_at`、`deleted`、`version`、`tenant_id` 等工程字段。只有它们已是 Structure 字段或存在已确认、可追溯的系统级持久化规范时才能加入;v1 暂不定义自由 `systemField`。
98
+
99
+ ## 物理类型
100
+
101
+ v1 面向 Saddle 当前 MySQL/MariaDB 数据源,允许以下封闭类型:
102
+
103
+ ```yaml
104
+ {kind: varchar, length: 64}
105
+ {kind: text}
106
+ {kind: signed_integer, bits: 32}
107
+ {kind: signed_integer, bits: 64}
108
+ {kind: unsigned_integer, bits: 32}
109
+ {kind: unsigned_integer, bits: 64}
110
+ {kind: decimal, precision: 18, scale: 2}
111
+ {kind: boolean}
112
+ {kind: date}
113
+ {kind: datetime, precision: 6}
114
+ {kind: binary, length: 32}
115
+ ```
116
+
117
+ 规则:
118
+
119
+ - `varchar.length` 与 `binary.length` 必须是正整数,来自业务最大长度或已确认编码上界,不能随意统一为 255。
120
+ - integer 的 signed 与 bits 必须由值域证明。
121
+ - decimal 必须明确 precision、scale、单位及舍入语义;单位写入 comment,舍入语义来自 Structure/Rule。
122
+ - `date` 表示 `YYYY-MM-DD` 业务日期;`datetime` 表示精确时刻,必须有统一时区语义。
123
+ - 枚举在 v1 中映射为具有充分 length 的 `varchar`,合法值仍由 Structure 枚举定义;DB CHECK 策略尚未纳入 v1。
124
+ - JSON、浮点数、数据库 enum、blob、timestamp、生成列及其他类型不在 v1 中,遇到时产出 `unsupported-shape` finding。
125
+
126
+ API 类型不能单独决定 DB 类型;必须结合 Structure 值域、持久化语义和查询需求。
127
+
128
+ ## Nullable
129
+
130
+ `nullable` 表示持久化事实是否允许缺失,不表示页面暂时不展示、API 字段可选或 external 偶发未返回。
131
+
132
+ 必须从实体生命周期证明 nullable。若字段仅在某状态后产生,需要确认是允许 NULL、拆分实体,还是业务模型缺少状态事实;DB Skill 不自行选择。
133
+
134
+ ## 主键与唯一约束
135
+
136
+ `primaryKey.columns` 是非空列数组。主键优先使用 Structure 已定义的稳定实体身份,不能默认制造代理主键。
137
+
138
+ 复合业务身份使用 `uniqueConstraints`:
139
+
140
+ ```yaml
141
+ uniqueConstraints:
142
+ - name: uq_user_bill_user_period
143
+ columns: [user_id, bill_period]
144
+ reason: 同一用户同一账期最多存在一张本系统账单。
145
+ evidence:
146
+ - {entity: 用户账单, fields: [用户ID, 账期]}
147
+ ```
148
+
149
+ 唯一性必须由 Structure/Logic 明确证明;案例数据不构成唯一性证据。
150
+
151
+ ## References
152
+
153
+ 跨内部表关联显式写入:
154
+
155
+ ```yaml
156
+ references:
157
+ - name: ref_user_bill_user
158
+ columns: [user_id]
159
+ target:
160
+ entity: 用户
161
+ table: user
162
+ columns: [user_id]
163
+ enforcement: logical
164
+ reason: 当前平台尚未确认使用数据库外键约束,由后端保持引用一致性。
165
+ ```
166
+
167
+ `enforcement` v1 只允许 `logical`。是否启用数据库 FOREIGN KEY 尚未形成平台统一规范,不由业务 Agent 自行打开。
168
+
169
+ 引用 external 实体时不能建立目标表或 reference;只在本实体确有业务需要时保存 external 的稳定标识字段。
170
+
171
+ ## Indexes 与 API responsibility
172
+
173
+ 索引必须由已确认 API 的实际访问模式证明。每个索引包含稳定 name、有序 columns、purpose 和至少一个 supports。
174
+
175
+ `apiResponsibilities` 穷举当前表支持的 API 读写责任:
176
+
177
+ - `kind` 只允许 `read / insert / update / delete`;
178
+ - `columns` 是读取、插入或变更的列;
179
+ - `predicateColumns` 是定位或过滤使用的列;
180
+ - `apiId` 必须解析到已确认 API 契约。
181
+
182
+ 不要求每个 predicate 都单独建立索引;应按组合过滤、排序、唯一性和预期访问方式形成最小充分索引。没有 API 或 Logic 证据的预防性索引禁止加入。
183
+
184
+ ## 完成门禁
185
+
186
+ 表文件可交付必须满足:
187
+
188
+ 1. 实体已证明为内部持久化实体;
189
+ 2. 文件名与 `table.name` 原样一致且全局唯一;
190
+ 3. 每个业务列都能解析到唯一 Structure 字段;
191
+ 4. 类型参数、nullable、主键与唯一性均有依据;
192
+ 5. reference 目标存在、列数和类型一致;
193
+ 6. 每个索引都有 API/Logic 访问证据;
194
+ 7. 每个 `apiId` 都能解析到已确认 API;
195
+ 8. 不存在 external 多建表、瞬时数据落库、TODO、占位符或诊断标记;
196
+ 9. 交付目录中不存在本 Skill 生成的 `structure.sql`。
@@ -0,0 +1,27 @@
1
+ # DB 设计 Findings v1
2
+
3
+ 当输入不足或 v1 格式不支持某个确定需求时,在用户指定的交付目录写入 `db-design-findings.yaml`:
4
+
5
+ ```yaml
6
+ findingsVersion: saddle-db-findings/v1
7
+ findings:
8
+ - id: BILL-DB-001
9
+ severity: blocker
10
+ category: persistence-ownership-unknown
11
+ affected:
12
+ entity: 欠费单
13
+ path: entity.persistenceReason
14
+ evidence:
15
+ - {kind: structureEntity, entity: 欠费单}
16
+ - {kind: apiContract, apiId: query_bill}
17
+ message: 欠费单被标记为外部共享的当次查询事实,当前没有本系统持久化生命周期证据。
18
+ requiredUpstreamChange: 明确该事实由外部实时提供,或补齐由 Saddle 创建、更新和失效的生命周期。
19
+ ```
20
+
21
+ 必填字段为 `id / severity / category / affected / evidence / message / requiredUpstreamChange`。
22
+
23
+ 严重程度为 `blocker / warning`。
24
+
25
+ v1 分类:`persistence-ownership-unknown / external-entity-local-table-conflict / missing-entity-identity / missing-field-source / missing-type-bound / nullable-ambiguous / unique-constraint-ambiguous / reference-target-missing / api-responsibility-missing / unsupported-shape`。
26
+
27
+ Finding 必须精确到实体、表或字段。一个实体的问题不能阻塞其他完整表文件。Finding 不进入 `*.table.yaml`,也不授权 Agent 创建临时字段、宽泛类型或冗余表来绕过问题。