@antprofuse/saddle-islands-design 0.1.13
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 +49 -0
- package/agents/openai.yaml +3 -0
- package/modules/backend-contract/assets/backend-contract.template.yaml +33 -0
- package/modules/backend-contract/design.md +60 -0
- package/modules/backend-contract/references/backend-contract-v1.md +391 -0
- package/modules/backend-contract/references/coverage.md +59 -0
- package/modules/backend-contract/references/findings.md +31 -0
- package/modules/db/assets/table.template.yaml +45 -0
- package/modules/db/design.md +45 -0
- package/modules/db/references/db-table-v1.md +253 -0
- package/modules/db/references/findings.md +37 -0
- package/package.json +11 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: saddle-islands-design
|
|
3
|
+
description: 从 Islands 业务 Spec 设计 Saddle 落地所需的契约。当前支持后端服务契约 backend-contract 与逻辑数据库表设计,可独立或联合执行并审查一致性。不修改业务 Spec,不实现业务代码、外部 RPC 或数据库部署。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Saddle Islands 设计
|
|
7
|
+
|
|
8
|
+
统一承载 Islands 到 Saddle 的设计规范,使用一个 Skill、一个发布版本;后端契约和 DB 是内部设计模块,不再是独立 Skill。未来经用户确认的设计能力也在此扩展,不因为名称宽泛就自动新增设计职责。
|
|
9
|
+
|
|
10
|
+
Spec 是业务输入,设计契约是输出。Islands API 表达逻辑业务入口;backend-contract 表达后端向调用方提供的服务契约,两者不混称,也不预设一对一。profusecontract 是出口角色,不是后端契约的别名。
|
|
11
|
+
|
|
12
|
+
## 选择模块
|
|
13
|
+
|
|
14
|
+
按用户指定的设计任务选择模块;上下文已明确时不重复询问。只要求某模块时不捆绑另一模块;仅说“设计”且范围不明确时,先确认本次交付范围。
|
|
15
|
+
|
|
16
|
+
| 任务 | 执行前完整阅读 | 独立交付 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| 后端契约 | [设计流程](modules/backend-contract/design.md)、[格式规范](modules/backend-contract/references/backend-contract-v1.md)、[覆盖规则](modules/backend-contract/references/coverage.md) | 指定后端契约目录的 `*.backend-contract.yaml` |
|
|
19
|
+
| DB | [设计流程](modules/db/design.md)、[表契约规范](modules/db/references/db-table-v1.md) | 指定 DB 目录的 `*.table.yaml` |
|
|
20
|
+
| 联合设计 | 上述两组内容 | 两类文件独立交付,并做下述一致性检查 |
|
|
21
|
+
|
|
22
|
+
有问题时阅读对应模块的 findings 参考;不加载与本次任务无关的模块。模板位于各模块 assets,沿模块说明使用。模块不是独立安装包,不要求用户再安装旧 Skill。
|
|
23
|
+
|
|
24
|
+
## 共用输入与协作
|
|
25
|
+
|
|
26
|
+
- Spec 仓库与 Spec 目录分别指定,目录相对该仓库根目录。记录实际分支及 commit;未指定版本时使用远程默认分支,不猜 master/main。默认覆盖指定目录业务,用户明确的子范围优先。
|
|
27
|
+
- 交付仓库与各模块交付目录分别明确,可使用不同仓库;不从 Spec 地址擅自推导。目录建议分别为 `code/backend-contract`、`code/db`,实际位置以用户确认为准。只做 DB 不要求业务前缀;后端契约需要调用方提供完整业务前缀。
|
|
28
|
+
- 核对实际安装包名称与版本;完整读取所选模块说明和必需参考,不静默更换版本。无需用户提供包内入口路径。
|
|
29
|
+
- Islands 默认参考仓库为 `https://code.alipay.com/profuse/islands`。优先遵守项目版本约束,无约束时读取远程默认分支并记录 commit;调用方可以覆盖。版本不兼容、不可访问不等于业务缺失。
|
|
30
|
+
- 通读所选范围相关业务表达及 Islands 指南,按模块取得受支持的编译元数据并核对来源。不因缺少预生成文件而禁止业务推演,但不得把未执行的解析、校验或测试称为通过。
|
|
31
|
+
- 默认先粗后细,确认边界后展开完整交付;已授权直接设计则按授权执行。每次聚焦一个问题,不以推测填补业务事实或设计决策。
|
|
32
|
+
- 业务 Spec 只读。局部问题及时反馈并说明依据、影响及是否阻塞,继续不受影响部分;只有 Spec 业务缺失或矛盾才记录为 Spec finding。规范能力不足反馈维护者,不自行绕过。只有明确授权维护 Skill 时可修改通用规范。
|
|
33
|
+
- Findings 只进入用户指定过程位置,未指定时直接反馈,不混入正式交付目录。默认提交推送前展示 diff tree 并取得确认;已有授权按其范围执行。不自动发布 Skill 或向其他 workspace 分配任务。
|
|
34
|
+
|
|
35
|
+
## 联合设计的一致性
|
|
36
|
+
|
|
37
|
+
后端契约的范围与完备性由指定 Prototype + 测试用例决定,实体与规则用于解释来源;DB 则按本系统实体的实际持久化责任设计,也覆盖系统任务等非客户端入口。不能用其中一个模块的范围替代另一个。
|
|
38
|
+
|
|
39
|
+
共同核对实体身份、归属、字段语义、金额单位、关联和 Logic 读写责任。后端契约是投影,不要求与表字段一一对应;不得从返回字段反向建表,也不得为 external 实体制造本地表。DB 可选 `backendContractId` 指向实际后端契约 id,不代替 Logic 身份。
|
|
40
|
+
|
|
41
|
+
逻辑实体身份、数据库 uid 与外部识别条件不是同一个概念;本次合并不新增身份编码或入口、出口映射规则。已有格式未覆盖的适配需求先反馈讨论,不让业务 Spec 改成外部函数调用伪代码,不宣称实现已有解析能力。
|
|
42
|
+
|
|
43
|
+
允许按模块或已明确部分分阶段交付,不要求一项全部完成才开始另一项。两类设计完成不等于实现测试或生产链路完成;测试研发与业务代码实现仍是后续工作。
|
|
44
|
+
|
|
45
|
+
## 维护边界
|
|
46
|
+
|
|
47
|
+
当前仅有上述两个模块。新增模块须先与用户确认设计职责、输入、交付及模块间关系,再更新本 Skill;不另起独立设计 Skill,也不预建占位模块。版本默认只递增 `0.1.x` 的 patch。
|
|
48
|
+
|
|
49
|
+
统一包不自动迁移已发布旧包、项目文件或消费者。现有模块格式保持;涉及文件发现、格式标识和关联键的历史更名仍需按模块规范单独适配。保留用户未批准草稿,不将其混入发布产物。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
contractVersion: backend-contract/v1
|
|
2
|
+
id: replace_with_contract_id
|
|
3
|
+
binding:
|
|
4
|
+
type: mobilegw
|
|
5
|
+
operationType: alipay.profuse.industry.replace.replaceOperation
|
|
6
|
+
callSites:
|
|
7
|
+
- kind: visualPageLoad
|
|
8
|
+
page: 替换为页面
|
|
9
|
+
inputBindings: {}
|
|
10
|
+
input:
|
|
11
|
+
type: object
|
|
12
|
+
nullable: false
|
|
13
|
+
fields:
|
|
14
|
+
示例输入:
|
|
15
|
+
type: string
|
|
16
|
+
nullable: false
|
|
17
|
+
source: {entity: 替换为实体, field: 替换为字段}
|
|
18
|
+
# output 只定义成功响应的 data;失败固定为 success:false + failure.code/message。
|
|
19
|
+
# 来源也支持正式 Flow Form / Rule 返回合同,见参考;不可从样例猜测枚举。
|
|
20
|
+
output:
|
|
21
|
+
type: object
|
|
22
|
+
nullable: false
|
|
23
|
+
fields:
|
|
24
|
+
示例字段:
|
|
25
|
+
type: string
|
|
26
|
+
nullable: false
|
|
27
|
+
source: {entity: 替换为实体, field: 替换为字段}
|
|
28
|
+
errors:
|
|
29
|
+
- code: EXAMPLE_ERROR
|
|
30
|
+
description: 替换为封闭的可观察错误语义。
|
|
31
|
+
types: {}
|
|
32
|
+
# 必须逐项替换为真实 Prototype/测试关联,禁止空数组交付;见 references/coverage.md。
|
|
33
|
+
coverage: []
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
# Saddle 后端契约设计
|
|
3
|
+
|
|
4
|
+
## 命名与层次
|
|
5
|
+
|
|
6
|
+
本模块 的产物统一称为后端契约(backend-contract):后端向调用方提供的服务契约。Islands API 是业务 Spec 的逻辑入口声明,属于输入侧概念;不得将两者混称,也不预设一对一。`callSites.api` 专指 Islands API 来源,保留该字段,不改写业务 Spec。出口 profusecontract 不属于本模块。
|
|
7
|
+
|
|
8
|
+
本次只统一命名,不增加实体身份编码或逻辑到物理映射规则;有关规则尚待讨论,不因更名自行推导。
|
|
9
|
+
|
|
10
|
+
从 Islands Spec 产出完整、自包含的后端服务契约。后端契约是小程序与 Saddle 后端之间的边界;数据库访问和外部 RPC 属于后续的后端实现设计。
|
|
11
|
+
|
|
12
|
+
**设计范围与完备性的基准只有调用方指定范围内的 Prototype + 测试用例。** Prototype 决定交互、展示与通信需求,测试用例决定场景、预期结果和副作用;完整支撑两者即可认定当前范围的后端契约设计完备。其他 Spec 仅用于解释业务含义、追溯实体字段和规则,不据此扩展接口、字段或场景。两者冲突、缺失或不可定位时反馈,不自行补造,也不把缺失当作空集合宣称完成。
|
|
13
|
+
|
|
14
|
+
字段基于实体的业务概念表达,逐字段遵守来源规则;已有 Form/Rule 来源只用于有依据的临时或派生事实,不授权另造业务实体或整包透传技术答复。每份后端契约必须在文件内通过 `coverage` 与 Prototype、测试用例建立可核验的强关联,不能仅声称“已阅读”或只挂链接。设计覆盖不等于实现测试已经运行通过。
|
|
15
|
+
|
|
16
|
+
设计契约前必须阅读 [references/backend-contract-v1.md](references/backend-contract-v1.md) 和 [references/coverage.md](references/coverage.md)。当来源关联、类型、调用点或业务结果不能唯一确定时,阅读 [references/findings.md](references/findings.md)。
|
|
17
|
+
|
|
18
|
+
## 模块输入
|
|
19
|
+
|
|
20
|
+
按统一入口核对 Spec、交付目录及 Islands 版本。后端契约设计额外需要完整业务前缀;operationType 为 `<完整业务前缀>.<动作的camelCase>`,不重复添加前缀,不根据目录名改写。已有 operationType 与任务冲突时反馈,不静默重命名。契约 id 仍按参考使用 snake_case。
|
|
21
|
+
|
|
22
|
+
DB 设计、profusecontract 与外部 RPC 协议不是本模块的前置条件;external 实体仍需按业务来源阅读。先对齐服务边界及输入输出概览,再展开字段;用户已授权完整设计时直接执行。
|
|
23
|
+
|
|
24
|
+
## 工作流程
|
|
25
|
+
|
|
26
|
+
1. 先完整列出指定范围的 Prototype 交互/展示及测试用例责任,形成覆盖分母,再按需追溯 Visual、Logic、Structure、Rule 和编译元数据解释含义。不以所有 Spec 内容为接口清单,不要求 Spec 预先声明 API 或 external 调用契约。
|
|
27
|
+
2. 从 Visual 的页面加载、读取和动作出发,再追踪其 Logic 事务与数据引用。只有业务能力、输入含义、结果含义和副作用语义完全一致时,才把多个调用点归入同一个后端契约。页面加载所需的多项关联读取可以形成一个页面读模型;不要按 ER 读取数量机械拆分后端契约。
|
|
28
|
+
3. 使用 v1 格式定义完整后端契约。先确定可直接作为 Rust module 的全局唯一后端契约 ID,再精确绑定每个调用点,并在每个调用点绑定全部输入。
|
|
29
|
+
4. 每个输入和输出业务叶字段必须具有可解析的 Structure、Flow Form 或 Rule 来源,或一组结构化依赖加自然语言派生逻辑。Form 必须追溯正式端口及其赋值链,Rule 必须追溯正式返回合同;不能用样例推断枚举。输入字段的 `source` 表达字段的业务语义,调用点 `inputBindings` 表达本次值从何处取得;两者不可互相替代。绝不创造无根字段。
|
|
30
|
+
5. MobileGW 系统内置提供的值不属于客户端 input,也不属于业务后端契约的定义内容。绝不要求客户端重复提交系统已提供的当前用户 ID 等值。
|
|
31
|
+
6. 只向正式后端契约目录输出完整契约。Spec 中未闭合但被明确排除在现有业务结果之外的状态,可以由一个语义精确的 fail-closed error 暂时承接,并同时记录 Spec finding;不得猜测或细分尚未定义的业务结果。Skill 表达能力不足由规范维护者修复,不记录为项目 Spec finding,也不由消费方另造规则绕过。
|
|
32
|
+
7. 按 coverage 规则双向核对:每项 Prototype 与测试预期均被支撑,每个后端契约调用点、业务结果与所需字段均有依据。逐项推演动作输入、输出断言、副作用及后续交互,记录无需后端契约的纯前端责任,不以样例数量或链接存在代替覆盖证明。名称、类型、枚举、nullable 及来源仍必须封闭明确。
|
|
33
|
+
|
|
34
|
+
## 产出
|
|
35
|
+
|
|
36
|
+
- 每个完整后端契约产出一个 `<contract_id>.backend-contract.yaml`;文件名 stem 必须与契约 `id` 原样一致。
|
|
37
|
+
- 存在 Spec 问题时,写入调用方指定的过程记录位置;未指定时只在交付说明中报告。`backend-contract-design-findings.yaml` 绝不能进入正式 `code/backend-contract` 目录。
|
|
38
|
+
- 不把散文式设计文档作为交付物。
|
|
39
|
+
|
|
40
|
+
以 [assets/backend-contract.template.yaml](assets/backend-contract.template.yaml) 为起始结构。删除示例内容;交付的 `*.backend-contract.yaml` 中绝不能残留 TODO、占位符、阻塞标记或诊断节点。
|
|
41
|
+
|
|
42
|
+
## 边界
|
|
43
|
+
|
|
44
|
+
- 通用响应固定为成功 `{success: true, data: <output>}` 或失败 `{success: false, failure: {code, message}}`。output 只定义成功 data,不重复包装;不使用旧 value/errorCode 信封。failure.code 取本接口声明的错误码,failure.message 是字符串说明,不是程序判别字段。
|
|
45
|
+
- Spec 拥有业务真相;后端契约可以基于它做面向前后端的分层设计,包括实体投影、读取归并、实体引用和结果联合。技术表达不必逐字照搬 Spec,也不要求 Spec 声明物理字段或传输编码;不得改变业务条件、授权、副作用或缺失事实的含义。
|
|
46
|
+
- 页面入参不会自动成为后端契约 input;外部 RPC 参数也不会自动成为后端契约 input。
|
|
47
|
+
- `visualPageLoad` 表达页面打开时对一个后端读模型的调用。它不列举内部 ER 读取,不规定查询顺序,也不阻止后端依据已取得的事实短路执行。
|
|
48
|
+
- `binding` 只选择平台已有的协议类型和业务 operationType,不增加任何协议扩展字段。
|
|
49
|
+
- 后端契约 ID 是实现切片的唯一机器身份,后端契约文件、Backend operation 目录和 Rust module 必须原样复用,任何层级都不得二次命名或格式转换。
|
|
50
|
+
- 后端契约 input 类型必须显式声明,并与调用点绑定值的业务语义一致。若 Visual 的宽泛标量声明与 Structure 的封闭类型冲突,只有 Visual 的明确业务约束与 Structure 完全一致时,才可采用 Structure 类型继续产出并记录 Spec finding;否则不得自行收窄。
|
|
51
|
+
- `discriminatedUnion` 只是在后端契约中描述互斥 JSON 形状的程序结构,不是业务建模。判别字段必须来自 Structure 的已有封闭枚举;case key 直接复用枚举值,不得新增“结果类型”等无来源业务字段。
|
|
52
|
+
- 跨实体事实形成的结果可使用 `flowResult`,线上固定为 `case + data`。case 是契约分支标识,不是 Structure 字段,也不是页面路由指令;不能要求 Spec 为它新增业务枚举。保留上述单字段 `discriminatedUnion`,两者不混用。
|
|
53
|
+
- 正式实体引用使用 `entityReference`,目标实体由契约声明,线上为不透明字符串,前端原样回传,后端解析并独立校验权限。不假定引用等于数据库 uid,不因 external 实体未声明 ID 属性而认定业务模型缺少身份。
|
|
54
|
+
- 新版 Business Flow Graph 使用 `visualFlow` 调用点,关联真实页面组件及板块/节点,不虚构旧 Paragraph 身份。元数据唯一解析是验证要求,不代表现有消费者已经支持新格式。
|
|
55
|
+
- 公开业务 Entry 可以使用 `flowEntry` 作为调用来源;没有本域页面时不虚构页面。终止 Logic 的公开交付也可以形成结果 case,不强制补造 Visual。具体规则见契约参考;不能因此将任意后台事件或外部 RPC 自动暴露成后端契约。
|
|
56
|
+
- 除非 Visual 明确将其定义为用户可观察的业务信息,否则不得暴露路由、服务商、链路追踪、物理地址、适配器或审计事实。
|
|
57
|
+
- 一段不完整流程只阻塞受影响的契约,不阻塞项目其余部分。
|
|
58
|
+
- Spec 是业务输入,后端契约和本模块 是设计输出。不得让 Spec 为满足传输格式而新增实体或接口声明;只有业务含义确实缺失或冲突才反馈 Spec 问题。
|
|
59
|
+
- 审查投影时覆盖后续纯前端导航和重试。实体引用不是完整实体数据,页面需要的名称、关联和业务值必须有可取得的投影;重试应使用流程实际交付的当前上下文,不能默认入口上下文在写入后仍有效。
|
|
60
|
+
- 页面呈现的跨域办理意图不等于本域存在可调用流程。没有真实办理合同和调用点时,不复制查询结果构造确认接口。
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Saddle 后端契约 v1
|
|
2
|
+
|
|
3
|
+
## 契约边界
|
|
4
|
+
|
|
5
|
+
每个 `*.backend-contract.yaml` 都是一个由小程序与 Saddle 后端共同遵守的独立契约。契约不包含数据库表结构、SQL、外部 RPC 调用顺序、代码布局、框架配置或代码生成设置。
|
|
6
|
+
|
|
7
|
+
使用受限 YAML:UTF-8 编码、与 JSON 兼容的数据值;禁止 alias、anchor、tag、merge key 和重复 key。
|
|
8
|
+
|
|
9
|
+
## 必需的顶层结构
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
contractVersion: backend-contract/v1
|
|
13
|
+
id: query_bill
|
|
14
|
+
binding:
|
|
15
|
+
type: mobilegw
|
|
16
|
+
operationType: alipay.profuse.industry.bill.queryBill
|
|
17
|
+
callSites: []
|
|
18
|
+
input: {type: object, nullable: false, fields: {}}
|
|
19
|
+
output: {type: object, nullable: false, fields: {}}
|
|
20
|
+
errors: []
|
|
21
|
+
types: {}
|
|
22
|
+
coverage: []
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
九个顶层字段全部必填,禁止未知顶层字段。`operationType` 全局唯一,使用完整、无歧义的命名。`coverage` 的形状与全量审查规则见 [coverage.md](coverage.md),正式交付不得为空。
|
|
26
|
+
|
|
27
|
+
coverage 是必填设计元数据;旧八字段契约不能宣称符合新版设计门禁。严格只接受八字段的消费者需适配;现有项目应单独 review 后补关联,不捏造旧测试证据。
|
|
28
|
+
|
|
29
|
+
0.1.12 统一更名:格式标识为 `backend-contract/v1`,文件后缀为 `.backend-contract.yaml`。线上请求、响应、operationType 和契约 id 的语义不变,但文件发现规则与格式标识校验必须同步迁移;不得宣称旧消费者自动兼容。旧发布版本及历史交付不自动重写,项目迁移须单独获得授权。`callSites.api` 仍引用 Islands 逻辑 API,不属于本层名称。
|
|
30
|
+
|
|
31
|
+
## 后端契约 ID
|
|
32
|
+
|
|
33
|
+
后端契约 ID 是实现切片稳定、全局唯一、可直接落盘的机器身份。它必须原样用于:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
契约 id query_bill
|
|
37
|
+
后端契约文件名 query_bill.backend-contract.yaml
|
|
38
|
+
Backend operation 目录 query_bill/
|
|
39
|
+
Rust module 名 query_bill
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
任何消费方都不得再建立 kebab-case、camelCase、缩写或别名映射,也不得把后端契约 ID 重新翻译为实现名称。文件名去掉固定后缀 `.backend-contract.yaml` 后必须严格等于 `id`;operation 目录 basename 与 Rust module 标识符也必须严格等于 `id`。
|
|
43
|
+
|
|
44
|
+
后端契约 ID 必须同时满足:
|
|
45
|
+
|
|
46
|
+
- 格式为 `^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$`;
|
|
47
|
+
- 使用完整、无歧义的英文业务动作名称,禁止缩写;
|
|
48
|
+
- 在全部后端契约中全局唯一;
|
|
49
|
+
- 不是 Rust 当前关键字或保留关键字;
|
|
50
|
+
- 不使用 `r#` 原始标识符逃逸;
|
|
51
|
+
- 一经发布即保持稳定,业务含义变化时不能复用旧 ID 承载另一能力。
|
|
52
|
+
|
|
53
|
+
禁止使用的 Rust 关键字及保留字至少包括:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
as async await break const continue crate dyn else enum extern false fn for
|
|
57
|
+
if impl in let loop match mod move mut pub ref return self Self static struct
|
|
58
|
+
super trait true try type union unsafe use where while abstract become box do
|
|
59
|
+
final macro override priv typeof unsized virtual yield
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
设计门禁必须同时检查契约 `id` 唯一性、文件名一致性和 Rust 标识符合法性。Backend 生成阶段只消费该 ID,不拥有命名权。
|
|
63
|
+
|
|
64
|
+
`binding` 只声明当前后端契约采用系统已有的 MobileGW 协议,并分配唯一 `operationType`,不允许增加其他字段。
|
|
65
|
+
|
|
66
|
+
## 调用点
|
|
67
|
+
|
|
68
|
+
Visual 读取:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- kind: visualRead
|
|
72
|
+
page: 收款概览
|
|
73
|
+
read: $工作台数据
|
|
74
|
+
inputBindings:
|
|
75
|
+
账单月份: {pageInput: $账单月份}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
页面加载读模型:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- kind: visualPageLoad
|
|
82
|
+
page: 欠费单
|
|
83
|
+
inputBindings:
|
|
84
|
+
机构名称: {pageInput: $机构名称}
|
|
85
|
+
费种: {pageInput: $费种}
|
|
86
|
+
户号: {pageInput: $户号}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`visualPageLoad` 适用于页面打开时需要一次后端调用取得页面读模型,尤其是页面包含多个有依赖或可短路的 ER 读取时。调用点只绑定页面和业务输入:
|
|
90
|
+
|
|
91
|
+
- 不把 `读取`列表、数据库查询顺序或短路流程写进后端契约;
|
|
92
|
+
- 不因页面声明多项读取而机械生成多个后端契约;
|
|
93
|
+
- 输出仍须逐字段遵守下述来源规则,并由 Visual 的实际消费证明必要性;
|
|
94
|
+
- 页面中彼此独立、刷新周期不同或业务语义不同的读取不得强行聚合。
|
|
95
|
+
|
|
96
|
+
Visual 事务动作:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
- kind: visualTransaction
|
|
100
|
+
page: 欠费单查询
|
|
101
|
+
pageBranch: 查询失败
|
|
102
|
+
component: 重新查询
|
|
103
|
+
transaction:
|
|
104
|
+
paragraph: 查缴域·查账
|
|
105
|
+
branch: 主分支
|
|
106
|
+
transactionId: 查询并形成欠费单
|
|
107
|
+
inputBindings:
|
|
108
|
+
临时查账资料: {actionInput: "&临时查账资料"}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- 每个身份都必须通过 Compiler 元数据唯一解析。
|
|
112
|
+
- 只有组件不属于任何页面分支时,才能省略 `pageBranch`。
|
|
113
|
+
- 每个调用点都必须绑定全部非 nullable 输入;嵌套绑定的结构与 input schema 一致。
|
|
114
|
+
- v1 支持的来源为 `pageInput`、`pageRead`、`actionInput` 和下述 `collectionItem`。格式本身缺少来源表达能力时修复 Skill,不登记为 Spec finding。
|
|
115
|
+
- 绝不持久化源码数组下标或不稳定的生成 key。
|
|
116
|
+
|
|
117
|
+
新版 Business Flow 动作使用 `visualFlow`:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
- kind: visualFlow
|
|
121
|
+
page: 查询户号
|
|
122
|
+
component: 页面主体/户号区/户号卡列表/户号卡
|
|
123
|
+
flow: {board: 查询欠费单, node: 输入查询信息}
|
|
124
|
+
inputBindings:
|
|
125
|
+
机构: {collectionItem: $item.户号.机构}
|
|
126
|
+
费种: {collectionItem: $item.户号.费种}
|
|
127
|
+
户号: {collectionItem: $item.户号.户号}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`component` 为页面组件的稳定语义路径;经过分支时包含分支名称。`flow` 必须解析到该组件实际流程动作的板块和节点。`collectionItem` 是该组件所在集合项的表达式,必须验证集合来源和嵌套作用域;不把数组下标当身份。`pageInput/pageRead/actionInput` 仍按其原有含义使用。
|
|
131
|
+
|
|
132
|
+
Visual Form 的宽泛类型不自动构成业务类型冲突:应追溯真实赋值来源。若完整赋值链均来自同一 Structure 封闭枚举且无转换,可以在后端契约保留该枚举;不能仅依据同名字段猜测值域。
|
|
133
|
+
|
|
134
|
+
公开业务入口已有明确上游交互来源、且属于已确认前后端调用范围时,可以使用 `flowEntry`:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- kind: flowEntry
|
|
138
|
+
flow: {board: 查询欠费单, entry: 上游已选定户号}
|
|
139
|
+
api: 提交已选定户号查账
|
|
140
|
+
inputBindings:
|
|
141
|
+
已选定户号: {eventInput: 已选定户号}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
entry 必须解析到真实板块入口,api 必须是该入口绑定的 Islands 逻辑 API,eventInput 必须是其真实参数;系统身份仍不进入客户端 input。这里的 api 是业务输入边界,不是外部 SOFA RPC。不能为了兼容旧格式虚构首页、页面加载或按钮,也不能将任意后台事件机械转换为 MobileGW 后端契约。
|
|
145
|
+
|
|
146
|
+
## Schema 节点
|
|
147
|
+
|
|
148
|
+
每个节点都必须包含 `type` 和 `nullable`。
|
|
149
|
+
|
|
150
|
+
标量类型为 `string / integer / decimal / boolean / date / datetime`:
|
|
151
|
+
|
|
152
|
+
- `date`:RFC 3339 完整日期 `YYYY-MM-DD`。
|
|
153
|
+
- `datetime`:带明确时区偏移的 RFC 3339 时间戳。
|
|
154
|
+
- `integer`:位于约定运行时安全范围内的 JSON 整数。
|
|
155
|
+
- `decimal`:使用精确的十进制 JSON 字符串,绝不使用二进制浮点数。业务正确性依赖精度和舍入时,来源语义必须定义二者。
|
|
156
|
+
- `string`:不隐含格式。业务编码、月份等应有 Spec 业务依据;后端契约自有传输编码由契约规范定义,不要求业务 Spec 表达技术细节。
|
|
157
|
+
|
|
158
|
+
实体引用:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
type: entityReference
|
|
162
|
+
nullable: false
|
|
163
|
+
entity: 机构
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`entity` 必须解析到真实 Structure 实体(包括 external),本身构成身份来源,不另外虚构一个 ID 属性作为 source。线上为不透明字符串;客户端保存和原样回传,后端恢复对应实体身份并校验当前权限。引用不构成授权,不等于展示名称、样例 Row 别名或数据库 uid。具体编码和运行时解析不由后端契约规定;不得宣称尚未实现的解析能力已可用。`nullable: true` 时允许 null。
|
|
167
|
+
|
|
168
|
+
枚举:
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
type: enum
|
|
172
|
+
nullable: false
|
|
173
|
+
values: [查询成功, 无欠费]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
容器与文件内引用:
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
type: object
|
|
180
|
+
nullable: false
|
|
181
|
+
fields:
|
|
182
|
+
户号: {type: string, nullable: false}
|
|
183
|
+
|
|
184
|
+
type: array
|
|
185
|
+
nullable: false
|
|
186
|
+
items: {type: ref, nullable: false, ref: 账单项}
|
|
187
|
+
|
|
188
|
+
type: ref
|
|
189
|
+
nullable: false
|
|
190
|
+
ref: 账单项
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
判别联合:
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
type: discriminatedUnion
|
|
197
|
+
nullable: false
|
|
198
|
+
discriminator:
|
|
199
|
+
field: 机构状态
|
|
200
|
+
cases:
|
|
201
|
+
维护中:
|
|
202
|
+
type: object
|
|
203
|
+
nullable: false
|
|
204
|
+
fields:
|
|
205
|
+
机构状态:
|
|
206
|
+
type: enum
|
|
207
|
+
nullable: false
|
|
208
|
+
values: [维护中]
|
|
209
|
+
source: {entity: 机构, field: 机构状态}
|
|
210
|
+
机构名称:
|
|
211
|
+
type: string
|
|
212
|
+
nullable: false
|
|
213
|
+
source: {entity: 机构, field: 机构名称}
|
|
214
|
+
正常:
|
|
215
|
+
type: object
|
|
216
|
+
nullable: false
|
|
217
|
+
fields:
|
|
218
|
+
机构状态:
|
|
219
|
+
type: enum
|
|
220
|
+
nullable: false
|
|
221
|
+
values: [正常]
|
|
222
|
+
source: {entity: 机构, field: 机构状态}
|
|
223
|
+
业务字段:
|
|
224
|
+
type: string
|
|
225
|
+
nullable: false
|
|
226
|
+
source: {entity: 业务实体, field: 业务字段}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`discriminatedUnion` 的约束:
|
|
230
|
+
|
|
231
|
+
- `discriminator.field` 只支持当前联合对象的直接字段;
|
|
232
|
+
- 判别字段必须是具有直接 Structure 来源的非 nullable 枚举;
|
|
233
|
+
- 每个 case 必须包含同名判别字段,且 `values` 恰好为该 case key;
|
|
234
|
+
- 所有 case 的判别字段必须指向同一个 Structure 字段;
|
|
235
|
+
- case key 互不重复,并属于来源枚举的合法值;
|
|
236
|
+
- case 只是契约和代码生成使用的结构分支,不出现在 JSON 中;
|
|
237
|
+
- 不允许另加无 Structure 来源的“结果类型”字段充当判别器;
|
|
238
|
+
- 若只有部分枚举值能够形成当前后端契约的成功结果,其余值必须由 Spec 已定义的错误或明确的 fail-closed error 承接,不能静默遗漏。
|
|
239
|
+
|
|
240
|
+
`ref` 只能解析到当前文件的 `types`;后端契约之间绝不共享业务模型。
|
|
241
|
+
|
|
242
|
+
### 跨实体结果联合
|
|
243
|
+
|
|
244
|
+
当结果来自多个实体事实或流程分支,使用 `flowResult`,不为满足单字段判别联合而修改理想模型:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
type: flowResult
|
|
248
|
+
nullable: false
|
|
249
|
+
cases:
|
|
250
|
+
机构维护:
|
|
251
|
+
source: {board: 查询欠费单, node: 展示机构维护, page: 机构维护}
|
|
252
|
+
payload:
|
|
253
|
+
type: object
|
|
254
|
+
nullable: false
|
|
255
|
+
fields:
|
|
256
|
+
维护说明:
|
|
257
|
+
type: string
|
|
258
|
+
nullable: false
|
|
259
|
+
source: {entity: 查询服务维护, field: 维护说明}
|
|
260
|
+
invariants:
|
|
261
|
+
- 仅承接对应查询服务当前适用的维护事实。
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
线上为 `{case: "机构维护", data: {维护说明: "…"}}`。`case` 与 `data` 必填且无额外字段;不直接输出 source、payload 或 invariants 这些 schema 元数据。case key 是唯一且封闭的后端契约标签,不是 Structure 字段;不要求它有单个枚举字段来源。payload 是非 nullable 对象或指向该对象的文件内 ref;静态业务结果可为 `{}`。联合内每个对象仍禁止省略字段,nullable 不等于可省略。
|
|
265
|
+
|
|
266
|
+
每个 case 必须有可解析的 `source: {board, node, page}` 与非空 `invariants`,说明该结果的业务条件。来源不能只引用节点名称而忽略到达它的业务含义。payload 的业务叶字段仍逐一遵守来源规则。节点/页面不是线上跳转指令;前端拥有展示组织权。
|
|
267
|
+
|
|
268
|
+
若 Spec 明确由终止 Logic 向调用方公开交付事实,可改用 `source: {board, node}`:不得填写虚构 page;节点必须无后继且有明确的公开结果说明及真实产出。单纯无后继不自动构成后端契约结果。原有 Visual 来源保留 page。这样可表达交回上游,而不新增错误页或把所有内部 Logic 都暴露成结果。
|
|
269
|
+
|
|
270
|
+
从真实调用点追踪到下一次需要用户参与或展示的业务边界,核对所有可达结果及后续纯前端动作消费。不是每个节点都产生一个后端契约,也不规定每个 Visual 必须机械对应一种结果。归并必须保留业务差异并说明依据。
|
|
271
|
+
|
|
272
|
+
仅查询命中分支需要的数据,不为其他 case 填字段而继续调用外部系统。不得把尚未查询、部分响应或 null 当作零/无欠费。Spec 遗留问题留在过程记录,不以新增 case、禁用操作或错误码暗中补业务规则。
|
|
273
|
+
|
|
274
|
+
## Input
|
|
275
|
+
|
|
276
|
+
`input` 是完整、显式的对象。名称必须携带完整业务含义,例如 `当前户号绑定ID`,绝不使用依赖周边语义才能理解的缩写。
|
|
277
|
+
|
|
278
|
+
每个 input 业务叶字段也必须声明 `source`,或声明完整的 `dependencies` 与 `derivation`。这里的来源表示字段在理想业务模型中的语义身份,不表示客户端值的传输来源:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
费种:
|
|
282
|
+
type: enum
|
|
283
|
+
nullable: false
|
|
284
|
+
values: [电费, 水费, 燃气费]
|
|
285
|
+
source: {entity: 户号, field: 费种}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
来源定义在每个调用点的 `inputBindings` 中。Compiler 验证来源作用域和精确类型兼容性。不能仅因为 Logic、数据库或外部 RPC 需要某值,就增加客户端字段;后端应从系统内置能力、稳定标识、Structure、settings 和其他后端事实中取得这些值。系统内置提供的值不进入 `input` 或 `inputBindings`。
|
|
289
|
+
|
|
290
|
+
若 Visual 把字段声明为宽泛标量,但同一页面的业务说明明确给出与 Structure 完全相同的封闭值域,可以采用 Structure 类型产出,同时在过程记录中保留 `type-conflict` Spec finding。缺少这组一致证据时必须停止受影响字段,不能仅凭字段名推断或收窄。
|
|
291
|
+
|
|
292
|
+
## Output 来源
|
|
293
|
+
|
|
294
|
+
`output` 或 `types` 中的每个业务叶字段必须具有且仅具有一个直接来源:
|
|
295
|
+
|
|
296
|
+
```yaml
|
|
297
|
+
户号:
|
|
298
|
+
type: string
|
|
299
|
+
nullable: false
|
|
300
|
+
source: {entity: 欠费单, field: 户号}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
或者一个派生定义:
|
|
304
|
+
|
|
305
|
+
```yaml
|
|
306
|
+
管理房间数量:
|
|
307
|
+
type: integer
|
|
308
|
+
nullable: false
|
|
309
|
+
dependencies:
|
|
310
|
+
- {entity: 转供收款范围, field: 房间名称}
|
|
311
|
+
- {entity: 转供收款范围, field: 用户ID}
|
|
312
|
+
- {entity: 转供收款范围, field: 范围状态}
|
|
313
|
+
derivation: 当前房东正常收款范围内的房间名称去重数量。
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`source` 表示精确的语义同一性;field 可使用点分路径定位实体内嵌结构的属性,每一段必须真实存在。`dependencies` 必须完整且非空;`derivation` 描述不含实现细节的确定性业务投影。容器节点不要求来源,但其所有业务叶字段都必须有来源;entityReference 以目标实体作为身份来源,flowResult 的技术标签以 case 来源及条件作为依据。
|
|
317
|
+
|
|
318
|
+
## Flow Form 与 Rule 来源(0.1.9)
|
|
319
|
+
|
|
320
|
+
上文的 Structure 来源保持有效;`source` 及 `dependencies` 元素还支持以下两种封闭形状:
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
# 精确关联节点公开端口的字段,不是 UI 文案或样例别名。
|
|
324
|
+
source:
|
|
325
|
+
kind: flowForm
|
|
326
|
+
board: 查询欠费单
|
|
327
|
+
node: 展示查询暂不可用
|
|
328
|
+
direction: input
|
|
329
|
+
port: 准入答复
|
|
330
|
+
field: 原因
|
|
331
|
+
|
|
332
|
+
# 精确关联正式 Rule 返回合同;对象返回值可用 field 指定真实字段路径。
|
|
333
|
+
source:
|
|
334
|
+
kind: ruleResult
|
|
335
|
+
rule: 生活缴费·查账准入结论
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`flowForm` 必需且仅允许 `kind/board/node/direction/port/field`。direction 为 `input` 或 `output`,分别解析节点的入参或产出;port 是正式名称,field 是 Form 内逐段存在的字段路径。输出端口若存在多个分支声明,须全部兼容且追踪其生产分支,不能任选一个样例。不存在公开端口的内部过程值不能用本形状冒充公开输出。
|
|
339
|
+
|
|
340
|
+
`ruleResult` 必需 `kind/rule`,只额外允许 `field`;不带 field 表示整个标量返回值,带 field 表示已声明对象返回合同的真实路径。必须验证参数绑定及调用作用域,不能从规则名称猜测结果含义。Rule 依赖与 Form 字段可用于派生定义,但单有 derivation 文本而无 dependencies 仍不合法。
|
|
341
|
+
|
|
342
|
+
这些关联只是来源证据,不授权暴露整个 Form、系统用户身份、服务商或内部配置。临时对象不因此成为实体或数据库表,Spec 不必新增实体适配后端契约。
|
|
343
|
+
|
|
344
|
+
若上游声明为字符串,后端契约只能在**所有可达生产路径**的正式赋值、Rule 分支或守卫共同证明封闭值域时收窄为 enum;在对应 case 的 invariants 中说明限制依据。不得把 few-shot 中出现过的值当作穷尽列表。若只是部分路径可证明,应按已证明的业务结果设计 flowResult,不把原始宽泛字符串强制当作全局枚举。单字段 discriminatedUnion 的 Structure 限制不变。
|
|
345
|
+
|
|
346
|
+
同一个 Visual 可以对应多个 flowResult case。覆盖检查按可达业务结果及后续组件条件核对,不能要求 case 数等于 Visual 节点数;也不能仅检查节点名称存在就认为契约正确。每个 case 的 invariants 必须表达互斥条件以及跨字段约束,例如特定原因下时间对必非空、其它原因下均为 null。仅标 nullable 不代表允许所有字段组合。
|
|
347
|
+
|
|
348
|
+
投影可以用已经证明的 case 代替临时答复的整体传输。必须证明页面及后续纯前端动作所需事实都能从结果取得,不能机械照搬所有页面内部入参,也不能用实体引用代替未提供的展示属性。重试调用点与写入后实际绑定同样属于契约完备性检查。
|
|
349
|
+
|
|
350
|
+
以上为 v1 来源能力的增量扩展,旧来源仍有效;消费方需支持新来源元数据。规范升级不代表现有生成器、运行时或校验器已经兼容。
|
|
351
|
+
|
|
352
|
+
## Errors 与响应信封(线上结构)
|
|
353
|
+
|
|
354
|
+
`errors` 是 `{code, description}` 的封闭列表。code 使用 upper snake case;description 表达可观察语义,而不是堆栈信息或外部物理错误码。
|
|
355
|
+
|
|
356
|
+
线上响应使用统一的封闭联合,以 success 区分两种形状:
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{"success": true, "data": {"示例业务字段": "示例值"}}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{"success": false, "failure": {"code": "TECHNICAL_FAILURE", "message": "暂时无法取得结果"}}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
- 成功形状只有 `success: true` 和 `data`,两者必填;data 精确满足本接口 output schema,不带 failure。
|
|
367
|
+
- 失败形状只有 `success: false` 和非空对象 `failure`;failure 内只有必填、非 nullable 字符串 code、message,不带 data。code 必须属于本接口 errors 声明。
|
|
368
|
+
- 不输出旧通用字段 value 或 errorCode,不额外输出顶层 code/message;不把 success 定义为业务 output 的字段。
|
|
369
|
+
- errors 中的 description 是错误码的契约含义;failure.message 是运行时响应的说明文本,含义应与对应错误码一致,不要求逐字等于 description,也不自动将 description 当作固定页面文案。程序根据 success 和 failure.code 判别,不依赖 message 文本;不泄漏内部堆栈或凭据。
|
|
370
|
+
- 不在每个后端契约的 output 重复定义通用信封,也不要求业务 Spec 增加这些系统字段。成功 data 的 nullable 由 output 控制,失败 failure/code/message 本身不可为 null。
|
|
371
|
+
|
|
372
|
+
当 output 使用 flowResult 时,外层 data 承载整个业务结果;内层 data 是已确认的分支 payload,两个层级不能混淆或擅自压平:
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
{"success": true, "data": {"case": "机构维护", "data": {"维护说明": "系统维护中"}}}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
消费方必须统一运行时信封;仅升级设计文档不代表已有实现已迁移。迁移不得改变业务 output、错误码意义或具体后端契约的结果分支。
|
|
379
|
+
|
|
380
|
+
当 Spec 明确表示某些状态不得映射为已定义业务结果、但尚未为其定义精确结果时,可以增加一个 fail-closed error 继续交付已明确部分。该 error 必须:
|
|
381
|
+
|
|
382
|
+
- 只说明“未形成当前契约已定义的结果”,不替 Spec 分类或解释具体原因;
|
|
383
|
+
- 不把多个未知状态伪装成一个新业务结果;
|
|
384
|
+
- 与对应 Spec finding 同时存在,以便 Spec 补齐后继续细化;
|
|
385
|
+
- 不得用于掩盖字段来源、类型、调用点或已定义结果本身的歧义。
|
|
386
|
+
|
|
387
|
+
## 完成门禁
|
|
388
|
+
|
|
389
|
+
首先必须完整支撑指定范围的 Prototype 与测试用例,且按 coverage 参考逐项关联、无漏项或伪造覆盖;其他 Spec 的未关联内容不能用于扩展接口范围。覆盖记录与后端契约同版本维护,不包含测试执行状态或运行日志。测试缺口或双方冲突应报告,不能宣布整体设计完备。
|
|
390
|
+
|
|
391
|
+
只有满足以下条件,契约才可交付:后端契约 ID 全局唯一、符合 Rust module 标识符规则且与文件名原样一致;全部位置可解析;每个调用点绑定全部必填输入;类型、枚举、nullable、日期时间和 decimal 语义封闭;每个输入与输出叶字段均有来源;判别联合的 case 完整且互斥;ref 可解析且无环;output 与 errors 不矛盾;不存在 TODO、占位符、空枚举、未解析 ref 或诊断标记。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Prototype 与测试覆盖关联
|
|
2
|
+
|
|
3
|
+
覆盖元数据与后端契约同生命周期,放在每份后端契约文件顶层 `coverage`,不进入线上请求/响应,也不是运行测试日志。实体 source 表达字段业务来源,callSites 表达调用位置,coverage 表达为何足以支撑输入要求;三者不能互相替代。
|
|
4
|
+
|
|
5
|
+
## 形状
|
|
6
|
+
|
|
7
|
+
`coverage` 为非空数组,每项必填且只允许 `id / prototypes / tests / obligations`。id 是文件内唯一的稳定语义标识,不使用数组序号。
|
|
8
|
+
|
|
9
|
+
- prototypes:Prototype 来源数组,每项必填且只允许 `repository / revision / path / selector`。
|
|
10
|
+
- tests:测试来源数组,同样四个必填键。selector 为真实测试责任/case ID;脚本没有独立 ID 时用文件内唯一的完整测试名称,不自创 ID。
|
|
11
|
+
- repository:实际输入仓库地址;revision:固定完整 Git commit;path:仓库根目录相对文件路径;selector:该版本内可唯一解析的页面/组件/动作/状态或测试身份。禁止仅用行号、数组下标或样例别名。可由四者生成固定版本源码链接,不额外保存漂移的 master 链接。原型与测试可在不同仓库或不同文件。
|
|
12
|
+
- 每项 prototypes/tests 至少一个非空;每份后端契约两类来源都必须非空。原型独有显示责任可无 tests,测试独有边界责任可无 prototypes,但必须参与全量核对,不能人为删空掩盖已有对应来源。
|
|
13
|
+
- obligations:非空数组。每项必填且只允许 `aspect / requirement / targets / explanation`。
|
|
14
|
+
- aspect:封闭枚举 `input / output / constraint / frontend`。requirement 是来源中具体操作、展示或断言的准确含义;不是另造需求。explanation 说明目标如何完整支撑该责任,含必要前置事实和适用条件。
|
|
15
|
+
- targets:指向**当前后端契约 YAML 文档**的非空 JSON Pointer 数组,如 `/input/fields/当前绑定`、`/output/cases/查询暂不可用/payload/fields/原因`。转义按 JSON Pointer:`~` 为 `~0`,`/` 为 `~1`。目标必须存在,不是线上 JSON 路径;不得指向 coverage 自身。不得使用样例行号或不稳定数组下标;约束可指向所属 invariants 数组整体,并在 explanation 精确引用其语义,错误码可指向 errors 整体并写明具体 code。空 targets 只允许 frontend 且确实完全不消费后端契约数据的行为,必须说明无需后端契约的依据。
|
|
16
|
+
|
|
17
|
+
结构示意(以下来源字符串需替换为实际输入,不能原样作为交付):
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
coverage:
|
|
21
|
+
- id: non_service_hours
|
|
22
|
+
prototypes:
|
|
23
|
+
- repository: <原型仓库>
|
|
24
|
+
revision: <完整commit>
|
|
25
|
+
path: <原型文件>
|
|
26
|
+
selector: <非服务时段分支的真实身份>
|
|
27
|
+
tests:
|
|
28
|
+
- repository: <测试仓库>
|
|
29
|
+
revision: <完整commit>
|
|
30
|
+
path: <测试文件>
|
|
31
|
+
selector: <非服务时段测试的真实case ID>
|
|
32
|
+
obligations:
|
|
33
|
+
- aspect: output
|
|
34
|
+
requirement: 展示非服务时段原因及服务起止时间。
|
|
35
|
+
targets:
|
|
36
|
+
- /output/cases/查询暂不可用/payload/fields/原因
|
|
37
|
+
- /output/cases/查询暂不可用/payload/fields/每日服务开始时间
|
|
38
|
+
- /output/cases/查询暂不可用/payload/fields/每日服务结束时间
|
|
39
|
+
explanation: 该场景返回原因非服务时段,两时间非空且与来源预期一致,足以支持原型展示和测试断言。
|
|
40
|
+
- aspect: constraint
|
|
41
|
+
requirement: 被服务时段拦截时不执行账单查询。
|
|
42
|
+
targets: [/output/cases/查询暂不可用/invariants]
|
|
43
|
+
explanation: 对应本分支在读取账单前返回的约束,不能只靠返回错误页面而忽略禁止查账的责任。
|
|
44
|
+
- aspect: frontend
|
|
45
|
+
requirement: 返回来源页。
|
|
46
|
+
targets: []
|
|
47
|
+
explanation: 来源中的返回行为不提交业务变更也不消费新增数据,无需额外后端契约。
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 如何证明完整
|
|
51
|
+
|
|
52
|
+
1. 从实际 Prototype 和测试责任清单取得指定范围的完整分母,不能从已经写出的后端契约反推分母。逐项辨认前置事实、动作、预期显示、断言、禁止副作用和后续交互。纯样式/导航等纯前端责任明确分类,不为覆盖它们造后端契约。
|
|
53
|
+
2. 每个后端契约的 coverage 只声明本接口承担的部分。同一用例涉及多个后端契约时,各自映射其负责的操作/断言,按相同来源身份汇总后证明整条用例被支撑;不共享业务 schema,也不要求一用例一接口。
|
|
54
|
+
3. 对每条关联实际推演:动作值能否形成合法 input;系统身份是否仍由系统提供;预期结果是否落到正确输出分支;字段、枚举、nullable、顺序和关联是否足够;写入/不写入及后续页面行为是否符合预期。仅验证 URL/selector/target 存在不算语义覆盖。
|
|
55
|
+
4. 反向检查各调用点、业务结果、叶字段及约束可追溯到两类基准的需求或为满足它们所必需的实体投影。系统通用信封遵守既有规范,不为其伪造业务测试。Form/Rule 来源规则保持,但不能借机自造实体、展示包装或技术字段。有限样例不定义完整枚举。
|
|
56
|
+
5. 汇总所有后端契约的关联,检查输入分母的所有项。完全独立且无需后端契约的纯前端项在交付 review 中逐项说明,无需为其创建空后端契约文件;消费当前后端契约输出的后续前端项留在该文件 coverage 内。未覆盖责任和冲突在过程反馈中列明,不能把缺口写成“已覆盖”或以 fail-closed 错误替代已要求的业务结果。
|
|
57
|
+
6. Prototype 或测试更新时重核对应 revision/selector 和 obligations,后端契约 schema 变化时重核 targets 和覆盖语义;不能只更新 commit 字符串。固定版本关联是追溯依据,不允许用旧版本清单宣称覆盖新的输入。
|
|
58
|
+
|
|
59
|
+
设计完备表示当前指定 Prototype 与测试用例全部得到支撑,并且字段业务来源、协议类型等契约检查成立。不是已完成生产实现、测试已运行或所有业务理论情况被穷举。来源缺失、冲突或无法定位时,不自行扩充所有 Spec 需求或补写测试来制造完备;报告并继续不受影响部分。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 后端契约设计 Findings v1
|
|
2
|
+
|
|
3
|
+
当 Spec 无法支持完整契约时记录 finding。Finding 是诊断,不是后端契约字段,也不构成自行增加业务 fallback 的授权。Skill 或 v1 格式自身的表达能力问题应修复 Skill,不登记成项目 Spec finding。
|
|
4
|
+
|
|
5
|
+
Finding 写入调用方指定的过程记录位置;未指定时只在交付说明中报告。不得在正式 `code/backend-contract` 目录创建 findings 文件。
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
findingsVersion: backend-contract-findings/v1
|
|
9
|
+
findings:
|
|
10
|
+
- id: BILL-VISUAL-001
|
|
11
|
+
severity: blocker
|
|
12
|
+
category: missing-call-site
|
|
13
|
+
affected:
|
|
14
|
+
contractCandidate: query_bill_input_model
|
|
15
|
+
path: callSites
|
|
16
|
+
evidence:
|
|
17
|
+
- {kind: logicTransaction, paragraph: 查缴域·查账, branch: 主分支, transactionId: 查询并形成欠费单}
|
|
18
|
+
- {kind: visualPage, page: 欠费单查询}
|
|
19
|
+
message: Logic 要求取得动态字段协议后接收用户输入,但 Visual 中不存在取得并展示该协议的调用点。
|
|
20
|
+
requiredUpstreamChange: 增加承载输入模型的 Visual 状态,以及取得模型并绑定提交动作的精确读取或动作。
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
必填字段为 `id / severity / category / affected / evidence / message / requiredUpstreamChange`。
|
|
24
|
+
|
|
25
|
+
严重程度为 `blocker / warning`。
|
|
26
|
+
|
|
27
|
+
`blocker` 只阻塞无法安全表达的后端契约或字段。若 Spec 已明确排除未定义状态,且一个不创造业务含义的 fail-closed error 足以隔离它们,可以继续交付已明确结果,并把 finding 标记为 `warning`。
|
|
28
|
+
|
|
29
|
+
问题分类为 `missing-call-site / ambiguous-call-site / missing-type / type-conflict / missing-source / ambiguous-source / logic-visual-conflict / result-space-incomplete`。`unsupported-shape` 只用于当前消费者环境尚未升级到 Skill 已定义的格式,不能把 Skill 自身缺少通用表达能力伪装成 Spec 问题。
|
|
30
|
+
|
|
31
|
+
Finding 的影响范围必须精确。一个受影响的后端契约或字段绝不能阻塞无关的完整契约。evidence 优先使用 canonical Compiler location;不可用时使用稳定来源身份,绝不能只使用行号。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
schemaVersion: saddle-db-table/v1
|
|
2
|
+
entity:
|
|
3
|
+
source: {entity: 替换为内部持久化实体}
|
|
4
|
+
persistenceReason: 替换为可追溯的跨事务持久化原因。
|
|
5
|
+
table:
|
|
6
|
+
name: 替换为同一个Structure实体名
|
|
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
|
|
31
|
+
columns:
|
|
32
|
+
替换为Structure字段名:
|
|
33
|
+
type: {kind: varchar, length: 64}
|
|
34
|
+
nullable: true
|
|
35
|
+
comment: 替换为字段业务语义。
|
|
36
|
+
source: {entity: 替换为字段声明实体, field: 替换为Structure字段名}
|
|
37
|
+
accessResponsibilities:
|
|
38
|
+
- source:
|
|
39
|
+
kind: logic
|
|
40
|
+
board: 替换为板块
|
|
41
|
+
node: 替换为Logic节点
|
|
42
|
+
operations:
|
|
43
|
+
- kind: read
|
|
44
|
+
columns: [替换为Structure字段名]
|
|
45
|
+
predicateColumns: []
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
# Saddle DB 设计
|
|
3
|
+
|
|
4
|
+
从 Islands Spec 确定性产出 Saddle 拥有的逻辑表契约。数据库设计覆盖后端契约、系统事务、定时任务、消息和回调等全部 Saddle 持久化责任;后端契约只是可选入口,不是建表前提。
|
|
5
|
+
|
|
6
|
+
执行前必须阅读 [references/db-table-v1.md](references/db-table-v1.md)。遇到实体归属、生命周期、字段来源、类型或事务责任无法唯一确定时,阅读 [references/findings.md](references/findings.md)。
|
|
7
|
+
|
|
8
|
+
## 输入
|
|
9
|
+
|
|
10
|
+
- 用户明确提供的交付目录;不得自行改用固定仓库路径。
|
|
11
|
+
- 已编译 Islands Spec 中相关 Structure、Logic、Rule 与 Test Responsibility。
|
|
12
|
+
- 已确认的后端契约可作为事务入口证据,但不是必需输入。
|
|
13
|
+
|
|
14
|
+
## 工作流程
|
|
15
|
+
|
|
16
|
+
1. 从 Logic 生命周期识别必须跨事务保留并在后续读取、更新或判定的事实。
|
|
17
|
+
2. 回到 Structure 确认实体所有权。只有未声明 `external: true` 的实体才可能建表;本地所有权不自动等于需要持久化。
|
|
18
|
+
3. 每个需要持久化的具体实体产出一个完整表契约。表与业务列原样复用 Structure 实体和字段身份,并包含全部 Structure 字段。
|
|
19
|
+
4. 子实体表展开全部继承字段。父实体只有存在独立持久化生命周期时才单独建表。
|
|
20
|
+
5. 精确关联每个访问该表的 Logic 事务及其读写字段。事务由后端契约承载时可附加 `backendContractId`,但不得用后端契约身份替代 Logic 事务身份。
|
|
21
|
+
6. 跨文件检查逻辑表身份唯一、字段来源可解析、关联目标存在、类型完整。
|
|
22
|
+
7. 只交付完整表文件。局部不确定项按 findings 规则报告,不用临时字段、宽泛类型或冗余表绕过。
|
|
23
|
+
|
|
24
|
+
## 产出
|
|
25
|
+
|
|
26
|
+
- 在用户指定的 DB 交付目录中,每个持久化实体产出一个 `<Structure实体名>.table.yaml`。
|
|
27
|
+
- DB 交付目录只放正式表契约。Findings 写入用户指定的过程协作位置;未指定时直接反馈给用户,不在交付目录落盘。
|
|
28
|
+
|
|
29
|
+
使用 [assets/table.template.yaml](assets/table.template.yaml) 作为起始结构。交付文件不得包含 TODO、占位符、阻塞标记或诊断节点。
|
|
30
|
+
|
|
31
|
+
不生成物理表名/列名映射、索引、外键、唯一约束、CHECK、DDL、迁移脚本或数据库访问代码。Saddle 的必填映射能力只负责逻辑表名/列名到物理标识符的映射,不拥有其他表结构设计权。
|
|
32
|
+
|
|
33
|
+
## 固定边界
|
|
34
|
+
|
|
35
|
+
- 表契约使用 MySQL 基础类型,但逻辑表名和业务列名保持 Islands 原始业务概念。
|
|
36
|
+
- 每张表固定使用 `uid`、`gmt_create`、`gmt_modified`、`is_deleted` 四个系统列;业务 Agent 不增删或改写其语义。
|
|
37
|
+
- 所有业务字段允许 `NULL`,且不设置数据库默认值。必填、枚举、值域、业务唯一性和关联完整性由 Saddle 业务逻辑负责。
|
|
38
|
+
- 不设计业务唯一约束、普通索引或数据库外键。索引属于运行后的观测与运维优化。
|
|
39
|
+
- 实体关联字段保留 Structure 的关联业务语义;内部关联存目标 uid,external 关联存可长期解析的外部身份引用,不强制本地 UUID。多重关联依靠原始角色语义区分。不得为制造 uid 而给 external 实体建表,也不要求业务 Spec 定义技术编码。
|
|
40
|
+
- varchar 使用已确认的长度分档;人民币金额沿用有符号 BIGINT 分存储,不沿用后端契约的元字符串作为物理列类型。
|
|
41
|
+
- 文章正文等长文本可使用 `type: {kind: text}`,对应 MySQL TEXT,不填写 length。仍需真实 Structure 字段来源、nullable 为 true 且无默认值;TEXT 不是无限容量,也不用于绕过不明确的结构化建模。具体容量边界见类型参考。
|
|
42
|
+
- 新版 Flow 使用真实板块/节点记录 Logic、Branch 访问责任,也覆盖 Visual 的持久读取;不虚构旧 Paragraph/Transaction。已传入 Row 的纯内存消费不等于再次读库。
|
|
43
|
+
- 删除统一为软删除。Saddle 数据访问层必须对普通读取强制过滤 `is_deleted = false`;业务查询不得各自实现这条规则。
|
|
44
|
+
- 把业务状态更新为“已删除”仍是 update,不等于实体 delete,不自动联动 is_deleted。
|
|
45
|
+
- 一个不完整实体只阻塞对应表,不阻塞其他完整表的交付。
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Saddle DB 逻辑表契约 v1
|
|
2
|
+
|
|
3
|
+
0.1.4 命名调整:可选的后端契约关联键统一为 `backendContractId`,旧键 `apiId` 不用于新交付。其值仍为对应后端契约的 `id`,不替代 Logic 事务身份。现有表文件与消费者需单独迁移,不能宣称旧消费者自动兼容;表结构和运行时存储语义不变。
|
|
4
|
+
|
|
5
|
+
## 设计单位与身份
|
|
6
|
+
|
|
7
|
+
一个 `*.table.yaml` 只定义一个 Saddle 内部持久化实体的一张逻辑表。v1 不支持一个实体拆多表或多实体合表;遇到该场景产出 `unsupported-shape` finding。
|
|
8
|
+
|
|
9
|
+
逻辑表身份直接复用 Islands Structure 实体名。文件固定命名为 `<Structure实体名>.table.yaml`,`table.name`、`entity.source.entity` 和文件名 stem 必须原样一致。中文业务身份合法,不翻译为物理标识符。
|
|
10
|
+
|
|
11
|
+
Saddle 的必填物理映射配置负责逻辑表名/列名到 MySQL 表名/列名的映射。映射层只改名字,不得改变字段集合、类型、nullable、默认值、主键或系统语义;物理映射不属于本 Skill 产物。
|
|
12
|
+
|
|
13
|
+
## 完整结构
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
schemaVersion: saddle-db-table/v1
|
|
17
|
+
entity:
|
|
18
|
+
source: {entity: 用户账单}
|
|
19
|
+
persistenceReason: 用户账单由本系统创建并跨事务保留,供后续查询和状态更新。
|
|
20
|
+
table:
|
|
21
|
+
name: 用户账单
|
|
22
|
+
comment: 用户账单持久化事实
|
|
23
|
+
systemColumns:
|
|
24
|
+
uid:
|
|
25
|
+
type: {kind: varchar, length: 32}
|
|
26
|
+
nullable: false
|
|
27
|
+
primaryKey: true
|
|
28
|
+
generation: {owner: saddle, algorithm: uuid_v7, format: lowercase_hex_without_hyphens}
|
|
29
|
+
gmt_create:
|
|
30
|
+
type: {kind: datetime, precision: 3}
|
|
31
|
+
nullable: false
|
|
32
|
+
default: current_timestamp
|
|
33
|
+
maintainedBy: database
|
|
34
|
+
gmt_modified:
|
|
35
|
+
type: {kind: datetime, precision: 3}
|
|
36
|
+
nullable: false
|
|
37
|
+
default: current_timestamp
|
|
38
|
+
onUpdate: current_timestamp
|
|
39
|
+
maintainedBy: database
|
|
40
|
+
is_deleted:
|
|
41
|
+
type: {kind: boolean}
|
|
42
|
+
nullable: false
|
|
43
|
+
default: false
|
|
44
|
+
maintainedBy: saddle
|
|
45
|
+
readPolicy: exclude_deleted_by_default
|
|
46
|
+
columns:
|
|
47
|
+
账单标识:
|
|
48
|
+
type: {kind: varchar, length: 64}
|
|
49
|
+
nullable: true
|
|
50
|
+
comment: 用户账单的业务标识
|
|
51
|
+
source: {entity: 用户账单, field: 账单标识}
|
|
52
|
+
所属用户:
|
|
53
|
+
type: {kind: varchar, length: 32}
|
|
54
|
+
nullable: true
|
|
55
|
+
comment: 关联用户的 uid
|
|
56
|
+
source: {entity: 用户账单, field: 所属用户}
|
|
57
|
+
storesEntityUid: {entity: 用户}
|
|
58
|
+
应缴金额:
|
|
59
|
+
type: {kind: bigint}
|
|
60
|
+
nullable: true
|
|
61
|
+
comment: 应缴金额,单位为人民币分
|
|
62
|
+
money: {currency: CNY, unit: minor, minorUnitsPerMajor: 100}
|
|
63
|
+
source: {entity: 用户账单, field: 应缴金额}
|
|
64
|
+
accessResponsibilities:
|
|
65
|
+
- transaction:
|
|
66
|
+
paragraph: 账单域·账单维护
|
|
67
|
+
branch: 主分支
|
|
68
|
+
transactionId: 创建用户账单
|
|
69
|
+
backendContractId: create_user_bill
|
|
70
|
+
operations:
|
|
71
|
+
- kind: insert
|
|
72
|
+
columns: [账单标识, 所属用户, 应缴金额]
|
|
73
|
+
predicateColumns: []
|
|
74
|
+
- transaction:
|
|
75
|
+
paragraph: 账单域·账单维护
|
|
76
|
+
branch: 主分支
|
|
77
|
+
transactionId: 查询用户账单
|
|
78
|
+
operations:
|
|
79
|
+
- kind: read
|
|
80
|
+
columns: [账单标识, 所属用户, 应缴金额]
|
|
81
|
+
predicateColumns: [所属用户]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
顶层字段 `schemaVersion / entity / table / systemColumns / columns / accessResponsibilities` 全部必填,禁止未知顶层字段。示例中的 `backendContractId` 可省略;其他示例身份和内容必须替换为真实编译来源。
|
|
85
|
+
|
|
86
|
+
## 持久化判定
|
|
87
|
+
|
|
88
|
+
DB 设计覆盖 Saddle 拥有的全部持久化事实,不以是否存在后端契约为边界。只有同时满足以下条件的实体才产出表:
|
|
89
|
+
|
|
90
|
+
1. 实体未声明 `external: true`;
|
|
91
|
+
2. Logic 明确要求该事实跨事务保留;
|
|
92
|
+
3. 后续 Logic 会读取、更新、删除或据此判定;
|
|
93
|
+
4. 实体生命周期和所有权可以唯一确定。
|
|
94
|
+
|
|
95
|
+
以下内容不建表:`external: true` 的实体;只由外部 SOFA RPC 维护的事实;页面状态、后端契约返回模型 或请求内临时对象;一次 External 调用的临时快照;没有独立持久化生命周期的可重算结果。
|
|
96
|
+
|
|
97
|
+
“页面需要展示”“后端契约需要返回”或“Structure 中存在实体”本身都不构成持久化理由。
|
|
98
|
+
|
|
99
|
+
## 实体与继承
|
|
100
|
+
|
|
101
|
+
一个需要持久化的具体实体对应一张逻辑表,并包含其全部 Structure 字段。子实体表直接展开父实体继承的全部字段;继承字段的 `source` 指向其原始声明实体和字段。父实体只有存在独立持久化生命周期时才单独建表。v1 不设计父子表连接或其他数据库继承策略。
|
|
102
|
+
|
|
103
|
+
## 系统列
|
|
104
|
+
|
|
105
|
+
每张表的 `systemColumns` 必须与固定结构完全一致:
|
|
106
|
+
|
|
107
|
+
- `uid`:唯一主键。Saddle 应用侧生成标准 UUIDv7,去掉中横线后保存为 32 位小写十六进制字符串;不增加严格单调等自定义扩展。
|
|
108
|
+
- `gmt_create`:`DATETIME(3)`,数据库使用 `DEFAULT CURRENT_TIMESTAMP(3)` 维护。
|
|
109
|
+
- `gmt_modified`:`DATETIME(3)`,数据库使用 `DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)` 维护。
|
|
110
|
+
- `is_deleted`:布尔值,数据库默认 `false`;Logic 删除由 Saddle 更新为 `true`,数据库随之更新 `gmt_modified`。
|
|
111
|
+
|
|
112
|
+
表契约不规定数据库实例或会话时区;`DATETIME` 的环境时间语义属于运维配置。
|
|
113
|
+
|
|
114
|
+
Saddle 数据访问层必须对所有普通读取强制施加 `is_deleted = false`。只有明确的恢复、审计或运维能力可以读取已删除记录。普通业务查询不得自行重复实现或绕过默认过滤。关联到已软删除记录时,业务上视为目标不存在。
|
|
115
|
+
|
|
116
|
+
## 业务列与来源
|
|
117
|
+
|
|
118
|
+
`columns` 必须包含实体的全部 Structure 字段。每个业务列名原样复用 Structure 字段名,并具有且仅具有一个直接来源:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
source: {entity: 用户账单, field: 账单状态}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
所有业务列固定 `nullable: true`,不允许 `default` 或 `onUpdate`。Structure 的必填、枚举和值域仍是业务规则,由 Saddle 逻辑校验,不下沉为数据库约束。
|
|
125
|
+
|
|
126
|
+
内部实体关联保留原始业务角色名,并存储目标实体的 `uid`:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
卖家:
|
|
130
|
+
type: {kind: varchar, length: 32}
|
|
131
|
+
nullable: true
|
|
132
|
+
source: {entity: 订单, field: 卖家}
|
|
133
|
+
storesEntityUid: {entity: 用户}
|
|
134
|
+
买家:
|
|
135
|
+
type: {kind: varchar, length: 32}
|
|
136
|
+
nullable: true
|
|
137
|
+
source: {entity: 订单, field: 买家}
|
|
138
|
+
storesEntityUid: {entity: 用户}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
同一目标实体被多次关联时,以 Structure 中不同的关联角色区分。不得改成含义不清的统一“用户”列,也不建立数据库外键。
|
|
142
|
+
|
|
143
|
+
external 关联使用 `storesExternalReference`,与 `storesEntityUid` 互斥:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
户号:
|
|
147
|
+
type: {kind: varchar, length: 256}
|
|
148
|
+
nullable: true
|
|
149
|
+
comment: 可长期解析的外部户号身份,包含区分机构和费种的身份语义。
|
|
150
|
+
source: {entity: 户号绑定关系, field: 户号}
|
|
151
|
+
storesExternalReference: {entity: 户号}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
目标必须是真实 external 实体。引用必须跨请求持续定位同一外部身份,不强制本地 UUIDv7,不用可能重名的展示字段代替身份,不把有失效期的后端契约令牌直接作为长期关联。长度是引用实现必须遵守的存储上界;不能静默截断,也不能宣称未知编码已经验证可用。具体编码和解析由实现层承担,出现冲突反馈讨论,不要求业务 Spec 表达技术细节。不因此新增本地映射表或 external 镜像表。
|
|
155
|
+
|
|
156
|
+
## 基础 MySQL 类型
|
|
157
|
+
|
|
158
|
+
v1 只允许以下封闭类型:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
{kind: varchar, length: 64}
|
|
162
|
+
{kind: text}
|
|
163
|
+
{kind: bigint}
|
|
164
|
+
{kind: decimal, precision: 18, scale: 2}
|
|
165
|
+
{kind: boolean}
|
|
166
|
+
{kind: date}
|
|
167
|
+
{kind: datetime, precision: 3}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- `varchar.length` 默认采用已确认的64/256/1024档:标识符、编码、户号文本、枚举64;名称256;地址1024。明确的项目约定优先。外部复合身份引用不是裸户号文本,应按已确认编码预算选档;例如 bill 的用户引用64、户号引用256。内部 uid 固定32。不要求 Spec 精确给出数据库长度;超出已选上界或需要新类型时讨论,不静默截断。
|
|
171
|
+
- `bigint` 固定生成 MySQL 64 位有符号整数。
|
|
172
|
+
- `text` 对应 MySQL `TEXT`,类型节点仅为 `{kind: text}`,不允许 length、precision 或 scale。适用于文章正文等有明确业务含义的长文本,不强制按 varchar 的长度分档。最大容量为 65,535 字节而非字符数,可容纳字符数取决于数据库字符集;环境字符集仍由运维负责,不新增实例配置。业务长度与内容格式由业务逻辑处理,不生成数据库约束,不静默截断。明确超过 TEXT 容量时反馈讨论,不自动升级 MEDIUMTEXT/LONGTEXT。依据:[MySQL 字符串类型](https://dev.mysql.com/doc/refman/8.4/en/string-type-syntax.html)。
|
|
173
|
+
- TEXT 字段继续使用 `nullable: true`、无数据库默认值和原始 Structure 字段来源。仅选择存储类型,不擅自规定正文是纯文本、HTML 或 Markdown,也不因正文可能很长而另拆表、增加索引或生成 DDL。不能用 TEXT 序列化未知对象来绕过未支持的结构或关联。
|
|
174
|
+
- `decimal` 必须明确 precision、scale 和单位;精度与小数位来自 Structure/Rule。
|
|
175
|
+
- 人民币金额使用有符号 `bigint` 保存分,附加 `money: {currency: CNY, unit: minor, minorUnitsPerMajor: 100}`;与后端契约的元精确十进制字符串在应用边界准确换算,不使用二进制浮点数。非负等业务值域仍由业务逻辑负责。其他币种或需要不足一分的精度时先讨论,不套用人民币比例。没有本地金额字段时不为使用此规则新增字段或表。
|
|
176
|
+
- `boolean` 固定生成 MySQL `TINYINT(1)`,但契约保留布尔业务语义。
|
|
177
|
+
- `date` 表示业务日期。
|
|
178
|
+
- `datetime` 必须明确 precision;系统时间列固定为 3。
|
|
179
|
+
- 枚举使用能够容纳全部合法值的 `varchar`;合法值由业务逻辑校验,不生成数据库 ENUM 或 CHECK。
|
|
180
|
+
- JSON、TINYTEXT、MEDIUMTEXT、LONGTEXT、BLOB、浮点数、TIMESTAMP、数据库 ENUM、生成列等不在 v1 中;确定需要时产出 `unsupported-shape` finding。
|
|
181
|
+
|
|
182
|
+
长文本业务列示例(只在输入确有此字段时使用,不是要求所有表增加正文):
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
文章内容:
|
|
186
|
+
type: {kind: text}
|
|
187
|
+
nullable: true
|
|
188
|
+
comment: 文章正文内容
|
|
189
|
+
source: {entity: 文章, field: 文章内容}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 数据库约束边界
|
|
193
|
+
|
|
194
|
+
除 `uid` 主键及四个系统列的固定规则外,v1 不设计其他数据库约束:
|
|
195
|
+
|
|
196
|
+
- 不使用复合主键或自增主键;
|
|
197
|
+
- 不建立业务唯一约束;
|
|
198
|
+
- 不建立数据库外键;
|
|
199
|
+
- 不建立普通索引;
|
|
200
|
+
- 不生成 CHECK、触发器或业务默认值。
|
|
201
|
+
|
|
202
|
+
业务唯一性、关联一致性、必填和值域校验由 Saddle 业务逻辑实现。索引属于系统运行后的性能观测和运维优化,不在初始表结构设计阶段预判。
|
|
203
|
+
|
|
204
|
+
## Logic 事务访问责任
|
|
205
|
+
|
|
206
|
+
`accessResponsibilities` 穷举当前表支持的 Logic 事务读写责任。事务身份必须通过 Islands Compiler 元数据唯一解析:
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
transaction:
|
|
210
|
+
paragraph: 查缴域·查账
|
|
211
|
+
branch: 主分支
|
|
212
|
+
transactionId: 查询并形成欠费单
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
事务可以来自人工后端契约、系统事务、定时任务、消息或回调。`backendContractId` 仅在该事务由已确认后端契约承载时出现,并必须解析到对应后端契约。
|
|
216
|
+
|
|
217
|
+
每个 operation:
|
|
218
|
+
|
|
219
|
+
- `kind` 只允许 `read / insert / update / delete`;
|
|
220
|
+
- `columns` 精确列出读取、插入或变更的逻辑业务列;
|
|
221
|
+
- `predicateColumns` 精确列出定位、关联或筛选使用的逻辑业务列;
|
|
222
|
+
- 不记录 SQL、索引或物理列名;
|
|
223
|
+
- `delete` 表达 Logic 删除语义,运行时固定执行软删除,不执行物理 `DELETE`。
|
|
224
|
+
|
|
225
|
+
系统列生成、时间维护和默认软删除过滤是 Saddle 固定责任,不在每个 operation 中重复列出。
|
|
226
|
+
|
|
227
|
+
### 新版 Flow 与 Visual 来源
|
|
228
|
+
|
|
229
|
+
每项责任用 `transaction`(旧结构)或 `source`(新结构)之一,不能同时填写。新版 source 支持:
|
|
230
|
+
|
|
231
|
+
```yaml
|
|
232
|
+
source: {kind: logic, board: 查询欠费单, node: 替换为新户号绑定}
|
|
233
|
+
source: {kind: branch, board: 查询欠费单, node: 校验户号绑定}
|
|
234
|
+
source: {kind: visualRead, page: 查询户号, read: $本人绑定列表}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
节点类型必须与来源一致;名字通过编译元数据或稳定源码身份解析,不保存数组下标。责任覆盖当前持久查询和写入,不把每个页面对已传入 Row 的内存读取都编造为独立数据库查询;后端契约引用解析需要的按 uid 加载属于运行时实现责任,不能虚构 Spec 事务。
|
|
238
|
+
|
|
239
|
+
Logic 责任可附 `atomic: true` 表示来源明确要求该节点的多项操作同一事务提交,不代表整条后端契约链路或外部调用具备原子性。更新传入 Row 时 operation 可附 `targetInput: 当前绑定`:必须对应该节点真实 Row 入参,通过其实体身份定位目标行;不把 uid 塞入业务 predicateColumns,也不把空 predicateColumns 解释为全表更新。新建行及同节点后续赋值可以合为一个 insert 责任,但必须包含全部赋值列。
|
|
240
|
+
|
|
241
|
+
Spec 把业务状态写为“已删除”时,记录 update 和相应业务列,不推导额外的 is_deleted 更新。只有实体 delete 语义才执行统一软删除。
|
|
242
|
+
|
|
243
|
+
## 完成门禁
|
|
244
|
+
|
|
245
|
+
1. 实体已证明为 Saddle 内部持久化实体,且不是 `external: true`;
|
|
246
|
+
2. 文件名、`table.name` 和 Structure 实体身份原样一致;
|
|
247
|
+
3. 表包含固定且完整的四个系统列;
|
|
248
|
+
4. 全部 Structure 字段均出现,且每个业务列解析到唯一 Structure 字段;
|
|
249
|
+
5. 内部关联声明 storesEntityUid;external 关联声明 storesExternalReference,引用稳定且长度明确;两者互斥,多重关联角色无歧义;
|
|
250
|
+
6. 类型参数完整,全部业务字段 nullable 且无默认值;
|
|
251
|
+
7. 每个旧事务或新版 Flow/Visual 来源身份可解析,字段访问责任完整;更新传入Row时目标身份明确;
|
|
252
|
+
8. 不存在业务唯一约束、普通索引、外键、物理映射、TODO、占位符或诊断节点;
|
|
253
|
+
9. 交付目录中不存在本 Skill 生成的 findings、DDL、迁移脚本或 `structure.sql`。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# DB 设计 Findings v1
|
|
2
|
+
|
|
3
|
+
当 Islands Spec 的业务表达无法支持完整表契约时,报告 DB finding。Skill 自身的格式问题由 lead 修复,不伪装为 Spec 问题;技术编码、长度选档和外部引用实现问题也不要求业务 Spec 补字段。Finding 是过程诊断,不是正式 DB 交付物,也不授权 Agent 创建临时字段、宽泛类型、冗余表或物理约束绕过问题。
|
|
4
|
+
|
|
5
|
+
DB 交付目录只放完整的 `*.table.yaml`。用户指定过程协作位置时,可在那里写入 `db-design-findings.yaml`;未指定时直接向用户反馈,不自行在交付目录或其他仓库落盘。
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
findingsVersion: saddle-db-findings/v1
|
|
9
|
+
findings:
|
|
10
|
+
- id: BILL-DB-001
|
|
11
|
+
severity: blocker
|
|
12
|
+
category: persistence-ownership-unknown
|
|
13
|
+
affected:
|
|
14
|
+
entity: 欠费单
|
|
15
|
+
path: entity.persistenceReason
|
|
16
|
+
evidence:
|
|
17
|
+
- {kind: structureEntity, entity: 欠费单}
|
|
18
|
+
- {kind: logicTransaction, paragraph: 查缴域·查账, branch: 主分支, transactionId: 查询并形成欠费单}
|
|
19
|
+
message: 当前只能确认欠费单来自一次外部查询,不能证明 Saddle 需要跨事务持久保存。
|
|
20
|
+
requiredUpstreamChange: 明确该事实只在请求内使用,或补齐由 Saddle 创建、读取、更新或删除的跨事务生命周期。
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
必填字段为 `id / severity / category / affected / evidence / message / requiredUpstreamChange`。
|
|
24
|
+
|
|
25
|
+
严重程度为 `blocker / warning`。
|
|
26
|
+
|
|
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`
|
|
36
|
+
|
|
37
|
+
Finding 必须精确到实体、字段或 Logic 事务。一个实体的问题不能阻塞其他完整表文件。Evidence 优先使用 Islands Compiler 的稳定身份,不得只使用源码行号。
|
package/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@antprofuse/saddle-islands-design",
|
|
3
|
+
"version": "0.1.13",
|
|
4
|
+
"description": "从 Islands Spec 统一设计 Saddle 后端服务契约与逻辑数据库表。",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"files": ["SKILL.md", "agents", "modules"],
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
}
|
|
11
|
+
}
|