@antprofuse/saddle-skill 0.3.0-alpha.4 → 0.3.0-alpha.5
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 +10 -6
- package/agents/openai.yaml +1 -1
- package/package.json +2 -2
- package/references/consumer-start.md +2 -2
- package/references/external-function-contracts.md +1 -1
- package/references/local-integration.md +2 -2
- package/references/programming-model.md +5 -4
- package/references/response-model.md +143 -0
- package/references/validation.md +6 -4
- package/scripts/saddle-03-gate.js +17 -5
package/SKILL.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: saddle-service-0-3
|
|
3
|
-
description: 使用 Saddle 0.3.0-alpha.
|
|
3
|
+
description: 使用 Saddle 0.3.0-alpha.5 Skill 开发或审查带 profusegw 固定入口和 profusecontract 强类型外部函数调用的 Rust 业务应用。用于定义 protobuf 契约、编写固定 ProfuseGwContext handler、以框架响应映射业务拒绝和技术失败,或运行正式业务门禁。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Saddle 0.3.0-alpha.
|
|
6
|
+
# Saddle 0.3.0-alpha.5 业务研发契约
|
|
7
7
|
|
|
8
8
|
业务 Agent 的直接输入只有业务 spec 与本 Skill。不要要求用户阅读 Saddle 仓库、审计报告、组件提交或内部实现。
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ description: 使用 Saddle 0.3.0-alpha.4 开发或审查带 profusegw 固定入
|
|
|
12
12
|
1. 阅读 [消费者起手式](references/consumer-start.md),只从业务 spec 提取接口、业务类型、业务规则、业务结果和外部业务能力。
|
|
13
13
|
2. 先按 [外部函数协议](references/external-function-contracts.md) 在唯一 `contract_dir` 根部生成固定 `saddle_external_function.proto`,再只把具体外部函数写入业务 `.proto`。冻结目录内容后,才并行交给 Saddle 后端和 profusecontract 实现。
|
|
14
14
|
3. 按 [编程模型](references/programming-model.md) 声明应用、固定 profusegw operation、应用级强类型外部函数能力和唯一 handler。
|
|
15
|
-
4.
|
|
15
|
+
4. 按 [响应建模](references/response-model.md) 定义封闭业务码,并把业务拒绝与八类技术失败 × 三种执行确定性逐项映射为框架固定响应。禁止 `Result`、wildcard/default、空成功或自建响应外壳。
|
|
16
16
|
5. 按 [本地联调与部署配置](references/local-integration.md) 配置唯一 plaintext profusecontract authority,使用固定 profusegw body 验证真实强类型调用链。
|
|
17
17
|
6. 按 [正式 Gate](references/validation.md) 生成 lock,并在交付前运行:
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ description: 使用 Saddle 0.3.0-alpha.4 开发或审查带 profusegw 固定入
|
|
|
20
20
|
npx --no-install saddle-03-gate --manifest-path Cargo.toml
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
7. Gate 或编译失败时停止,保留最小业务 consumer 复现;不得复制或修改 Saddle 中间件绕过。
|
|
24
24
|
|
|
25
25
|
## 正式版本身份
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ description: 使用 Saddle 0.3.0-alpha.4 开发或审查带 profusegw 固定入
|
|
|
28
28
|
|
|
29
29
|
```toml
|
|
30
30
|
[dependencies]
|
|
31
|
-
saddle-framework = "=0.3.0-alpha.
|
|
31
|
+
saddle-framework = "=0.3.0-alpha.5"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
`saddle-framework` 是唯一业务 facade。业务不得直接依赖 `saddle-core`、`saddle-admission`、`saddle-boundary`、`saddle-db`、`saddle-macros`、`saddle-observability`、`saddle-runtime` 或 `saddle-service`。
|
|
@@ -40,6 +40,8 @@ saddle-framework = "=0.3.0-alpha.4"
|
|
|
40
40
|
- 应用声明唯一字面量 `deployment_app`;请求 `target.app` 必须逐字匹配。每个字面量 `target.interfaceId` 只由静态 `operation_type` 分派,业务 handler 不读取或自行路由它。
|
|
41
41
|
- profusegw body 固定且只含 `target`、`profuseGwContext`、`requestData`;`profuseGwContext.userInfo.userId` 由框架形成 `ProfuseGwContext { user_id }`,业务 payload 不得覆盖。
|
|
42
42
|
- 所有 handler 使用 `saddle::ingress::ProfuseGwContext`;只读取正式 API 提供的只读、有界字段,不得读取 Header、Cookie、原始 JSON 或任意上下文 Map。
|
|
43
|
+
- 所有 handler 直接返回 `saddle::ingress::ProfuseGwResponse<Data, Code>`。成功只携带有界强类型 `Data`;失败必须携带封闭业务码和不超过 256 bytes 的展示型 `ResultView`。
|
|
44
|
+
- 每个 service 显式声明 `result_code`。业务码实现 `ProfuseGwResultCode` 并以穷尽 match 返回稳定机器码;`resultView` 不是机器判断依据。
|
|
43
45
|
- 应用只配置一个 `contract_dir`。Saddle 在编译期确定性递归扫描其中的 `.proto`、闭合 import,并按 canonical 相对路径形成身份;不得列 proto 文件清单、维护聚合 proto,或在运行时扫描目录。
|
|
44
46
|
- 将 [固定协议模板](assets/saddle_external_function.proto) 原样写为 `${contract_dir}/saddle_external_function.proto`。不得改名、改内容、增加版本副本或另建 protocol 目录。
|
|
45
47
|
- 具体外部函数只写在业务 proto,并使用 `import "saddle_external_function.proto";`。profusecontract 直接消费整个 `contract_dir`,无需安装 Saddle。
|
|
@@ -59,6 +61,8 @@ saddle-framework = "=0.3.0-alpha.4"
|
|
|
59
61
|
- 需要动态 function/descriptor、raw invoke、裸 gRPC、原始网络上下文或未声明外部 I/O;
|
|
60
62
|
- 缺少固定 `deployment_app`,`target.app`/`interfaceId` 依赖运行时业务选择,或 endpoint 不是单一 plaintext authority;
|
|
61
63
|
- 八类技术失败或三种执行确定性未显式处理;
|
|
62
|
-
-
|
|
64
|
+
- handler 返回 Rust `Result`/`Err`、空成功、任意 JSON,或业务自建 `{success,...}` 响应结构;
|
|
65
|
+
- 业务拒绝或任一技术失败组合没有显式映射,机器码不稳定,或 `ResultView` 超过 256 bytes;
|
|
66
|
+
- Cargo 不是精确 alpha.5 Rust facade、缺 lock,或正式 Gate 不通过。
|
|
63
67
|
|
|
64
68
|
停止时只交最小复现和期望业务表达,由 Saddle 修复公共能力缺口。
|
package/agents/openai.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Saddle 0.3 业务应用"
|
|
3
3
|
short_description: "使用 Saddle 0.3 开发受控边界的 Rust 业务应用"
|
|
4
|
-
default_prompt: "请安装 Saddle 0.3.0-alpha.
|
|
4
|
+
default_prompt: "请安装 Saddle 0.3.0-alpha.5 Skill(npm install --save-exact --ignore-scripts --no-audit --no-fund @antprofuse/saddle-skill@0.3.0-alpha.5),结合业务 spec 开始研发。"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antprofuse/saddle-skill",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
4
|
-
"description": "Saddle 0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.5",
|
|
4
|
+
"description": "Saddle 0.3.0-alpha.5 中文 AI Coding 研发契约与业务门禁。",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"files": ["SKILL.md", "agents", "references", "scripts", "assets"],
|
|
7
7
|
"bin": {"saddle-03-gate": "scripts/saddle-03-gate.js"},
|
|
@@ -46,7 +46,7 @@ Saddle 在编译期从 `contract_dir` 确定性递归收集 `.proto`,闭合 im
|
|
|
46
46
|
|
|
47
47
|
## 阶段 3:Rust 实现
|
|
48
48
|
|
|
49
|
-
业务 crate 精确使用 `saddle-framework = "=0.3.0-alpha.
|
|
49
|
+
业务 crate 精确使用 `saddle-framework = "=0.3.0-alpha.5"`,然后按 [最小编程模型](programming-model.md) 编写固定 `deployment_app`、静态 operation 声明和 handler。不得直接依赖任何 Saddle 中间件 crate。
|
|
50
50
|
|
|
51
51
|
实现通过 Gate 后,按 [本地联调](local-integration.md) 由外部 harness 注入唯一 plaintext profusecontract authority,并以固定 profusegw body 验证真实 typed call。endpoint、request/call identity 和 deadline 都是框架或部署输入,不写入业务 spec。
|
|
52
52
|
|
|
@@ -63,6 +63,6 @@ Saddle 在编译期从 `contract_dir` 确定性递归收集 `.proto`,闭合 im
|
|
|
63
63
|
- 缺少固定 `deployment_app`,或 app/interfaceId/endpoint 被做成动态业务输入;
|
|
64
64
|
- 技术失败码或执行确定性存在 catch-all/default;
|
|
65
65
|
- 需要裸 gRPC/channel/bytes、第二入口、动态 descriptor 或未声明外部 I/O;
|
|
66
|
-
- `saddle-framework` 不是精确 `=0.3.0-alpha.
|
|
66
|
+
- `saddle-framework` 不是精确 `=0.3.0-alpha.5`,或 [正式 Gate](validation.md) 失败。
|
|
67
67
|
|
|
68
68
|
停止产物只包含最小复现、缺失身份和期望业务表达,不包含中间件替代实现。
|
|
@@ -68,7 +68,7 @@ message BoundAccount {
|
|
|
68
68
|
|
|
69
69
|
这里的跨系统正式身份是 `("puc", "查询用户绑定户号")`。`QueryBoundAccounts`、未来生成的 Rust 方法名和 Java 方法名都只是本地代码标识符,不能替换、翻译或推导中文 `function`。
|
|
70
70
|
|
|
71
|
-
该 import 必须精确指向同目录根部的 `saddle_external_function.proto`。其 bytes 来自 Rust Alpha.
|
|
71
|
+
该 import 必须精确指向同目录根部的 `saddle_external_function.proto`。其 bytes 来自 Rust Alpha.5 公开闭包的权威模板并由 Skill 固定携带;不得自行创建同名 extension,也不得复制为另一份协议。
|
|
72
72
|
|
|
73
73
|
实际线上网络仍只调用 Saddle 固定 `ProfuseContractBoundary/Invoke` unary。契约中的 service/rpc 用于绑定身份、request 和 result,不要求 profusecontract 暴露同名网络方法。
|
|
74
74
|
|
|
@@ -26,7 +26,7 @@ profusegw 只向固定路径发送通用 JSON body;业务接口不新增 URL
|
|
|
26
26
|
|
|
27
27
|
## 唯一 plaintext endpoint
|
|
28
28
|
|
|
29
|
-
Alpha.
|
|
29
|
+
Alpha.5 部署配置只接受 profusecontract authority:
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
32
|
http://127.0.0.1:50051
|
|
@@ -34,7 +34,7 @@ http://127.0.0.1:50051
|
|
|
34
34
|
|
|
35
35
|
它必须是 `http` plaintext 的 `host:port`,不得包含 path、query、fragment、userinfo、凭据或动态函数信息。所有强类型函数共享这一部署级连接目标;线上 gRPC method 固定为 `ProfuseContractBoundary/Invoke`,应用不得按函数配置 endpoint。
|
|
36
36
|
|
|
37
|
-
此 endpoint 是部署输入,不进入业务 spec、request 或 handler。TLS、鉴权、服务发现、负载均衡和生产凭据不属于 Alpha.
|
|
37
|
+
此 endpoint 是部署输入,不进入业务 spec、request 或 handler。TLS、鉴权、服务发现、负载均衡和生产凭据不属于 Alpha.5;需要其中任一能力时立即停止,不得在业务 crate 自建 client。
|
|
38
38
|
|
|
39
39
|
## 联调完成条件
|
|
40
40
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 应用显式声明
|
|
4
4
|
|
|
5
|
-
业务应用使用 `saddle-framework = "=0.3.0-alpha.
|
|
5
|
+
业务应用使用 `saddle-framework = "=0.3.0-alpha.5"` 的公开编程面:
|
|
6
6
|
|
|
7
7
|
```rust
|
|
8
8
|
saddle::application! {
|
|
@@ -51,12 +51,13 @@ async fn query_my_bound_accounts(
|
|
|
51
51
|
request: QueryMyBoundAccountsRequest,
|
|
52
52
|
context: saddle::ingress::ProfuseGwContext,
|
|
53
53
|
contract: PucProfuseContract,
|
|
54
|
-
) -> QueryMyBoundAccountsResult
|
|
54
|
+
) -> saddle::ingress::ProfuseGwResponse<QueryMyBoundAccountsResult, BusinessCode>;
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
- `request`:纯业务接口输入。
|
|
58
58
|
- `context`:固定 profusegw 框架上下文;所有接口共用同一类型,0.3.0 只有 `user_id`。
|
|
59
59
|
- `contract`:应用级强类型 profusecontract 调用能力,只包含应用显式登记的函数;不是通用依赖注入容器。
|
|
60
|
+
- 返回值:框架固定 `ProfuseGwResponse<Data, Code>`;详细成功、业务拒绝与技术失败建模见 [响应建模](response-model.md)。禁止 Rust `Result` 和自建 JSON 外壳。
|
|
60
61
|
|
|
61
62
|
所有 profusegw operation 必须使用同一个固定 `ProfuseGwContext`。业务只能读取其只读 `user_id`;禁止用自建 context、Header/JSON Map 或通用依赖容器补位。
|
|
62
63
|
|
|
@@ -64,7 +65,7 @@ async fn query_my_bound_accounts(
|
|
|
64
65
|
|
|
65
66
|
```rust
|
|
66
67
|
match contract.query_bound_accounts(input).await {
|
|
67
|
-
ExternalFunctionResult::Completed(result) =>
|
|
68
|
+
ExternalFunctionResult::Completed(result) => map_business_result(result),
|
|
68
69
|
ExternalFunctionResult::TechnicalFailure(failure) => match failure.code() {
|
|
69
70
|
// 穷举固定技术失败,不允许 wildcard/default。
|
|
70
71
|
},
|
|
@@ -88,7 +89,7 @@ match contract.query_bound_accounts(input).await {
|
|
|
88
89
|
|
|
89
90
|
每个分支还必须显式考虑正式 API 允许的执行确定性。不得使用 `_`、`default`、`UNKNOWN` 或 catch-all;新增技术码必须让旧消费者编译或 Gate 失败,而不是静默进入通用失败。
|
|
90
91
|
|
|
91
|
-
正式枚举名是 `TechnicalFailureCode::{FunctionNotFound, FunctionRequestInvalid, CapacityRejected, DeadlineExceeded, DependencyUnavailable, ContractResultInvalid, InternalFailure, TransportFailure}` 与 `ExecutionCertainty::{NotExecuted, Executed, MayHaveExecuted}`。对二者做笛卡尔积 match,每个技术码在三种 certainty 下均须出现于具名 arm;可用 `|` 合并业务决策相同的 arm
|
|
92
|
+
正式枚举名是 `TechnicalFailureCode::{FunctionNotFound, FunctionRequestInvalid, CapacityRejected, DeadlineExceeded, DependencyUnavailable, ContractResultInvalid, InternalFailure, TransportFailure}` 与 `ExecutionCertainty::{NotExecuted, Executed, MayHaveExecuted}`。对二者做笛卡尔积 match,每个技术码在三种 certainty 下均须出现于具名 arm;可用 `|` 合并业务决策相同的 arm,但不能省略任一组合。每个结果必须落为 `ProfuseGwResponse::failure`,不能返回裸业务数据或 `Err`。
|
|
92
93
|
|
|
93
94
|
## 本地真实 I/O
|
|
94
95
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Profusegw 业务响应建模
|
|
2
|
+
|
|
3
|
+
## 唯一响应类型
|
|
4
|
+
|
|
5
|
+
每个 profusegw handler 必须直接返回框架类型:
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
saddle::ingress::ProfuseGwResponse<Data, BusinessCode>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
禁止返回 `Result<...>` 或以 `Err` 穿过业务边界;禁止自建 `{ success, data, resultCode, resultView }`、任意 JSON、Map 或空成功。框架拥有 JSON 字段、互斥分支与 HTTP framing。
|
|
12
|
+
|
|
13
|
+
成功必须是完整、有界、可序列化的业务数据:
|
|
14
|
+
|
|
15
|
+
```rust
|
|
16
|
+
ProfuseGwResponse::success(Output { found: true })
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
业务拒绝和依赖技术失败均使用失败分支:
|
|
20
|
+
|
|
21
|
+
```rust
|
|
22
|
+
ProfuseGwResponse::failure(
|
|
23
|
+
BusinessCode::BusinessRejected,
|
|
24
|
+
ResultView::new("No bound account").expect("static view is bounded"),
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`ResultView` 只用于展示,UTF-8 bytes 上限为 256;机器判断只看稳定业务码。不得把动态堆栈、依赖原文、任意用户输入或未设上限的格式化文本放入 view。
|
|
29
|
+
|
|
30
|
+
## 封闭业务码
|
|
31
|
+
|
|
32
|
+
为应用业务结果定义封闭 Rust enum,并穷尽实现稳定码:
|
|
33
|
+
|
|
34
|
+
```rust
|
|
35
|
+
enum BusinessCode {
|
|
36
|
+
BusinessRejected,
|
|
37
|
+
DependencyFailed,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl ProfuseGwResultCode for BusinessCode {
|
|
41
|
+
fn stable_code(&self) -> &'static str {
|
|
42
|
+
match self {
|
|
43
|
+
Self::BusinessRejected => "BUSINESS_REJECTED",
|
|
44
|
+
Self::DependencyFailed => "DEPENDENCY_FAILED",
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
机器码一旦进入外部合同即保持稳定。禁止 wildcard/default、动态字符串、把 `resultView` 当码、或为技术失败直接泄漏框架枚举名。每个 service 在 `application!` 中声明同一个封闭类型:
|
|
51
|
+
|
|
52
|
+
```rust
|
|
53
|
+
result_code BusinessCode;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 业务拒绝
|
|
57
|
+
|
|
58
|
+
`ExternalFunctionResult::Completed` 只说明依赖形成了合法 result,不保证当前业务成功。handler 必须根据业务 spec 显式决定:
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
if completed.count > 0 {
|
|
62
|
+
ProfuseGwResponse::success(Output { found: true })
|
|
63
|
+
} else {
|
|
64
|
+
ProfuseGwResponse::failure(
|
|
65
|
+
BusinessCode::BusinessRejected,
|
|
66
|
+
ResultView::new("No bound account").unwrap(),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
若业务 spec 没有规定拒绝码与展示文案,立即停止,不生成默认成功或空数据。
|
|
72
|
+
|
|
73
|
+
## 八类技术失败 × 三种确定性
|
|
74
|
+
|
|
75
|
+
对 `TechnicalFailureCode × ExecutionCertainty` 的 24 个组合做穷尽 match。必须逐项写出:
|
|
76
|
+
|
|
77
|
+
- `FunctionNotFound`
|
|
78
|
+
- `FunctionRequestInvalid`
|
|
79
|
+
- `CapacityRejected`
|
|
80
|
+
- `DeadlineExceeded`
|
|
81
|
+
- `DependencyUnavailable`
|
|
82
|
+
- `ContractResultInvalid`
|
|
83
|
+
- `InternalFailure`
|
|
84
|
+
- `TransportFailure`
|
|
85
|
+
|
|
86
|
+
以及:
|
|
87
|
+
|
|
88
|
+
- `NotExecuted`
|
|
89
|
+
- `Executed`
|
|
90
|
+
- `MayHaveExecuted`
|
|
91
|
+
|
|
92
|
+
完整形状如下;实际 view 与业务码必须来自业务 spec,不能照抄示例文案作为默认策略:
|
|
93
|
+
|
|
94
|
+
```rust
|
|
95
|
+
use saddle::profusecontract::{ExecutionCertainty::*, TechnicalFailureCode::*};
|
|
96
|
+
|
|
97
|
+
let view = match (failure.code(), failure.certainty()) {
|
|
98
|
+
(FunctionNotFound, NotExecuted) => "Function is unavailable",
|
|
99
|
+
(FunctionNotFound, Executed) => "Function execution failed",
|
|
100
|
+
(FunctionNotFound, MayHaveExecuted) => "Function outcome is unavailable",
|
|
101
|
+
(FunctionRequestInvalid, NotExecuted) => "Request was rejected",
|
|
102
|
+
(FunctionRequestInvalid, Executed) => "Request processing failed",
|
|
103
|
+
(FunctionRequestInvalid, MayHaveExecuted) => "Request outcome is unavailable",
|
|
104
|
+
(CapacityRejected, NotExecuted) => "Service is busy",
|
|
105
|
+
(CapacityRejected, Executed) => "Service capacity failed",
|
|
106
|
+
(CapacityRejected, MayHaveExecuted) => "Service outcome is unavailable",
|
|
107
|
+
(DeadlineExceeded, NotExecuted) => "Request expired",
|
|
108
|
+
(DeadlineExceeded, Executed) => "Response deadline expired",
|
|
109
|
+
(DeadlineExceeded, MayHaveExecuted) => "Timed out with unknown outcome",
|
|
110
|
+
(DependencyUnavailable, NotExecuted) => "Dependency is unavailable",
|
|
111
|
+
(DependencyUnavailable, Executed) => "Dependency execution failed",
|
|
112
|
+
(DependencyUnavailable, MayHaveExecuted) => "Dependency outcome is unavailable",
|
|
113
|
+
(ContractResultInvalid, NotExecuted) => "Contract result is invalid",
|
|
114
|
+
(ContractResultInvalid, Executed) => "Contract response is invalid",
|
|
115
|
+
(ContractResultInvalid, MayHaveExecuted) => "Contract outcome is invalid",
|
|
116
|
+
(InternalFailure, NotExecuted) => "Service failed before execution",
|
|
117
|
+
(InternalFailure, Executed) => "Service execution failed",
|
|
118
|
+
(InternalFailure, MayHaveExecuted) => "Service outcome is unavailable",
|
|
119
|
+
(TransportFailure, NotExecuted) => "Transport is unavailable",
|
|
120
|
+
(TransportFailure, Executed) => "Transport failed after execution",
|
|
121
|
+
(TransportFailure, MayHaveExecuted) => "Transport outcome is unavailable",
|
|
122
|
+
};
|
|
123
|
+
ProfuseGwResponse::failure(
|
|
124
|
+
BusinessCode::DependencyFailed,
|
|
125
|
+
ResultView::new(view).expect("all static views are shorter than 256 bytes"),
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
每个 arm 选择业务 spec 允许的稳定业务码和静态有界 view。不得 wildcard/default、自动重试、panic 转成功、统一空响应或忽略 certainty。即使多个 arm 使用同一业务码,也要显式保留全部组合;有副作用时 `MayHaveExecuted` 不得伪装为未执行。
|
|
130
|
+
|
|
131
|
+
新增框架失败码或 certainty 必须使旧代码编译/Gate 失败,而不是静默落入兜底。
|
|
132
|
+
|
|
133
|
+
## 反例
|
|
134
|
+
|
|
135
|
+
以下均为 STOP:
|
|
136
|
+
|
|
137
|
+
```rust
|
|
138
|
+
async fn handler(...) -> Result<Output, Error> { ... } // Rust Err 越界
|
|
139
|
+
async fn handler(...) -> serde_json::Value { ... } // 任意 JSON
|
|
140
|
+
struct MyResponse { success: bool, result_view: String } // 自建外壳、无界文案
|
|
141
|
+
match failure.code() { Known => ..., _ => ... } // 技术失败兜底
|
|
142
|
+
ProfuseGwResponse::success(Output::default()) // 业务拒绝被伪造成空成功
|
|
143
|
+
```
|
package/references/validation.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Saddle 0.3.0-alpha.
|
|
1
|
+
# Saddle 0.3.0-alpha.5 Skill 正式业务 Gate
|
|
2
2
|
|
|
3
3
|
## 依赖与锁
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ edition = "2024"
|
|
|
12
12
|
rust-version = "1.95"
|
|
13
13
|
|
|
14
14
|
[dependencies]
|
|
15
|
-
saddle-framework = "=0.3.0-alpha.
|
|
15
|
+
saddle-framework = "=0.3.0-alpha.5"
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
在允许访问正式 registry 的依赖准备阶段生成并提交 `Cargo.lock`。禁止 `path`、`git`、`[patch]`、source replacement 或宽松 Saddle 版本。
|
|
@@ -25,14 +25,16 @@ saddle-framework = "=0.3.0-alpha.4"
|
|
|
25
25
|
npx --no-install saddle-03-gate --manifest-path Cargo.toml
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
命令由已安装的 `@antprofuse/saddle-skill@0.3.0-alpha.
|
|
28
|
+
命令由已安装的 `@antprofuse/saddle-skill@0.3.0-alpha.5` 提供,不需要 Saddle 源码仓库。它会检查:
|
|
29
29
|
|
|
30
30
|
- facade 精确版本、禁止直接中间件依赖和 Cargo source 旁路;
|
|
31
|
-
- `Cargo.lock` 中 boundary、db、framework、macros、observability、runtime、service 七包统一精确映射 `0.3.0-alpha.
|
|
31
|
+
- `Cargo.lock` 中 boundary、db、framework、macros、observability、runtime、service 七包统一精确映射 `0.3.0-alpha.5`;
|
|
32
32
|
- 唯一 `contract_dir`、目录存在、禁止旧单文件 contract 声明;
|
|
33
33
|
- 目录根部 `saddle_external_function.proto` 与 Skill 权威模板逐字一致且仅出现一次,使用 option 的业务 proto 必须 direct import 根文件;
|
|
34
34
|
- 编译期递归扫描/import 闭包、option-only 索引、身份唯一、请求结果类型匹配,以及所有 function 均有实际 `uses`;
|
|
35
35
|
- 固定 `ProfuseGwContext`、应用声明、八类技术失败与三种 certainty 均被显式引用;
|
|
36
|
+
- handler 直接返回框架 `ProfuseGwResponse`,service 声明封闭 `result_code`,业务码实现稳定机器码,失败使用有界 `ResultView`;
|
|
37
|
+
- 拒绝 Rust `Result`/`Err`、自建 success 外壳、任意 JSON、无界展示文案、空成功及技术失败 wildcard/default;
|
|
36
38
|
- 唯一字面量 `deployment_app`、静态 `operation_type`,并拒绝业务代码读取或动态路由 `target.app/interfaceId`;
|
|
37
39
|
- 无 wildcard/default、第二 listener/Runtime、裸 gRPC/HTTP、动态函数或 raw invoke;
|
|
38
40
|
- Rust 1.95 下 `cargo check --locked --offline`。
|
|
@@ -30,8 +30,8 @@ if (/^\s*\[(patch|replace)\b/m.test(cargo) || /\b(path|git|branch|tag)\s*=/.test
|
|
|
30
30
|
stop("DEPENDENCY_SOURCE", "禁止 path/git/patch/replace 依赖旁路");
|
|
31
31
|
}
|
|
32
32
|
const facade = cargo.match(/^\s*saddle-framework\s*=\s*(.+)$/m);
|
|
33
|
-
if (!facade || !/^"=0\.3\.0-alpha\.
|
|
34
|
-
stop("SADDLE_VERSION", '必须直接且精确依赖 saddle-framework = "=0.3.0-alpha.
|
|
33
|
+
if (!facade || !/^"=0\.3\.0-alpha\.5"\s*(?:#.*)?$/.test(facade[1].trim())) {
|
|
34
|
+
stop("SADDLE_VERSION", '必须直接且精确依赖 saddle-framework = "=0.3.0-alpha.5"');
|
|
35
35
|
}
|
|
36
36
|
const internals = ["saddle-core","saddle-admission","saddle-boundary","saddle-db","saddle-macros","saddle-observability","saddle-runtime","saddle-service"];
|
|
37
37
|
for (const name of internals) {
|
|
@@ -44,8 +44,8 @@ if (!fs.existsSync(lockPath)) {
|
|
|
44
44
|
const publicClosure = ["saddle-boundary", "saddle-db", "saddle-framework", "saddle-macros", "saddle-observability", "saddle-runtime", "saddle-service"];
|
|
45
45
|
for (const name of publicClosure) {
|
|
46
46
|
const escaped = name.replaceAll("-", "\\-");
|
|
47
|
-
if (!new RegExp(`name = "${escaped}"\\nversion = "0\\.3\\.0-alpha\\.
|
|
48
|
-
stop("LOCK", `Cargo.lock 未绑定 ${name} 0.3.0-alpha.
|
|
47
|
+
if (!new RegExp(`name = "${escaped}"\\nversion = "0\\.3\\.0-alpha\\.5"`).test(lock)) {
|
|
48
|
+
stop("LOCK", `Cargo.lock 未绑定 ${name} 0.3.0-alpha.5`);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -63,6 +63,13 @@ visit(sourceRoot);
|
|
|
63
63
|
const source = files.map(p => fs.readFileSync(p, "utf8")).join("\n");
|
|
64
64
|
if (!source.includes("application!")) stop("APPLICATION", "缺少 Saddle application! 声明");
|
|
65
65
|
if (!source.includes("ProfuseGwContext")) stop("CONTEXT", "handler 必须使用固定 ProfuseGwContext");
|
|
66
|
+
if (!source.includes("ProfuseGwResponse")) stop("RESPONSE", "handler 必须直接返回框架 ProfuseGwResponse");
|
|
67
|
+
if (!source.includes("ProfuseGwResultCode")) stop("RESULT_CODE", "业务码必须实现 ProfuseGwResultCode");
|
|
68
|
+
if (!source.includes("ResultView::new")) stop("RESULT_VIEW", "失败响应必须使用 256-byte 有界 ResultView");
|
|
69
|
+
if (!/\bresult_code\s+[A-Za-z_][A-Za-z0-9_]*\s*;/.test(source)) stop("RESULT_CODE", "每个 service 必须声明封闭 result_code");
|
|
70
|
+
if (!/impl\s+ProfuseGwResultCode\s+for\s+[A-Za-z_][A-Za-z0-9_]*/.test(source)) stop("RESULT_CODE", "缺少业务码稳定机器码实现");
|
|
71
|
+
if (!source.includes("ProfuseGwResponse::success") && !source.includes("Response::success")) stop("RESPONSE", "缺少框架成功响应");
|
|
72
|
+
if (!source.includes("ProfuseGwResponse::failure") && !source.includes("Response::failure")) stop("RESPONSE", "缺少框架失败响应");
|
|
66
73
|
const deploymentApps = [...source.matchAll(/\bdeployment_app\s+"([^"]+)"\s*;/g)];
|
|
67
74
|
if (deploymentApps.length !== 1) stop("DEPLOYMENT_APP", "必须且只能声明一个字面量 deployment_app");
|
|
68
75
|
const operations = [...source.matchAll(/\boperation_type\s+"([^"]+)"\s*;/g)];
|
|
@@ -119,6 +126,11 @@ const forbidden = [
|
|
|
119
126
|
["RUNTIME", /\b(tokio::runtime|Runtime::new|spawn\s*\()/], ["RAW_RPC", /\b(tonic|Channel|ProfuseContractBoundary|raw_invoke|invoke_raw)\b/],
|
|
120
127
|
["DYNAMIC_FUNCTION", /\b(dynamic_function|call_dynamic|DynamicExternalFunction)\b/],
|
|
121
128
|
["DYNAMIC_INGRESS_IDENTITY", /\b(target\.app|target\.interfaceId|interface_id\s*\()/],
|
|
129
|
+
["HANDLER_RESULT", /async\s+fn\s+[A-Za-z_][A-Za-z0-9_]*[\s\S]{0,1000}->\s*(?:std::result::)?Result\s*</],
|
|
130
|
+
["ARBITRARY_JSON", /\bserde_json::(?:Value|json!)\b/],
|
|
131
|
+
["CUSTOM_RESPONSE", /struct\s+[A-Za-z_][A-Za-z0-9_]*\s*\{[^}]*\b(success|result_code|result_view)\s*:/s],
|
|
132
|
+
["UNBOUNDED_RESULT_VIEW", /ResultView::new\s*\(\s*(?:&?format!|String::|[^)]*\.to_string\s*\()/],
|
|
133
|
+
["EMPTY_SUCCESS", /(?:ProfuseGwResponse|Response)::success\s*\(\s*(?:\(\)|Default::default\s*\(\s*\)|[A-Za-z_][A-Za-z0-9_]*::default\s*\(\s*\))\s*\)/],
|
|
122
134
|
["UNSAFE", /\bunsafe\b/]
|
|
123
135
|
];
|
|
124
136
|
for (const [rule, pattern] of forbidden) if (pattern.test(source)) stop(rule, "命中禁止业务旁路");
|
|
@@ -133,4 +145,4 @@ try {
|
|
|
133
145
|
fs.rmSync(target, {recursive: true, force: true});
|
|
134
146
|
}
|
|
135
147
|
if (process.exitCode) process.exit(process.exitCode);
|
|
136
|
-
process.stdout.write("SADDLE_03_GATE PASS; SKILL=0.3.0-alpha.
|
|
148
|
+
process.stdout.write("SADDLE_03_GATE PASS; SKILL=0.3.0-alpha.5; RUST=0.3.0-alpha.5; FRAMEWORK_RESPONSE; CLOSED_CODES; RESULT_VIEW_256; FAILURE_8X3; LOCKED_OFFLINE\n");
|