@antprofuse/saddle-db-design 0.1.0 → 0.1.1

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 CHANGED
@@ -1,46 +1,45 @@
1
1
  ---
2
2
  name: saddle-db-design
3
- description: 在 Saddle 后端研发前,从 Islands Spec 与已确认的前后端 API 契约设计可追溯的数据库表结构 YAML。适用于判定内部持久化实体、定义字段类型与约束、设计索引和关联,并报告局部输入缺口。不用于 API 设计、外部 RPC 设计、生成 SQL 或编写数据库访问代码。
3
+ description: 在 Saddle 后端研发前,从已编译 Islands Spec 设计可追溯的逻辑数据库表契约。适用于判定 Saddle 持久化实体、定义字段与基础 MySQL 类型、关联 Logic 事务访问责任,并报告局部输入缺口。不用于 API、External RPC、物理表名映射、索引优化、DDL 或数据库访问代码设计。
4
4
  ---
5
5
 
6
6
  # Saddle DB 设计
7
7
 
8
- Saddle 后端需要持久化的内部业务实体产出完整、自包含、可追溯的表结构 YAML。数据库是 API 后端实现与持久化之间的边界,不能反向决定前后端 API
8
+ Islands Spec 确定性产出 Saddle 拥有的逻辑表契约。数据库设计覆盖 API、系统事务、定时任务、消息和回调等全部 Saddle 持久化责任;前端 API 只是可选入口,不是建表前提。
9
9
 
10
- 执行前必须阅读 [references/db-table-v1.md](references/db-table-v1.md)。遇到实体归属、生命周期、字段来源、唯一性、类型、关联或索引无法唯一确定时,阅读 [references/findings.md](references/findings.md)。
10
+ 执行前必须阅读 [references/db-table-v1.md](references/db-table-v1.md)。遇到实体归属、生命周期、字段来源、类型或事务责任无法唯一确定时,阅读 [references/findings.md](references/findings.md)。
11
11
 
12
12
  ## 输入
13
13
 
14
14
  - 用户明确提供的交付目录;不得自行改用固定仓库路径。
15
- - 已编译 Islands Spec 中相关 Structure、Logic、Rule、Visual 与 Test Responsibility。
16
- - 已确认的前后端 API 契约,尤其是字段来源、派生依赖和调用责任。
17
- - 当前 Saddle 数据库平台已经确认的物理能力;不得从旧 Harness 规范继承未经确认的策略。
15
+ - 已编译 Islands Spec 中相关 Structure、Logic、Rule 与 Test Responsibility。
16
+ - 已确认的 API 契约可作为事务入口证据,但不是必需输入。
18
17
 
19
18
  ## 工作流程
20
19
 
21
- 1. 先按 API 后端责任识别需要持久化的业务事实,再回到 Structure 判断实体归属与生命周期。不能看到实体就机械建表。
22
- 2. 将实体分类为内部持久化、外部事实或瞬时/派生事实。只有内部持久化实体进入表设计。
23
- 3. 每个内部持久化实体产出一个 v1 表结构 YAML;逐字段关联 Structure,并明确物理类型、nullable、主键、唯一约束、关联和索引。
24
- 4. 使用 API access pattern 证明表和索引服务于哪些读写责任。不能用“以后可能查询”增加字段或索引。
25
- 5. 跨文件检查表名、列名、约束名全局唯一,引用目标存在且类型兼容。
26
- 6. 只交付完整表文件。局部不确定项写入 findings,并继续设计不受影响的实体。
20
+ 1. Logic 生命周期识别必须跨事务保留并在后续读取、更新或判定的事实。
21
+ 2. 回到 Structure 确认实体所有权。只有未声明 `external: true` 的实体才可能建表;本地所有权不自动等于需要持久化。
22
+ 3. 每个需要持久化的具体实体产出一个完整表契约。表与业务列原样复用 Structure 实体和字段身份,并包含全部 Structure 字段。
23
+ 4. 子实体表展开全部继承字段。父实体只有存在独立持久化生命周期时才单独建表。
24
+ 5. 精确关联每个访问该表的 Logic 事务及其读写字段。事务由 API 承载时可附加 `apiId`,但不得用 API 身份替代 Logic 事务身份。
25
+ 6. 跨文件检查逻辑表身份唯一、字段来源可解析、关联目标存在、类型完整。
26
+ 7. 只交付完整表文件。局部不确定项按 findings 规则报告,不用临时字段、宽泛类型或冗余表绕过。
27
27
 
28
28
  ## 产出
29
29
 
30
- 在用户提供的交付目录中写入:
31
-
32
- - 每个内部持久化实体一个 `<table_name>.table.yaml`;
33
- - 存在问题时写一个 `db-design-findings.yaml`。
30
+ - 在用户指定的 DB 交付目录中,每个持久化实体产出一个 `<Structure实体名>.table.yaml`。
31
+ - DB 交付目录只放正式表契约。Findings 写入用户指定的过程协作位置;未指定时直接反馈给用户,不在交付目录落盘。
34
32
 
35
33
  使用 [assets/table.template.yaml](assets/table.template.yaml) 作为起始结构。交付文件不得包含 TODO、占位符、阻塞标记或诊断节点。
36
34
 
37
- 不生成、拼接、维护或要求 `structure.sql`。DDL 与迁移脚本属于后续确定性生成和部署工作,不是本 Skill 的设计产物。
35
+ 不生成物理表名/列名映射、索引、外键、唯一约束、CHECK、DDL、迁移脚本或数据库访问代码。Saddle 的必填映射能力只负责逻辑表名/列名到物理标识符的映射,不拥有其他表结构设计权。
38
36
 
39
- ## 边界
37
+ ## 固定边界
40
38
 
41
- - API 先于 DB;表结构必须链接其支持的 API 后端责任,但 API 字段不必机械等同于数据库列。
42
- - 标记为外部、外部共享、外部只读或由 external 能力维护的实体不建本地表。
43
- - 查询响应、页面模型、统计值、当次外部快照和可重新计算派生值默认不建表;只有 Spec 明确要求跨请求持久保存且生命周期闭合时才可进入表。
44
- - 不默认增加代理主键、审计时间、软删除、版本号、租户字段或状态列。每个字段都必须有明确来源或已确认的工程必要性。
45
- - 不默认禁用或启用外键、CHECK、JSON、生成列、触发器等能力;v1 中未定义的物理能力产出 `unsupported-shape` finding,等待后续版本扩展。
39
+ - 表契约使用 MySQL 基础类型,但逻辑表名和业务列名保持 Islands 原始业务概念。
40
+ - 每张表固定使用 `uid`、`gmt_create`、`gmt_modified`、`is_deleted` 四个系统列;业务 Agent 不增删或改写其语义。
41
+ - 所有业务字段允许 `NULL`,且不设置数据库默认值。必填、枚举、值域、业务唯一性和关联完整性由 Saddle 业务逻辑负责。
42
+ - 不设计业务唯一约束、普通索引或数据库外键。索引属于运行后的观测与运维优化。
43
+ - 实体关联字段保留 Structure 的关联业务语义,值存储目标实体 `uid`。同一目标的多重关联依靠原始角色语义区分,例如卖家与买家。
44
+ - 删除统一为软删除。Saddle 数据访问层必须对普通读取强制过滤 `is_deleted = false`;业务查询不得各自实现这条规则。
46
45
  - 一个不完整实体只阻塞对应表,不阻塞其他完整表的交付。
@@ -1,3 +1,3 @@
1
1
  interface:
2
2
  display_name: "Saddle DB 设计"
3
- short_description: "从 Islands Spec 与 API 契约设计可追溯的表结构"
3
+ short_description: "从 Islands Spec 设计可追溯的逻辑表契约"
@@ -1,19 +1,45 @@
1
1
  schemaVersion: saddle-db-table/v1
2
2
  entity:
3
3
  source: {entity: 替换为内部持久化实体}
4
- persistenceReason: 替换为可追溯的持久化原因。
4
+ persistenceReason: 替换为可追溯的跨事务持久化原因。
5
5
  table:
6
- name: replace_with_table_name
6
+ name: 替换为同一个Structure实体名
7
7
  comment: 替换为表的业务语义。
8
+ systemColumns:
9
+ uid:
10
+ type: {kind: varchar, length: 32}
11
+ nullable: false
12
+ primaryKey: true
13
+ generation: {owner: saddle, algorithm: uuid_v7, format: lowercase_hex_without_hyphens}
14
+ gmt_create:
15
+ type: {kind: datetime, precision: 3}
16
+ nullable: false
17
+ default: current_timestamp
18
+ maintainedBy: database
19
+ gmt_modified:
20
+ type: {kind: datetime, precision: 3}
21
+ nullable: false
22
+ default: current_timestamp
23
+ onUpdate: current_timestamp
24
+ maintainedBy: database
25
+ is_deleted:
26
+ type: {kind: boolean}
27
+ nullable: false
28
+ default: false
29
+ maintainedBy: saddle
30
+ readPolicy: exclude_deleted_by_default
8
31
  columns:
9
- replace_with_identity_column:
32
+ 替换为Structure字段名:
10
33
  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: []
34
+ nullable: true
35
+ comment: 替换为字段业务语义。
36
+ source: {entity: 替换为字段声明实体, field: 替换为Structure字段名}
37
+ accessResponsibilities:
38
+ - transaction:
39
+ paragraph: 替换为段落
40
+ branch: 替换为分支
41
+ transactionId: 替换为事务ID
42
+ operations:
43
+ - kind: read
44
+ columns: [替换为Structure字段名]
45
+ predicateColumns: []
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@antprofuse/saddle-db-design",
3
- "version": "0.1.0",
4
- "description": "从 Islands Spec API 契约设计可追溯的 Saddle 数据库表结构。",
3
+ "version": "0.1.1",
4
+ "description": "从 Islands Spec 设计可追溯的 Saddle 逻辑数据库表契约。",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "files": ["SKILL.md", "agents", "assets", "references"],
7
7
  "publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"}
@@ -1,10 +1,12 @@
1
- # Saddle DB 表结构 v1
1
+ # Saddle DB 逻辑表契约 v1
2
2
 
3
- ## 设计单位与文件身份
3
+ ## 设计单位与身份
4
4
 
5
- 一个 `*.table.yaml` 只定义一个内部持久化实体的一张表。v1 不支持一个实体拆多表或多实体合表;遇到该场景产出 `unsupported-shape` finding。
5
+ 一个 `*.table.yaml` 只定义一个 Saddle 内部持久化实体的一张逻辑表。v1 不支持一个实体拆多表或多实体合表;遇到该场景产出 `unsupported-shape` finding。
6
6
 
7
- 文件固定命名为 `<table_name>.table.yaml`。`table.name` 必须是全局唯一的 snake_case 标识符,并与文件名去掉 `.table.yaml` 后原样一致。
7
+ 逻辑表身份直接复用 Islands Structure 实体名。文件固定命名为 `<Structure实体名>.table.yaml`,`table.name`、`entity.source.entity` 和文件名 stem 必须原样一致。中文业务身份合法,不翻译为物理标识符。
8
+
9
+ Saddle 的必填物理映射配置负责逻辑表名/列名到 MySQL 表名/列名的映射。映射层只改名字,不得改变字段集合、类型、nullable、默认值、主键或系统语义;物理映射不属于本 Skill 产物。
8
10
 
9
11
  ## 完整结构
10
12
 
@@ -12,185 +14,194 @@
12
14
  schemaVersion: saddle-db-table/v1
13
15
  entity:
14
16
  source: {entity: 用户账单}
15
- persistenceReason: 用户账单由本系统创建并跨请求保留,供查询与状态更新使用。
17
+ persistenceReason: 用户账单由本系统创建并跨事务保留,供后续查询和状态更新。
16
18
  table:
17
- name: user_bill
19
+ name: 用户账单
18
20
  comment: 用户账单持久化事实
19
- columns:
20
- bill_id:
21
- type: {kind: varchar, length: 64}
21
+ systemColumns:
22
+ uid:
23
+ type: {kind: varchar, length: 32}
22
24
  nullable: false
23
- comment: 用户账单稳定标识
24
- source: {entity: 用户账单, field: 账单ID}
25
- user_id:
26
- type: {kind: varchar, length: 64}
25
+ primaryKey: true
26
+ generation: {owner: saddle, algorithm: uuid_v7, format: lowercase_hex_without_hyphens}
27
+ gmt_create:
28
+ type: {kind: datetime, precision: 3}
27
29
  nullable: false
28
- comment: 账单所属用户标识
29
- source: {entity: 用户账单, field: 用户ID}
30
- amount:
31
- type: {kind: decimal, precision: 18, scale: 2}
30
+ default: current_timestamp
31
+ maintainedBy: database
32
+ gmt_modified:
33
+ type: {kind: datetime, precision: 3}
32
34
  nullable: false
33
- comment: 应缴金额,单位为人民币元,按业务金额规则舍入。
34
- source: {entity: 用户账单, field: 应缴金额}
35
- bill_status:
36
- type: {kind: varchar, length: 32}
35
+ default: current_timestamp
36
+ onUpdate: current_timestamp
37
+ maintainedBy: database
38
+ is_deleted:
39
+ type: {kind: boolean}
37
40
  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
41
+ default: false
42
+ maintainedBy: saddle
43
+ readPolicy: exclude_deleted_by_default
44
+ columns:
45
+ 账单标识:
46
+ type: {kind: varchar, length: 64}
47
+ nullable: true
48
+ comment: 用户账单的业务标识
49
+ source: {entity: 用户账单, field: 账单标识}
50
+ 所属用户:
51
+ type: {kind: varchar, length: 32}
52
+ nullable: true
53
+ comment: 关联用户的 uid
54
+ source: {entity: 用户账单, field: 所属用户}
55
+ storesEntityUid: {entity: 用户}
56
+ 应缴金额:
57
+ type: {kind: decimal, precision: 18, scale: 2}
58
+ nullable: true
59
+ comment: 应缴金额,单位为人民币元
60
+ source: {entity: 用户账单, field: 应缴金额}
61
+ accessResponsibilities:
62
+ - transaction:
63
+ paragraph: 账单域·账单维护
64
+ branch: 主分支
65
+ transactionId: 创建用户账单
66
+ apiId: create_user_bill
54
67
  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
68
+ - kind: insert
69
+ columns: [账单标识, 所属用户, 应缴金额]
70
+ predicateColumns: []
71
+ - transaction:
72
+ paragraph: 账单域·账单维护
73
+ branch: 主分支
74
+ transactionId: 查询用户账单
59
75
  operations:
60
- - kind: update
61
- columns: [bill_status]
62
- predicateColumns: [bill_id]
76
+ - kind: read
77
+ columns: [账单标识, 所属用户, 应缴金额]
78
+ predicateColumns: [所属用户]
63
79
  ```
64
80
 
65
- 顶层字段 `schemaVersion / entity / table / columns / primaryKey / uniqueConstraints / indexes / references / apiResponsibilities` 全部必填。没有内容的集合显式写 `[]`,禁止未知顶层字段。
81
+ 顶层字段 `schemaVersion / entity / table / systemColumns / columns / accessResponsibilities` 全部必填,禁止未知顶层字段。示例中的 `apiId` 可省略;其他示例身份和内容必须替换为真实编译来源。
82
+
83
+ ## 持久化判定
84
+
85
+ DB 设计覆盖 Saddle 拥有的全部持久化事实,不以是否存在前端 API 为边界。只有同时满足以下条件的实体才产出表:
66
86
 
67
- ## Entity 与持久化判定
87
+ 1. 实体未声明 `external: true`;
88
+ 2. Logic 明确要求该事实跨事务保留;
89
+ 3. 后续 Logic 会读取、更新、删除或据此判定;
90
+ 4. 实体生命周期和所有权可以唯一确定。
68
91
 
69
- `entity.source` 使用 Islands Structure 的稳定实体身份。`persistenceReason` 说明为什么该业务事实必须由 Saddle 跨请求保存,并必须能从 Logic 生命周期和 API 后端责任得到证明。
92
+ 以下内容不建表:`external: true` 的实体;只由外部 SOFA RPC 维护的事实;页面状态、API response model 或请求内临时对象;一次 External 调用的临时快照;没有独立持久化生命周期的可重算结果。
70
93
 
71
- 以下内容不得产出表文件:
94
+ “页面需要展示”“API 需要返回”或“Structure 中存在实体”本身都不构成持久化理由。
72
95
 
73
- - Spec 标记为 external、外部共享或外部只读的实体;
74
- - 由真实外部 SOFA RPC 负责维护的业务事实;
75
- - 页面状态、API response model、统计结果与可重新计算派生字段;
76
- - 只在一次请求或一次外部查询中成立的快照;
77
- - 生命周期、唯一身份或所有权尚未闭合的候选实体。
96
+ ## 实体与继承
78
97
 
79
- “页面需要展示”或“API 需要返回”本身不构成持久化理由。
98
+ 一个需要持久化的具体实体对应一张逻辑表,并包含其全部 Structure 字段。子实体表直接展开父实体继承的全部字段;继承字段的 `source` 指向其原始声明实体和字段。父实体只有存在独立持久化生命周期时才单独建表。v1 不设计父子表连接或其他数据库继承策略。
80
99
 
81
- ## 表与列命名
100
+ ## 系统列
82
101
 
83
- 表名、列名、约束名和索引名均使用完整、无歧义的英文 snake_case,不使用缩写。表名和列名分别在其作用域内唯一。
102
+ 每张表的 `systemColumns` 必须与固定结构完全一致:
84
103
 
85
- 表名、列名及命名对象格式为 `^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$`。名称必须稳定,实现层不得再次重命名。
104
+ - `uid`:唯一主键。Saddle 应用侧生成标准 UUIDv7,去掉中横线后保存为 32 位小写十六进制字符串;不增加严格单调等自定义扩展。
105
+ - `gmt_create`:`DATETIME(3)`,数据库使用 `DEFAULT CURRENT_TIMESTAMP(3)` 维护。
106
+ - `gmt_modified`:`DATETIME(3)`,数据库使用 `DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)` 维护。
107
+ - `is_deleted`:布尔值,数据库默认 `false`;Logic 删除由 Saddle 更新为 `true`,数据库随之更新 `gmt_modified`。
86
108
 
87
- ## 列来源
109
+ 表契约不规定数据库实例或会话时区;`DATETIME` 的环境时间语义属于运维配置。
88
110
 
89
- 每个业务列必须具有且仅具有一个直接 Structure 来源:
111
+ Saddle 数据访问层必须对所有普通读取强制施加 `is_deleted = false`。只有明确的恢复、审计或运维能力可以读取已删除记录。普通业务查询不得自行重复实现或绕过默认过滤。关联到已软删除记录时,业务上视为目标不存在。
112
+
113
+ ## 业务列与来源
114
+
115
+ `columns` 必须包含实体的全部 Structure 字段。每个业务列名原样复用 Structure 字段名,并具有且仅具有一个直接来源:
90
116
 
91
117
  ```yaml
92
118
  source: {entity: 用户账单, field: 账单状态}
93
119
  ```
94
120
 
95
- v1 不把派生值作为持久化列。若业务明确要求保存派生结果,但其刷新时机、一致性和来源依赖已经闭合,产出 `unsupported-shape` finding 以驱动后续格式扩展,不能先用自然语言列绕过。
121
+ 所有业务列固定 `nullable: true`,不允许 `default` `onUpdate`。Structure 的必填、枚举和值域仍是业务规则,由 Saddle 逻辑校验,不下沉为数据库约束。
122
+
123
+ 关联字段保留原始业务角色名,并存储目标实体的 `uid`:
96
124
 
97
- 不得默认增加 `id`、`created_at`、`updated_at`、`deleted`、`version`、`tenant_id` 等工程字段。只有它们已是 Structure 字段或存在已确认、可追溯的系统级持久化规范时才能加入;v1 暂不定义自由 `systemField`。
125
+ ```yaml
126
+ 卖家:
127
+ type: {kind: varchar, length: 32}
128
+ nullable: true
129
+ source: {entity: 订单, field: 卖家}
130
+ storesEntityUid: {entity: 用户}
131
+ 买家:
132
+ type: {kind: varchar, length: 32}
133
+ nullable: true
134
+ source: {entity: 订单, field: 买家}
135
+ storesEntityUid: {entity: 用户}
136
+ ```
98
137
 
99
- ## 物理类型
138
+ 同一目标实体被多次关联时,以 Structure 中不同的关联角色区分。不得改成含义不清的统一“用户”列,也不建立数据库外键。
100
139
 
101
- v1 面向 Saddle 当前 MySQL/MariaDB 数据源,允许以下封闭类型:
140
+ ## 基础 MySQL 类型
141
+
142
+ v1 只允许以下封闭类型:
102
143
 
103
144
  ```yaml
104
145
  {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}
146
+ {kind: bigint}
110
147
  {kind: decimal, precision: 18, scale: 2}
111
148
  {kind: boolean}
112
149
  {kind: date}
113
- {kind: datetime, precision: 6}
114
- {kind: binary, length: 32}
150
+ {kind: datetime, precision: 3}
115
151
  ```
116
152
 
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、浮点数、数据库 enumblobtimestamp、生成列及其他类型不在 v1 中,遇到时产出 `unsupported-shape` finding。
153
+ - `varchar.length` 必须是正整数,并有 Structure 值域、编码上界或已确认业务约束;不得任意统一为 255。
154
+ - `bigint` 固定生成 MySQL 64 位有符号整数。
155
+ - `decimal` 必须明确 precision、scale 和单位;精度与小数位来自 Structure/Rule
156
+ - `boolean` 固定生成 MySQL `TINYINT(1)`,但契约保留布尔业务语义。
157
+ - `date` 表示业务日期。
158
+ - `datetime` 必须明确 precision;系统时间列固定为 3。
159
+ - 枚举使用能够容纳全部合法值的 `varchar`;合法值由业务逻辑校验,不生成数据库 ENUM CHECK。
160
+ - JSON、TEXTBLOB、浮点数、TIMESTAMP、数据库 ENUM、生成列等不在 v1 中;确定需要时产出 `unsupported-shape` finding。
125
161
 
126
- API 类型不能单独决定 DB 类型;必须结合 Structure 值域、持久化语义和查询需求。
162
+ ## 数据库约束边界
127
163
 
128
- ## Nullable
164
+ `uid` 主键及四个系统列的固定规则外,v1 不设计其他数据库约束:
129
165
 
130
- `nullable` 表示持久化事实是否允许缺失,不表示页面暂时不展示、API 字段可选或 external 偶发未返回。
166
+ - 不使用复合主键或自增主键;
167
+ - 不建立业务唯一约束;
168
+ - 不建立数据库外键;
169
+ - 不建立普通索引;
170
+ - 不生成 CHECK、触发器或业务默认值。
131
171
 
132
- 必须从实体生命周期证明 nullable。若字段仅在某状态后产生,需要确认是允许 NULL、拆分实体,还是业务模型缺少状态事实;DB Skill 不自行选择。
172
+ 业务唯一性、关联一致性、必填和值域校验由 Saddle 业务逻辑实现。索引属于系统运行后的性能观测和运维优化,不在初始表结构设计阶段预判。
133
173
 
134
- ## 主键与唯一约束
174
+ ## Logic 事务访问责任
135
175
 
136
- `primaryKey.columns` 是非空列数组。主键优先使用 Structure 已定义的稳定实体身份,不能默认制造代理主键。
137
-
138
- 复合业务身份使用 `uniqueConstraints`:
176
+ `accessResponsibilities` 穷举当前表支持的 Logic 事务读写责任。事务身份必须通过 Islands Compiler 元数据唯一解析:
139
177
 
140
178
  ```yaml
141
- uniqueConstraints:
142
- - name: uq_user_bill_user_period
143
- columns: [user_id, bill_period]
144
- reason: 同一用户同一账期最多存在一张本系统账单。
145
- evidence:
146
- - {entity: 用户账单, fields: [用户ID, 账期]}
179
+ transaction:
180
+ paragraph: 查缴域·查账
181
+ branch: 主分支
182
+ transactionId: 查询并形成欠费单
147
183
  ```
148
184
 
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。
185
+ 事务可以来自人工 API、系统事务、定时任务、消息或回调。`apiId` 仅在该事务由已确认前端 API 承载时出现,并必须解析到对应 API 契约。
174
186
 
175
- `apiResponsibilities` 穷举当前表支持的 API 读写责任:
187
+ 每个 operation:
176
188
 
177
189
  - `kind` 只允许 `read / insert / update / delete`;
178
- - `columns` 是读取、插入或变更的列;
179
- - `predicateColumns` 是定位或过滤使用的列;
180
- - `apiId` 必须解析到已确认 API 契约。
190
+ - `columns` 精确列出读取、插入或变更的逻辑业务列;
191
+ - `predicateColumns` 精确列出定位、关联或筛选使用的逻辑业务列;
192
+ - 不记录 SQL、索引或物理列名;
193
+ - `delete` 表达 Logic 删除语义,运行时固定执行软删除,不执行物理 `DELETE`。
181
194
 
182
- 不要求每个 predicate 都单独建立索引;应按组合过滤、排序、唯一性和预期访问方式形成最小充分索引。没有 API 或 Logic 证据的预防性索引禁止加入。
195
+ 系统列生成、时间维护和默认软删除过滤是 Saddle 固定责任,不在每个 operation 中重复列出。
183
196
 
184
197
  ## 完成门禁
185
198
 
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`。
199
+ 1. 实体已证明为 Saddle 内部持久化实体,且不是 `external: true`;
200
+ 2. 文件名、`table.name` 和 Structure 实体身份原样一致;
201
+ 3. 表包含固定且完整的四个系统列;
202
+ 4. 全部 Structure 字段均出现,且每个业务列解析到唯一 Structure 字段;
203
+ 5. 关联字段明确存储目标实体 `uid`,多重关联角色无歧义;
204
+ 6. 类型参数完整,全部业务字段 nullable 且无默认值;
205
+ 7. 每个 Logic 事务身份可解析,字段访问责任完整;
206
+ 8. 不存在业务唯一约束、普通索引、外键、物理映射、TODO、占位符或诊断节点;
207
+ 9. 交付目录中不存在本 Skill 生成的 findings、DDL、迁移脚本或 `structure.sql`。
@@ -1,6 +1,8 @@
1
1
  # DB 设计 Findings v1
2
2
 
3
- 当输入不足或 v1 格式不支持某个确定需求时,在用户指定的交付目录写入 `db-design-findings.yaml`:
3
+ Islands Spec 或 v1 格式无法支持完整表契约时,报告 DB finding。Finding 是过程诊断,不是正式 DB 交付物,也不授权 Agent 创建临时字段、宽泛类型、冗余表或物理约束绕过问题。
4
+
5
+ DB 交付目录只放完整的 `*.table.yaml`。用户指定过程协作位置时,可在那里写入 `db-design-findings.yaml`;未指定时直接向用户反馈,不自行在交付目录或其他仓库落盘。
4
6
 
5
7
  ```yaml
6
8
  findingsVersion: saddle-db-findings/v1
@@ -13,15 +15,23 @@ findings:
13
15
  path: entity.persistenceReason
14
16
  evidence:
15
17
  - {kind: structureEntity, entity: 欠费单}
16
- - {kind: apiContract, apiId: query_bill}
17
- message: 欠费单被标记为外部共享的当次查询事实,当前没有本系统持久化生命周期证据。
18
- requiredUpstreamChange: 明确该事实由外部实时提供,或补齐由 Saddle 创建、更新和失效的生命周期。
18
+ - {kind: logicTransaction, paragraph: 查缴域·查账, branch: 主分支, transactionId: 查询并形成欠费单}
19
+ message: 当前只能确认欠费单来自一次外部查询,不能证明 Saddle 需要跨事务持久保存。
20
+ requiredUpstreamChange: 明确该事实只在请求内使用,或补齐由 Saddle 创建、读取、更新或删除的跨事务生命周期。
19
21
  ```
20
22
 
21
23
  必填字段为 `id / severity / category / affected / evidence / message / requiredUpstreamChange`。
22
24
 
23
25
  严重程度为 `blocker / warning`。
24
26
 
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`。
27
+ v1 分类:
28
+
29
+ - `persistence-ownership-unknown`
30
+ - `external-entity-local-table-conflict`
31
+ - `missing-field-source`
32
+ - `missing-type-bound`
33
+ - `association-target-ambiguous`
34
+ - `logic-responsibility-missing`
35
+ - `unsupported-shape`
26
36
 
27
- Finding 必须精确到实体、表或字段。一个实体的问题不能阻塞其他完整表文件。Finding 不进入 `*.table.yaml`,也不授权 Agent 创建临时字段、宽泛类型或冗余表来绕过问题。
37
+ Finding 必须精确到实体、字段或 Logic 事务。一个实体的问题不能阻塞其他完整表文件。Evidence 优先使用 Islands Compiler 的稳定身份,不得只使用源码行号。