@antprofuse/saddle-skill 0.2.0 → 0.3.0-alpha.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 +42 -61
- package/agents/openai.yaml +3 -3
- package/package.json +5 -15
- package/references/consumer-start.md +60 -0
- package/references/external-function-contracts.md +82 -0
- package/references/programming-model.md +84 -0
- package/references/validation.md +36 -0
- package/scripts/saddle-03-gate.js +84 -0
- package/assets/registry-consumer/Cargo.lock +0 -1814
- package/assets/registry-consumer/Cargo.toml +0 -20
- package/assets/registry-consumer/src/generated_artifact.rs +0 -858
- package/assets/registry-consumer/src/lib.rs +0 -12
- package/assets/registry-consumer/tests/provider_consumer.rs +0 -20
- package/package-files.txt +0 -25
- package/package-manifest.json +0 -112
- package/references/api-boundary.md +0 -71
- package/references/declaration-schema.md +0 -115
- package/references/identity-closure.json +0 -49
- package/references/machine-schema.md +0 -47
- package/references/migration.md +0 -24
- package/references/public-api.snapshot +0 -16
- package/scripts/parameterized-capacity-facts.py +0 -254
- package/scripts/sync-identity-closure.py +0 -124
- package/scripts/validate-contract.sh +0 -62
- package/scripts/validate-generated-preflight.sh +0 -120
- package/scripts/validate-generated-project.sh +0 -123
- package/scripts/validate-identity-closure.py +0 -138
- package/scripts/validate-machine-input.sh +0 -48
- package/scripts/validate-managed-objects-peak.py +0 -147
- package/scripts/validation-cache-key.py +0 -88
package/SKILL.md
CHANGED
|
@@ -1,73 +1,54 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: saddle-service-0-
|
|
3
|
-
description:
|
|
2
|
+
name: saddle-service-0-3
|
|
3
|
+
description: 使用正式 Saddle 0.3.0-alpha.1 开发或审查带 profusegw 固定入口和 profusecontract 强类型外部函数调用的 Rust 业务应用。用于根据纯业务 spec 定义共同 protobuf 契约、编写固定 ProfuseGwContext handler、穷举技术失败,或运行正式业务门禁。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Saddle 0.
|
|
6
|
+
# Saddle 0.3.0-alpha.1 业务研发契约
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
与 machine schema 的版本身份。开发候选必须精确匹配 manifest 中的 source commit;
|
|
10
|
-
正式使用必须匹配已发布 Rust 制品。任一身份不一致时停止生成,不用其他 workspace
|
|
11
|
-
path、设计目标或相邻版本替代。
|
|
8
|
+
业务 Agent 的直接输入只有业务 spec 与本 Skill。不要要求用户阅读 Saddle 仓库、审计报告、组件提交或内部实现。
|
|
12
9
|
|
|
13
|
-
##
|
|
10
|
+
## 必须按顺序执行
|
|
14
11
|
|
|
15
|
-
1. 阅读 [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
4. 交付前按 [machine schema](references/machine-schema.md) 生成并核对构建输入。
|
|
21
|
-
5. 以 `assets/registry-consumer` 为可编译基准生成私有
|
|
22
|
-
`GeneratedApplicationOwner`。application marker 必须公开一个 provider-generic、
|
|
23
|
-
consuming 的 `run<B: ApprovedExternalBundle>(bundle, config)`;函数体只能在私有
|
|
24
|
-
generated module 内移动一次 owner 到
|
|
25
|
-
`ProductionLauncher::verify(bundle)?.run(config, GeneratedOwner)`。不得生成 bundle、
|
|
26
|
-
credential、raw constructor,也不得公开 owner。
|
|
27
|
-
6. 运行 `scripts/validate-contract.sh <application.rs>` 与
|
|
28
|
-
`scripts/validate-generated-project.sh <project-root>`;核对 public API snapshot、
|
|
29
|
-
approved locks、package allowlist、同源 managed-object peak 与 machine
|
|
30
|
-
fingerprint,再运行正式 Rust package 提供的 Gate,不自行实现替代 Gate。生成工程
|
|
31
|
-
验证必须设置 `CARGO_NET_OFFLINE=true`,并通过已批准的 entry-local runner 创建
|
|
32
|
-
canonical `/tmp` 直属临时根;入口只接受其中独立的 `cargo-home`、`target` 与 `work`,
|
|
33
|
-
覆盖继承的 `CARGO_HOME`,在成功、STOP 和其他失败退出后删除整个临时根,并输出由
|
|
34
|
-
toolchain、target、features、lock、source 与 build contract 共同派生的 cache key。
|
|
12
|
+
1. 阅读 [消费者起手式](references/consumer-start.md),只从业务 spec 提取接口、业务类型、业务规则、业务结果和外部业务能力。
|
|
13
|
+
2. 每个外部能力先按 [外部函数协议](references/external-function-contracts.md) 定义双方共同的 `.proto`。冻结文件原始 bytes 与 SHA-256 后,才并行交给 Saddle 后端和 profusecontract 实现。
|
|
14
|
+
3. 按 [编程模型](references/programming-model.md) 声明应用、固定 profusegw operation、应用级强类型外部函数能力和唯一 handler。
|
|
15
|
+
4. 对八类技术失败与三种执行确定性逐项给出业务 spec 明确允许的处理。禁止 wildcard/default、自动重试、默认业务成功或空结果。
|
|
16
|
+
5. 按 [正式 Gate](references/validation.md) 生成 lock,并在交付前运行:
|
|
35
17
|
|
|
36
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npx --no-install saddle-03-gate --manifest-path Cargo.toml
|
|
20
|
+
```
|
|
37
21
|
|
|
38
|
-
|
|
39
|
-
assembly。
|
|
40
|
-
- generated module 和 owner 必须保持私有;业务声明只能命名 application marker,
|
|
41
|
-
不能命名、构造、Clone、提取或重放 owner/token/proof。
|
|
42
|
-
- Test/部署控制面从外部注入合法 `ApprovedExternalBundle` 与 `SaddleConfig`;Skill 只生成
|
|
43
|
-
可被该控制面调用的 business consumer,不生成或保存生产凭据与部署实例。
|
|
44
|
-
- route、Service、内部调用边、DB operation 与 transaction decision 必须全部来自
|
|
45
|
-
同一声明。
|
|
46
|
-
- query、write、transaction 只使用静态 SQL 与固定受管参数/结果 shape。
|
|
47
|
-
- transaction 仅允许一个 write step,显式 `commit` 或 `business_rollback`。
|
|
48
|
-
- handler 保持 async 源码体验,但不得出现 boxed/dyn Future 或业务自建 executor。
|
|
49
|
-
- generated artifact 只携 route/framing、DB work、具体静态 layout、每 route
|
|
50
|
-
commitment/managed-object/DB credit 与 build/artifact/route-set identity。
|
|
51
|
-
- capacity、deadline、task bytes、StartupPlan 与 deployment calibration 由 Gate/Runtime
|
|
52
|
-
从已验证资源产生;generated/业务不得填写、枚举、观察或依赖这些值。
|
|
53
|
-
- Service capacity、termination、continuation 与 runtime layout proof 必须由同一次
|
|
54
|
-
registry freeze 的整包进入 Facade 后在组件内部签发并消费。generated crate 不得实现
|
|
55
|
-
proof trait、声明 proof associated type、拆分 capacity leaf,或复制任何 identity。
|
|
56
|
-
- 私有 generated owner 只能把同次 freeze 的不可 `Clone` 整包移动进
|
|
57
|
-
`generated_bootstrap`,再把得到的 opaque bootstrap 作为 `seal.bind(bootstrap)` 的唯一
|
|
58
|
-
输入;业务/provider 不得观察、构造或传入 layout、size、route identity 或 proof。
|
|
22
|
+
6. Gate 或编译失败时停止,保留最小业务 consumer 复现;不得复制或修改 Saddle 中间件绕过。
|
|
59
23
|
|
|
60
|
-
##
|
|
24
|
+
## 正式版本身份
|
|
61
25
|
|
|
62
|
-
|
|
63
|
-
- 只有源码候选、没有身份匹配的已发布 Rust Facade,却要求按正式制品使用;
|
|
64
|
-
- 需要动态 SQL、多 step/嵌套事务、后台任务、第三方 I/O 或未交付能力;
|
|
65
|
-
- 需要 V1 `ServiceHandler`、`Statement`、普通 serde、`String`、`Vec`、raw sqlx、
|
|
66
|
-
第二 Runtime/task/dispatcher/assembly;
|
|
67
|
-
- signed production bundle、final calibration 或 release profile 尚未由正式制品冻结。
|
|
26
|
+
业务 crate 必须精确依赖:
|
|
68
27
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
28
|
+
```toml
|
|
29
|
+
[dependencies]
|
|
30
|
+
saddle-framework = "=0.3.0-alpha.1"
|
|
31
|
+
```
|
|
72
32
|
|
|
73
|
-
|
|
33
|
+
`saddle-framework` 是唯一业务 facade。业务不得直接依赖 `saddle-core`、`saddle-admission`、`saddle-boundary`、`saddle-db`、`saddle-macros`、`saddle-observability`、`saddle-runtime` 或 `saddle-service`。
|
|
34
|
+
|
|
35
|
+
## 固定边界
|
|
36
|
+
|
|
37
|
+
- Saddle 编译进业务应用,不是独立服务、进程或控制面。
|
|
38
|
+
- profusegw 唯一入口是 `POST /saddle/v1/ingress/profusegw/invoke`;不得手写 listener、HTTP handler 或第二路由。
|
|
39
|
+
- 所有 handler 使用 `saddle::ingress::ProfuseGwContext`;alpha.1 只读取只读、有界的 `user_id`,不得读取 Header、Cookie、原始 JSON 或任意上下文 Map。
|
|
40
|
+
- 外部函数由共同 `.proto` 的 `(business_unit, function)` 精确定位。中文 `function` 逐字保留,不翻译、不 alias、不 fallback、不动态选择。
|
|
41
|
+
- 业务只使用生成的应用级强类型 capability,不接触 channel、stub、裸 protobuf bytes、动态函数名或通用依赖容器。
|
|
42
|
+
- 不支持 contract 多版本路由、`Any`、开放 Map、任意 JSON、streaming、cancel 或框架自动业务重试。
|
|
43
|
+
- 不创建线程、第二 Runtime、后台 task、裸 DB/网络 I/O、日志 subscriber 或第二执行面。
|
|
44
|
+
|
|
45
|
+
## 立即停止
|
|
46
|
+
|
|
47
|
+
- 双方没有同一份已冻结 `.proto`,或 descriptor 摘要不同;
|
|
48
|
+
- method option 缺少逐字 `business_unit + function`;
|
|
49
|
+
- 请求、结果、字符串、bytes、集合或嵌套对象没有业务静态上限;
|
|
50
|
+
- 需要动态 function/descriptor、raw invoke、裸 gRPC、原始网络上下文或未声明外部 I/O;
|
|
51
|
+
- 八类技术失败或三种执行确定性未显式处理;
|
|
52
|
+
- Cargo 不是精确 alpha.1 facade、缺 lock,或正式 Gate 不通过。
|
|
53
|
+
|
|
54
|
+
停止时只交最小复现和期望业务表达,由 Saddle 修复公共能力缺口。
|
package/agents/openai.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name: "Saddle 0.
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "
|
|
2
|
+
display_name: "Saddle 0.3 业务应用"
|
|
3
|
+
short_description: "使用 Saddle 0.3 开发受控边界的 Rust 业务应用"
|
|
4
|
+
default_prompt: "请安装 Saddle 0.3.0-alpha.1 Skill(npm install --save-exact --ignore-scripts --no-audit --no-fund @antprofuse/saddle-skill@0.3.0-alpha.1),结合业务 spec 开始研发。"
|
package/package.json
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antprofuse/saddle-skill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0-alpha.1",
|
|
4
|
+
"description": "Saddle 0.3.0-alpha.1 中文 AI Coding 研发契约与业务门禁。",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
|
-
"files": [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"references",
|
|
10
|
-
"assets",
|
|
11
|
-
"scripts",
|
|
12
|
-
"package-files.txt",
|
|
13
|
-
"package-manifest.json"
|
|
14
|
-
],
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
"access": "public",
|
|
17
|
-
"registry": "https://registry.npmjs.org/"
|
|
18
|
-
}
|
|
6
|
+
"files": ["SKILL.md", "agents", "references", "scripts"],
|
|
7
|
+
"bin": {"saddle-03-gate": "scripts/saddle-03-gate.js"},
|
|
8
|
+
"publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"}
|
|
19
9
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Saddle 0.3 消费者起手式
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
从纯业务 spec 形成可供 Saddle 后端与 profusecontract 并行消费的同一份外部函数契约和可编译业务 consumer。
|
|
6
|
+
|
|
7
|
+
## 阶段 0:核对输入
|
|
8
|
+
|
|
9
|
+
只接受以下业务输入:接口与数据、业务规则、业务成功/拒绝/错误、业务状态,以及所需外部业务能力。若输入要求 HTTP/gRPC 细节、动态函数、框架重试、容量默认值或用户文案,先把这些技术决策从业务 spec 移出。
|
|
10
|
+
|
|
11
|
+
列出每个外部能力的:
|
|
12
|
+
|
|
13
|
+
- 业务用途;
|
|
14
|
+
- 具名 request/result;
|
|
15
|
+
- 字符串、bytes、集合与嵌套对象的业务静态上限;
|
|
16
|
+
- 是否有副作用;
|
|
17
|
+
- 八类技术失败与执行确定性下的显式业务决策。
|
|
18
|
+
|
|
19
|
+
任一项缺失时停止,不进入实现。
|
|
20
|
+
|
|
21
|
+
## 阶段 1:先冻结共同 `.proto`
|
|
22
|
+
|
|
23
|
+
按 [外部函数契约](external-function-contracts.md) 为每个能力定义 rpc 与 method option。`function` 必须保留业务契约中的中文原名,例如:
|
|
24
|
+
|
|
25
|
+
```protobuf
|
|
26
|
+
option (saddle.profusecontract.external_function) = {
|
|
27
|
+
business_unit: "puc"
|
|
28
|
+
function: "查询用户绑定户号"
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
冻结一个 canonical 文件,记录其原始 bytes SHA-256。Saddle 后端与 profusecontract 必须消费同一路径或同摘要副本;不得维护两份“等价”定义。
|
|
33
|
+
|
|
34
|
+
必须 import `saddle/profusecontract/external_function.proto` 并使用其 `external_function` method option;不得自建同名 extension。
|
|
35
|
+
|
|
36
|
+
## 阶段 2:同源并行交接
|
|
37
|
+
|
|
38
|
+
共同 `.proto` 冻结后才创建两个互不改写契约的工作项:
|
|
39
|
+
|
|
40
|
+
1. **Saddle 后端**:准备 operation、contract、允许函数、固定 `ProfuseGwContext` handler 和技术失败决策;只有阶段 3 身份门禁通过后才写真实 Rust 实现。
|
|
41
|
+
2. **profusecontract**:按同一 `(business_unit, function)` 实现 Java/SOFA 防腐与同 descriptor conformance。
|
|
42
|
+
|
|
43
|
+
两项交接必须携带相同的 proto bytes SHA-256。任何契约变化先合回 canonical `.proto`,重新冻结后同时通知两侧。
|
|
44
|
+
|
|
45
|
+
## 阶段 3:Rust 实现
|
|
46
|
+
|
|
47
|
+
业务 crate 精确使用 `saddle-framework = "=0.3.0-alpha.1"`,然后按 [最小编程模型](programming-model.md) 编写声明和 handler。不得直接依赖任何 Saddle 中间件 crate。
|
|
48
|
+
|
|
49
|
+
## 阶段 4:闭合前停止条件
|
|
50
|
+
|
|
51
|
+
以下任一情况立即 STOP:
|
|
52
|
+
|
|
53
|
+
- 两侧 proto bytes 或 `(business_unit, function)` 不同;
|
|
54
|
+
- 中文 `function` 被翻译、alias、fallback 或动态选择;
|
|
55
|
+
- handler 不使用唯一固定 `ProfuseGwContext`,或读取原始网络上下文;
|
|
56
|
+
- 技术失败码或执行确定性存在 catch-all/default;
|
|
57
|
+
- 需要裸 gRPC/channel/bytes、第二入口、动态 descriptor 或未声明外部 I/O;
|
|
58
|
+
- `saddle-framework` 不是精确 `=0.3.0-alpha.1`,或 [正式 Gate](validation.md) 失败。
|
|
59
|
+
|
|
60
|
+
停止产物只包含最小复现、缺失身份和期望业务表达,不包含中间件替代实现。
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# profusecontract 外部函数契约
|
|
2
|
+
|
|
3
|
+
## 目的
|
|
4
|
+
|
|
5
|
+
把业务应用需要的外部能力定义为 Saddle 后端与 profusecontract 的共同技术边界。先冻结契约,再分别实现业务编排和 SOFA RPC 防腐。
|
|
6
|
+
|
|
7
|
+
## 唯一身份
|
|
8
|
+
|
|
9
|
+
外部函数只由以下二元组定位:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
(businessUnit, function)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `businessUnit` 是业务单元,例如 `puc`。
|
|
16
|
+
- `function` 是契约原始函数名,允许中文,例如 `查询用户绑定户号`。
|
|
17
|
+
- protobuf method、Rust 方法和 Java 方法使用各语言合法的本地标识符;它们不是跨系统身份。
|
|
18
|
+
- 0.3.0 不定义 contract version、多版本路由、翻译表、alias 或 fallback。
|
|
19
|
+
|
|
20
|
+
## 定义步骤
|
|
21
|
+
|
|
22
|
+
1. 从业务 spec 识别外部业务能力,不复制 SOFA service、method 或传输模型。
|
|
23
|
+
2. 为每个能力定义具名 protobuf request 和 result。
|
|
24
|
+
3. 使用 Saddle 提供的 `google.protobuf.MethodOptions` extension,把 rpc 方法绑定到 `businessUnit + function`。
|
|
25
|
+
4. 把同一份 `.proto` 交给 Saddle 业务 Agent 和 profusecontract Agent。
|
|
26
|
+
5. Saddle 侧显式引用契约并登记允许调用的函数;profusecontract 侧实现对应函数和防腐映射。
|
|
27
|
+
6. 双方以同一 descriptor 运行 conformance fixture 后再联调。
|
|
28
|
+
|
|
29
|
+
第 4 步是并行研发的起点,不是第 1 步。交接时至少记录 `.proto` 相对路径、原始 bytes SHA-256、package、service、rpc、`business_unit` 和中文 `function`。任一方改动契约都必须回到共同冻结步骤,不能在各自分支继续演化。
|
|
30
|
+
|
|
31
|
+
## 模板
|
|
32
|
+
|
|
33
|
+
```protobuf
|
|
34
|
+
syntax = "proto3";
|
|
35
|
+
|
|
36
|
+
package puc;
|
|
37
|
+
|
|
38
|
+
import "saddle/profusecontract/external_function.proto";
|
|
39
|
+
|
|
40
|
+
service PucExternalFunctions {
|
|
41
|
+
rpc QueryBoundAccounts(
|
|
42
|
+
QueryBoundAccountsRequest
|
|
43
|
+
) returns (
|
|
44
|
+
QueryBoundAccountsResult
|
|
45
|
+
) {
|
|
46
|
+
option (saddle.profusecontract.external_function) = {
|
|
47
|
+
business_unit: "puc"
|
|
48
|
+
function: "查询用户绑定户号"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
message QueryBoundAccountsRequest {
|
|
54
|
+
string user_id = 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
message QueryBoundAccountsResult {
|
|
58
|
+
repeated BoundAccount accounts = 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
message BoundAccount {
|
|
62
|
+
string account_id = 1;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
这里的跨系统正式身份是 `("puc", "查询用户绑定户号")`。`QueryBoundAccounts`、未来生成的 Rust 方法名和 Java 方法名都只是本地代码标识符,不能替换、翻译或推导中文 `function`。
|
|
67
|
+
|
|
68
|
+
该 import 与 method option 属于 Saddle `0.3.0-alpha.1` 固定协议。不得自行创建同名 extension,也不得复制为另一份协议。
|
|
69
|
+
|
|
70
|
+
实际线上网络仍只调用 Saddle 固定 `ProfuseContractBoundary/Invoke` unary。契约中的 service/rpc 用于绑定身份、request 和 result,不要求 profusecontract 暴露同名网络方法。
|
|
71
|
+
|
|
72
|
+
## 数据约束
|
|
73
|
+
|
|
74
|
+
- request/result 必须具名、封闭且可有界。
|
|
75
|
+
- 禁止 `google.protobuf.Any`、开放 Map、任意 JSON 和承载业务对象的裸 bytes。
|
|
76
|
+
- 字符串、bytes、集合和嵌套对象必须由业务实现明确静态上限;Saddle 验证,不替业务决定数值。
|
|
77
|
+
- result 表达函数契约业务结果;框架 `TechnicalFailure` 不进入 result message。
|
|
78
|
+
- `.proto` 不包含 SOFA service/method、外部 Java 类型、重试、降级、用户文案或容量默认值。
|
|
79
|
+
|
|
80
|
+
## 变更规则
|
|
81
|
+
|
|
82
|
+
先修改共同 `.proto` 并重新通过双方 conformance,再修改两侧实现。禁止两侧各自维护近似定义后人工对齐;0.3.0 遇到不兼容变化时停止并共同升级,不在运行时并存或猜测版本。
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Saddle 0.3 最小编程模型
|
|
2
|
+
|
|
3
|
+
## 应用显式声明
|
|
4
|
+
|
|
5
|
+
业务应用使用 `saddle-framework = "=0.3.0-alpha.1"` 的公开编程面:
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
saddle::application! {
|
|
9
|
+
schema "saddle-application/2";
|
|
10
|
+
application PucBackend;
|
|
11
|
+
|
|
12
|
+
profusecontract {
|
|
13
|
+
contract "contracts/puc-external-functions.proto";
|
|
14
|
+
capability PucProfuseContract;
|
|
15
|
+
functions {
|
|
16
|
+
QueryBoundAccounts => query_bound_accounts {
|
|
17
|
+
business_unit "puc";
|
|
18
|
+
function "查询用户绑定户号";
|
|
19
|
+
} (
|
|
20
|
+
QueryBoundAccountsRequest
|
|
21
|
+
) -> QueryBoundAccountsResult;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
service QueryMyBoundAccounts {
|
|
26
|
+
ingress profusegw;
|
|
27
|
+
operation_type "alipay.profuse.industry.puc.queryMyBoundAccounts";
|
|
28
|
+
request QueryMyBoundAccountsRequest;
|
|
29
|
+
response QueryMyBoundAccountsResult;
|
|
30
|
+
handler query_my_bound_accounts;
|
|
31
|
+
uses QueryBoundAccounts;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
这是业务实现显式写出的静态事实。Saddle 不从 spec 自动选择 contract、function 或调用边,也不允许运行时动态修改。
|
|
37
|
+
|
|
38
|
+
## Handler
|
|
39
|
+
|
|
40
|
+
所有 profusegw handler 使用同一固定签名形状:
|
|
41
|
+
|
|
42
|
+
```rust
|
|
43
|
+
async fn query_my_bound_accounts(
|
|
44
|
+
request: QueryMyBoundAccountsRequest,
|
|
45
|
+
context: saddle::ingress::ProfuseGwContext,
|
|
46
|
+
contract: PucProfuseContract,
|
|
47
|
+
) -> QueryMyBoundAccountsResult;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `request`:纯业务接口输入。
|
|
51
|
+
- `context`:固定 profusegw 框架上下文;所有接口共用同一类型,0.3.0 只有 `user_id`。
|
|
52
|
+
- `contract`:应用级强类型 profusecontract 调用能力,只包含应用显式登记的函数;不是通用依赖注入容器。
|
|
53
|
+
|
|
54
|
+
所有 profusegw operation 必须使用同一个固定 `ProfuseGwContext`。业务只能读取其只读 `user_id`;禁止用自建 context、Header/JSON Map 或通用依赖容器补位。
|
|
55
|
+
|
|
56
|
+
生成的强类型 async 调用必须等待结果,并分别处理完成结果与技术失败:
|
|
57
|
+
|
|
58
|
+
```rust
|
|
59
|
+
match contract.query_bound_accounts(input).await {
|
|
60
|
+
ExternalFunctionResult::Completed(result) => handle_result(result),
|
|
61
|
+
ExternalFunctionResult::TechnicalFailure(failure) => match failure.code() {
|
|
62
|
+
// 穷举固定技术失败,不允许 wildcard/default。
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`Completed` 只表示函数形成确定的契约 result,不等于函数业务成功。`TechnicalFailure` 不携带业务 result,并带 `NOT_EXECUTED`、`EXECUTED` 或 `MAY_HAVE_EXECUTED` 执行确定性。
|
|
68
|
+
|
|
69
|
+
在实现前先为以下八项各写一条显式业务决策;正式 Rust 枚举名必须从身份匹配的 API 读取,不得根据本表猜模块或类型:
|
|
70
|
+
|
|
71
|
+
| 技术失败码 | 必须显式决定 |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `FUNCTION_NOT_FOUND` | 停止、传播或映射到业务已定义结果 |
|
|
74
|
+
| `FUNCTION_REQUEST_INVALID` | 停止、传播或映射到业务已定义结果 |
|
|
75
|
+
| `CAPACITY_REJECTED` | 停止、传播或执行业务已定义且无自动重试的路径 |
|
|
76
|
+
| `DEADLINE_EXCEEDED` | 停止、传播或执行业务已定义且无自动重试的路径 |
|
|
77
|
+
| `DEPENDENCY_UNAVAILABLE` | 停止、传播或执行业务已定义且无自动重试的路径 |
|
|
78
|
+
| `CONTRACT_RESULT_INVALID` | 停止或传播,不得伪造空结果 |
|
|
79
|
+
| `INTERNAL_FAILURE` | 停止或传播,不得伪造业务结果 |
|
|
80
|
+
| `TRANSPORT_FAILURE` | 按执行确定性停止或传播,不得自动重试 |
|
|
81
|
+
|
|
82
|
+
每个分支还必须显式考虑正式 API 允许的执行确定性。不得使用 `_`、`default`、`UNKNOWN` 或 catch-all;新增技术码必须让旧消费者编译或 Gate 失败,而不是静默进入通用失败。
|
|
83
|
+
|
|
84
|
+
正式枚举名是 `TechnicalFailureCode::{FunctionNotFound, FunctionRequestInvalid, CapacityRejected, DeadlineExceeded, DependencyUnavailable, ContractResultInvalid, InternalFailure, TransportFailure}` 与 `ExecutionCertainty::{NotExecuted, Executed, MayHaveExecuted}`。对二者做笛卡尔积 match,每个技术码在三种 certainty 下均须出现于具名 arm;可用 `|` 合并业务决策相同的 arm,但不能省略任一组合。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Saddle 0.3.0-alpha.1 正式业务 Gate
|
|
2
|
+
|
|
3
|
+
## 依赖与锁
|
|
4
|
+
|
|
5
|
+
业务 crate 只通过 facade 使用 Saddle:
|
|
6
|
+
|
|
7
|
+
```toml
|
|
8
|
+
[package]
|
|
9
|
+
name = "my-saddle-app"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
edition = "2024"
|
|
12
|
+
rust-version = "1.85"
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
saddle-framework = "=0.3.0-alpha.1"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
在允许访问正式 registry 的依赖准备阶段生成并提交 `Cargo.lock`。禁止 `path`、`git`、`[patch]`、source replacement 或宽松 Saddle 版本。
|
|
19
|
+
|
|
20
|
+
## 唯一命令
|
|
21
|
+
|
|
22
|
+
在业务 crate 根目录执行:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx --no-install saddle-03-gate --manifest-path Cargo.toml
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
命令由已安装的 `@antprofuse/saddle-skill@0.3.0-alpha.1` 提供,不需要 Saddle 源码仓库。它会检查:
|
|
29
|
+
|
|
30
|
+
- facade 精确版本、禁止直接中间件依赖和 Cargo source 旁路;
|
|
31
|
+
- `Cargo.lock` 同版映射;
|
|
32
|
+
- 固定 `ProfuseGwContext`、应用声明、八类技术失败与三种 certainty 均被显式引用;
|
|
33
|
+
- 无 wildcard/default、第二 listener/Runtime、裸 gRPC/HTTP、动态函数或 raw invoke;
|
|
34
|
+
- Rust 1.85 下 `cargo check --locked --offline`。
|
|
35
|
+
|
|
36
|
+
Gate 使用临时 target 并在成功、失败和信号退出时清理。任何失败均为 STOP;不得加跳过参数或改 Gate 脚本。
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const cp = require("child_process");
|
|
8
|
+
|
|
9
|
+
function stop(rule, detail) {
|
|
10
|
+
process.stderr.write(`SADDLE_03_GATE ${rule} ${detail}\n`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args.length !== 2 || args[0] !== "--manifest-path") {
|
|
16
|
+
process.stderr.write("usage: saddle-03-gate --manifest-path Cargo.toml\n");
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
const manifest = path.resolve(args[1]);
|
|
20
|
+
if (!fs.existsSync(manifest)) {
|
|
21
|
+
stop("MANIFEST", `${manifest}: 文件不存在`);
|
|
22
|
+
process.exit(process.exitCode);
|
|
23
|
+
}
|
|
24
|
+
const root = path.dirname(manifest);
|
|
25
|
+
const cargo = fs.readFileSync(manifest, "utf8");
|
|
26
|
+
const lockPath = path.join(root, "Cargo.lock");
|
|
27
|
+
|
|
28
|
+
if (/^\s*\[(patch|replace)\b/m.test(cargo) || /\b(path|git|branch|tag)\s*=/.test(cargo)) {
|
|
29
|
+
stop("DEPENDENCY_SOURCE", "禁止 path/git/patch/replace 依赖旁路");
|
|
30
|
+
}
|
|
31
|
+
const facade = cargo.match(/^\s*saddle-framework\s*=\s*(.+)$/m);
|
|
32
|
+
if (!facade || !/^"=0\.3\.0-alpha\.1"\s*(?:#.*)?$/.test(facade[1].trim())) {
|
|
33
|
+
stop("SADDLE_VERSION", '必须直接且精确依赖 saddle-framework = "=0.3.0-alpha.1"');
|
|
34
|
+
}
|
|
35
|
+
const internals = ["saddle-core","saddle-admission","saddle-boundary","saddle-db","saddle-macros","saddle-observability","saddle-runtime","saddle-service"];
|
|
36
|
+
for (const name of internals) {
|
|
37
|
+
if (new RegExp(`^\\s*${name}\\s*=`, "m").test(cargo)) stop("FACADE", `禁止直接依赖 ${name}`);
|
|
38
|
+
}
|
|
39
|
+
if (!fs.existsSync(lockPath)) {
|
|
40
|
+
stop("LOCK", "缺少 Cargo.lock");
|
|
41
|
+
} else {
|
|
42
|
+
const lock = fs.readFileSync(lockPath, "utf8");
|
|
43
|
+
if (!/name = "saddle-framework"\nversion = "0\.3\.0-alpha\.1"/.test(lock)) {
|
|
44
|
+
stop("LOCK", "Cargo.lock 未绑定 saddle-framework 0.3.0-alpha.1");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sourceRoot = path.join(root, "src");
|
|
49
|
+
const files = [];
|
|
50
|
+
function visit(dir) {
|
|
51
|
+
if (!fs.existsSync(dir)) return;
|
|
52
|
+
for (const ent of fs.readdirSync(dir, {withFileTypes: true})) {
|
|
53
|
+
const p = path.join(dir, ent.name);
|
|
54
|
+
if (ent.isDirectory()) visit(p); else if (ent.isFile() && p.endsWith(".rs")) files.push(p);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
visit(sourceRoot);
|
|
58
|
+
const source = files.map(p => fs.readFileSync(p, "utf8")).join("\n");
|
|
59
|
+
if (!source.includes("application!")) stop("APPLICATION", "缺少 Saddle application! 声明");
|
|
60
|
+
if (!source.includes("ProfuseGwContext")) stop("CONTEXT", "handler 必须使用固定 ProfuseGwContext");
|
|
61
|
+
for (const code of ["FunctionNotFound","FunctionRequestInvalid","CapacityRejected","DeadlineExceeded","DependencyUnavailable","ContractResultInvalid","InternalFailure","TransportFailure"]) {
|
|
62
|
+
if (!source.includes(code)) stop("TECHNICAL_FAILURE", `缺少显式分支 ${code}`);
|
|
63
|
+
}
|
|
64
|
+
for (const certainty of ["NotExecuted","Executed","MayHaveExecuted"]) {
|
|
65
|
+
if (!source.includes(certainty)) stop("EXECUTION_CERTAINTY", `缺少显式分支 ${certainty}`);
|
|
66
|
+
}
|
|
67
|
+
const forbidden = [
|
|
68
|
+
["WILDCARD", /_\s*=>/], ["LISTENER", /\b(TcpListener|axum|hyper|actix_web|warp)\b/],
|
|
69
|
+
["RUNTIME", /\b(tokio::runtime|Runtime::new|spawn\s*\()/], ["RAW_RPC", /\b(tonic|Channel|ProfuseContractBoundary|raw_invoke|invoke_raw)\b/],
|
|
70
|
+
["DYNAMIC_FUNCTION", /\b(dynamic_function|call_dynamic|DynamicExternalFunction)\b/], ["UNSAFE", /\bunsafe\b/]
|
|
71
|
+
];
|
|
72
|
+
for (const [rule, pattern] of forbidden) if (pattern.test(source)) stop(rule, "命中禁止业务旁路");
|
|
73
|
+
|
|
74
|
+
if (process.exitCode) process.exit(process.exitCode);
|
|
75
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "saddle-03-gate-"));
|
|
76
|
+
try {
|
|
77
|
+
const env = {...process.env, CARGO_TARGET_DIR: target, CARGO_NET_OFFLINE: "true"};
|
|
78
|
+
const result = cp.spawnSync("cargo", ["+1.85.0", "check", "--manifest-path", manifest, "--locked", "--offline"], {env, stdio: "inherit"});
|
|
79
|
+
if (result.error || result.status !== 0) stop("COMPILER", "Rust 1.85 locked/offline 编译失败");
|
|
80
|
+
} finally {
|
|
81
|
+
fs.rmSync(target, {recursive: true, force: true});
|
|
82
|
+
}
|
|
83
|
+
if (process.exitCode) process.exit(process.exitCode);
|
|
84
|
+
process.stdout.write("SADDLE_03_GATE PASS; VERSION=0.3.0-alpha.1; LOCKED_OFFLINE; BUSINESS_BOUNDARIES=CLOSED\n");
|