@antprofuse/saddle-skill 0.3.0-alpha.1 → 0.3.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -1,16 +1,16 @@
1
1
  ---
2
2
  name: saddle-service-0-3
3
- description: 使用正式 Saddle 0.3.0-alpha.1 开发或审查带 profusegw 固定入口和 profusecontract 强类型外部函数调用的 Rust 业务应用。用于根据纯业务 spec 定义共同 protobuf 契约、编写固定 ProfuseGwContext handler、穷举技术失败,或运行正式业务门禁。
3
+ description: 使用正式 Saddle 0.3.0-alpha.3 开发或审查带 profusegw 固定入口和 profusecontract 强类型外部函数调用的 Rust 业务应用。用于根据纯业务 spec 定义共同 protobuf 契约、编写固定 ProfuseGwContext handler、穷举技术失败,或运行正式业务门禁。
4
4
  ---
5
5
 
6
- # Saddle 0.3.0-alpha.1 业务研发契约
6
+ # Saddle 0.3.0-alpha.3 业务研发契约
7
7
 
8
8
  业务 Agent 的直接输入只有业务 spec 与本 Skill。不要要求用户阅读 Saddle 仓库、审计报告、组件提交或内部实现。
9
9
 
10
10
  ## 必须按顺序执行
11
11
 
12
12
  1. 阅读 [消费者起手式](references/consumer-start.md),只从业务 spec 提取接口、业务类型、业务规则、业务结果和外部业务能力。
13
- 2. 每个外部能力先按 [外部函数协议](references/external-function-contracts.md) 定义双方共同的 `.proto`。冻结文件原始 bytes SHA-256 后,才并行交给 Saddle 后端和 profusecontract 实现。
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
15
  4. 对八类技术失败与三种执行确定性逐项给出业务 spec 明确允许的处理。禁止 wildcard/default、自动重试、默认业务成功或空结果。
16
16
  5. 按 [正式 Gate](references/validation.md) 生成 lock,并在交付前运行:
@@ -27,7 +27,7 @@ description: 使用正式 Saddle 0.3.0-alpha.1 开发或审查带 profusegw 固
27
27
 
28
28
  ```toml
29
29
  [dependencies]
30
- saddle-framework = "=0.3.0-alpha.1"
30
+ saddle-framework = "=0.3.0-alpha.3"
31
31
  ```
32
32
 
33
33
  `saddle-framework` 是唯一业务 facade。业务不得直接依赖 `saddle-core`、`saddle-admission`、`saddle-boundary`、`saddle-db`、`saddle-macros`、`saddle-observability`、`saddle-runtime` 或 `saddle-service`。
@@ -36,19 +36,24 @@ saddle-framework = "=0.3.0-alpha.1"
36
36
 
37
37
  - Saddle 编译进业务应用,不是独立服务、进程或控制面。
38
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、不动态选择。
39
+ - 所有 handler 使用 `saddle::ingress::ProfuseGwContext`;只读取正式 API 提供的只读、有界字段,不得读取 Header、Cookie、原始 JSON 或任意上下文 Map。
40
+ - 应用只配置一个 `contract_dir`。Saddle 在编译期确定性递归扫描其中的 `.proto`、闭合 import,并按 canonical 相对路径形成身份;不得列 proto 文件清单、维护聚合 proto,或在运行时扫描目录。
41
+ - 将 [固定协议模板](assets/saddle_external_function.proto) 原样写为 `${contract_dir}/saddle_external_function.proto`。不得改名、改内容、增加版本副本或另建 protocol 目录。
42
+ - 具体外部函数只写在业务 proto,并使用 `import "saddle_external_function.proto";`。profusecontract 直接消费整个 `contract_dir`,无需安装 Saddle。
43
+ - 只有带正式 `external_function` method option 的 RPC 进入外部函数索引;普通 RPC 与纯消息 proto 可以存在于同一目录,不得被误判为外部函数。
44
+ - 外部函数由共同 `.proto` option 的 `(puc, function)` 精确定位;应用 DSL 的 `business_unit` 必须逐字等于 option 的 `puc`。中文 `function` 逐字保留,不翻译、不 alias、不 fallback、不动态选择。
45
+ - `functions` 只声明应用实际调用的外部函数,每项必须被至少一个 service 的 `uses` 引用;不得登记未使用能力。
41
46
  - 业务只使用生成的应用级强类型 capability,不接触 channel、stub、裸 protobuf bytes、动态函数名或通用依赖容器。
42
47
  - 不支持 contract 多版本路由、`Any`、开放 Map、任意 JSON、streaming、cancel 或框架自动业务重试。
43
48
  - 不创建线程、第二 Runtime、后台 task、裸 DB/网络 I/O、日志 subscriber 或第二执行面。
44
49
 
45
50
  ## 立即停止
46
51
 
47
- - 双方没有同一份已冻结 `.proto`,或 descriptor 摘要不同;
48
- - method option 缺少逐字 `business_unit + function`;
52
+ - 双方没有同一份已冻结契约目录,或 descriptor 摘要不同;
53
+ - method option 缺少逐字 `puc + function`,或 DSL `business_unit` 与 `puc` 不同;
49
54
  - 请求、结果、字符串、bytes、集合或嵌套对象没有业务静态上限;
50
55
  - 需要动态 function/descriptor、raw invoke、裸 gRPC、原始网络上下文或未声明外部 I/O;
51
56
  - 八类技术失败或三种执行确定性未显式处理;
52
- - Cargo 不是精确 alpha.1 facade、缺 lock,或正式 Gate 不通过。
57
+ - Cargo 不是精确 alpha.3 facade、缺 lock,或正式 Gate 不通过。
53
58
 
54
59
  停止时只交最小复现和期望业务表达,由 Saddle 修复公共能力缺口。
@@ -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.1 Skill(npm install --save-exact --ignore-scripts --no-audit --no-fund @antprofuse/saddle-skill@0.3.0-alpha.1),结合业务 spec 开始研发。"
4
+ default_prompt: "请安装 Saddle 0.3.0-alpha.3 Skill(npm install --save-exact --ignore-scripts --no-audit --no-fund @antprofuse/saddle-skill@0.3.0-alpha.3),结合业务 spec 开始研发。"
@@ -0,0 +1,26 @@
1
+ syntax = "proto3";
2
+
3
+ package puc;
4
+
5
+ import "saddle_external_function.proto";
6
+
7
+ service PucExternalFunctions {
8
+ rpc QueryBoundAccounts(QueryBoundAccountsRequest) returns (QueryBoundAccountsResult) {
9
+ option (saddle.profusecontract.external_function) = {
10
+ puc: "puc"
11
+ function: "查询用户绑定户号"
12
+ };
13
+ }
14
+ }
15
+
16
+ message QueryBoundAccountsRequest {
17
+ string user_id = 1;
18
+ }
19
+
20
+ message QueryBoundAccountsResult {
21
+ repeated BoundAccount accounts = 1;
22
+ }
23
+
24
+ message BoundAccount {
25
+ string account_id = 1;
26
+ }
@@ -0,0 +1,13 @@
1
+ syntax = "proto3";
2
+ package saddle.profusecontract;
3
+
4
+ import "google/protobuf/descriptor.proto";
5
+
6
+ message ExternalFunction {
7
+ string puc = 1;
8
+ string function = 2;
9
+ }
10
+
11
+ extend google.protobuf.MethodOptions {
12
+ ExternalFunction external_function = 50001;
13
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@antprofuse/saddle-skill",
3
- "version": "0.3.0-alpha.1",
4
- "description": "Saddle 0.3.0-alpha.1 中文 AI Coding 研发契约与业务门禁。",
3
+ "version": "0.3.0-alpha.3",
4
+ "description": "Saddle 0.3.0-alpha.3 中文 AI Coding 研发契约与业务门禁。",
5
5
  "license": "MIT OR Apache-2.0",
6
- "files": ["SKILL.md", "agents", "references", "scripts"],
6
+ "files": ["SKILL.md", "agents", "references", "scripts", "assets"],
7
7
  "bin": {"saddle-03-gate": "scripts/saddle-03-gate.js"},
8
8
  "publishConfig": {"access": "public", "registry": "https://registry.npmjs.org/"}
9
9
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 目标
4
4
 
5
- 从纯业务 spec 形成可供 Saddle 后端与 profusecontract 并行消费的同一份外部函数契约和可编译业务 consumer。
5
+ 从纯业务 spec 形成可供 Saddle 后端与 profusecontract 并行消费的同一棵外部函数契约目录和可编译业务 consumer。
6
6
 
7
7
  ## 阶段 0:核对输入
8
8
 
@@ -18,43 +18,48 @@
18
18
 
19
19
  任一项缺失时停止,不进入实现。
20
20
 
21
- ## 阶段 1:先冻结共同 `.proto`
21
+ ## 阶段 1:先冻结共同契约目录
22
22
 
23
- [外部函数契约](external-function-contracts.md) 为每个能力定义 rpc 与 method option。`function` 必须保留业务契约中的中文原名,例如:
23
+ 建立唯一 `contract_dir`。先把 Skill 的固定模板 `../assets/saddle_external_function.proto` 原样写到目录根部,目标路径必须是 `${contract_dir}/saddle_external_function.proto`;再按 [外部函数契约](external-function-contracts.md) 为每个能力定义业务 rpc 与 method option。`function` 必须保留业务契约中的中文原名,例如:
24
24
 
25
25
  ```protobuf
26
26
  option (saddle.profusecontract.external_function) = {
27
- business_unit: "puc"
27
+ puc: "puc"
28
28
  function: "查询用户绑定户号"
29
29
  };
30
30
  ```
31
31
 
32
- 冻结一个 canonical 文件,记录其原始 bytes SHA-256。Saddle 后端与 profusecontract 必须消费同一路径或同摘要副本;不得维护两份“等价”定义。
32
+ 冻结同一目录树。Saddle 后端与 profusecontract 必须消费同源目录或同摘要副本;不得维护两份“等价”定义,也不得另建 proto 聚合文件或手工文件清单。
33
33
 
34
- 必须 import `saddle/profusecontract/external_function.proto` 并使用其 `external_function` method option;不得自建同名 extension。
34
+ Saddle 在编译期从 `contract_dir` 确定性递归收集 `.proto`,闭合 import,并按 canonical 相对路径排序。目录扫描不是运行时发现机制,业务不得据此动态选择函数。
35
+
36
+ 每个使用外部函数 option 的业务 proto 必须 `import "saddle_external_function.proto";` 并使用其中的 `external_function` method option;不得自建同名 extension。可直接参考 `../assets/example-contracts/puc_external_functions.proto`。
35
37
 
36
38
  ## 阶段 2:同源并行交接
37
39
 
38
40
  共同 `.proto` 冻结后才创建两个互不改写契约的工作项:
39
41
 
40
- 1. **Saddle 后端**:准备 operation、contract、允许函数、固定 `ProfuseGwContext` handler 和技术失败决策;只有阶段 3 身份门禁通过后才写真实 Rust 实现。
41
- 2. **profusecontract**:按同一 `(business_unit, function)` 实现 Java/SOFA 防腐与同 descriptor conformance。
42
+ 1. **Saddle 后端**:准备 operation、唯一 `contract_dir`、实际使用的函数、固定 `ProfuseGwContext` handler 和技术失败决策;只有阶段 3 身份门禁通过后才写真实 Rust 实现。
43
+ 2. **profusecontract**:直接消费整个 `contract_dir`,按同一 `(puc, function)` 实现 Java/SOFA 防腐与同 descriptor conformance;不要求安装 Saddle
42
44
 
43
- 两项交接必须携带相同的 proto bytes SHA-256。任何契约变化先合回 canonical `.proto`,重新冻结后同时通知两侧。
45
+ 两项交接必须携带相同的契约树身份。任何契约变化先合回共同目录,重新冻结后同时通知两侧。
44
46
 
45
47
  ## 阶段 3:Rust 实现
46
48
 
47
- 业务 crate 精确使用 `saddle-framework = "=0.3.0-alpha.1"`,然后按 [最小编程模型](programming-model.md) 编写声明和 handler。不得直接依赖任何 Saddle 中间件 crate。
49
+ 业务 crate 精确使用 `saddle-framework = "=0.3.0-alpha.3"`,然后按 [最小编程模型](programming-model.md) 编写声明和 handler。不得直接依赖任何 Saddle 中间件 crate。
48
50
 
49
51
  ## 阶段 4:闭合前停止条件
50
52
 
51
53
  以下任一情况立即 STOP:
52
54
 
53
- - 两侧 proto bytes 或 `(business_unit, function)` 不同;
55
+ - 两侧契约树或 `(puc, function)` 不同;
56
+ - 应用声明单个 proto 路径、proto 文件清单、聚合 proto,或把递归扫描当成运行时发现;
57
+ - 目录根缺少固定 `saddle_external_function.proto`,模板 bytes 被修改,业务 proto 未按固定文件名 import,或出现第二份协议定义;
58
+ - `functions` 声明了没有被 service `uses` 的能力;
54
59
  - 中文 `function` 被翻译、alias、fallback 或动态选择;
55
60
  - handler 不使用唯一固定 `ProfuseGwContext`,或读取原始网络上下文;
56
61
  - 技术失败码或执行确定性存在 catch-all/default;
57
62
  - 需要裸 gRPC/channel/bytes、第二入口、动态 descriptor 或未声明外部 I/O;
58
- - `saddle-framework` 不是精确 `=0.3.0-alpha.1`,或 [正式 Gate](validation.md) 失败。
63
+ - `saddle-framework` 不是精确 `=0.3.0-alpha.3`,或 [正式 Gate](validation.md) 失败。
59
64
 
60
65
  停止产物只包含最小复现、缺失身份和期望业务表达,不包含中间件替代实现。
@@ -9,24 +9,27 @@
9
9
  外部函数只由以下二元组定位:
10
10
 
11
11
  ```text
12
- (businessUnit, function)
12
+ (puc, function)
13
13
  ```
14
14
 
15
- - `businessUnit` 是业务单元,例如 `puc`。
15
+ - `puc` 是业务单元,例如 `puc`。
16
16
  - `function` 是契约原始函数名,允许中文,例如 `查询用户绑定户号`。
17
17
  - protobuf method、Rust 方法和 Java 方法使用各语言合法的本地标识符;它们不是跨系统身份。
18
18
  - 0.3.0 不定义 contract version、多版本路由、翻译表、alias 或 fallback。
19
19
 
20
20
  ## 定义步骤
21
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 后再联调。
22
+ 1. `contract_dir` 根部原样生成 Skill 资产 `../assets/saddle_external_function.proto`,文件名绝对固定。
23
+ 2. 从业务 spec 识别外部业务能力,不复制 SOFA service、method 或传输模型。
24
+ 3. 为每个能力在业务 proto 中定义具名 protobuf request result,并 import 根部的 `saddle_external_function.proto`。
25
+ 4. 使用固定文件提供的 `google.protobuf.MethodOptions` extension,把 rpc 方法绑定到 `puc + function`。
26
+ 5. 把同一棵 `contract_dir` 契约树交给 Saddle 业务 Agent 和 profusecontract Agent。
27
+ 6. Saddle 侧配置契约根目录,并只登记业务实际调用的函数;profusecontract 直接消费整个目录,实现对应函数和防腐映射,无需安装 Saddle。
28
+ 7. 双方以同一 descriptor 运行 conformance fixture 后再联调。
28
29
 
29
- 4 步是并行研发的起点,不是第 1 步。交接时至少记录 `.proto` 相对路径、原始 bytes SHA-256、package、service、rpc、`business_unit` 和中文 `function`。任一方改动契约都必须回到共同冻结步骤,不能在各自分支继续演化。
30
+ 5 步是并行研发的起点,不是第 1 步。契约目录本身是唯一真源:不另写 proto 文件清单或聚合文件。固定协议文件不是第二真源,而是同一目录必须包含的根文件;不得创建独立 protocol 目录、版本副本或 manifest。任一方改动业务契约都必须回到共同冻结步骤,不能在各自分支继续演化。
31
+
32
+ Saddle 编译期递归扫描目录、闭合 import 并按 canonical 相对路径排序。只有带本页正式 method option 的 RPC 进入索引;普通 RPC 与纯消息文件只参与 descriptor/import 闭包,不成为可调用外部函数。
30
33
 
31
34
  ## 模板
32
35
 
@@ -35,7 +38,7 @@ syntax = "proto3";
35
38
 
36
39
  package puc;
37
40
 
38
- import "saddle/profusecontract/external_function.proto";
41
+ import "saddle_external_function.proto";
39
42
 
40
43
  service PucExternalFunctions {
41
44
  rpc QueryBoundAccounts(
@@ -44,7 +47,7 @@ service PucExternalFunctions {
44
47
  QueryBoundAccountsResult
45
48
  ) {
46
49
  option (saddle.profusecontract.external_function) = {
47
- business_unit: "puc"
50
+ puc: "puc"
48
51
  function: "查询用户绑定户号"
49
52
  };
50
53
  }
@@ -65,7 +68,7 @@ message BoundAccount {
65
68
 
66
69
  这里的跨系统正式身份是 `("puc", "查询用户绑定户号")`。`QueryBoundAccounts`、未来生成的 Rust 方法名和 Java 方法名都只是本地代码标识符,不能替换、翻译或推导中文 `function`。
67
70
 
68
- 该 import method option 属于 Saddle `0.3.0-alpha.1` 固定协议。不得自行创建同名 extension,也不得复制为另一份协议。
71
+ 该 import 必须精确指向同目录根部的 `saddle_external_function.proto`。其 bytes 来自 Saddle `0.3.0-alpha.3` 的权威模板并由 Skill 固定携带;不得自行创建同名 extension,也不得复制为另一份协议。
69
72
 
70
73
  实际线上网络仍只调用 Saddle 固定 `ProfuseContractBoundary/Invoke` unary。契约中的 service/rpc 用于绑定身份、request 和 result,不要求 profusecontract 暴露同名网络方法。
71
74
 
@@ -79,4 +82,4 @@ message BoundAccount {
79
82
 
80
83
  ## 变更规则
81
84
 
82
- 先修改共同 `.proto` 并重新通过双方 conformance,再修改两侧实现。禁止两侧各自维护近似定义后人工对齐;0.3.0 遇到不兼容变化时停止并共同升级,不在运行时并存或猜测版本。
85
+ 先修改共同契约目录并重新通过双方 conformance,再修改两侧实现。禁止两侧各自维护近似定义后人工对齐;0.3.0 遇到不兼容变化时停止并共同升级,不在运行时并存或猜测版本。
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 应用显式声明
4
4
 
5
- 业务应用使用 `saddle-framework = "=0.3.0-alpha.1"` 的公开编程面:
5
+ 业务应用使用 `saddle-framework = "=0.3.0-alpha.3"` 的公开编程面:
6
6
 
7
7
  ```rust
8
8
  saddle::application! {
@@ -10,7 +10,7 @@ saddle::application! {
10
10
  application PucBackend;
11
11
 
12
12
  profusecontract {
13
- contract "contracts/puc-external-functions.proto";
13
+ contract_dir "contracts";
14
14
  capability PucProfuseContract;
15
15
  functions {
16
16
  QueryBoundAccounts => query_bound_accounts {
@@ -33,7 +33,11 @@ saddle::application! {
33
33
  }
34
34
  ```
35
35
 
36
- 这是业务实现显式写出的静态事实。Saddle 不从 spec 自动选择 contract、function 或调用边,也不允许运行时动态修改。
36
+ 这是业务实现显式写出的静态事实。`contract_dir` 是唯一 proto 真源;不要再列文件清单或维护聚合 proto。Saddle 只在编译期确定性递归扫描、闭合 import 并建立 option-only 索引,不在运行时发现函数。
37
+
38
+ `functions` 只写当前应用实际使用的外部函数。每项必须由至少一个 service 的 `uses` 引用;普通 RPC、纯消息 proto 和未带正式 method option 的 RPC 不写入 `functions`。
39
+
40
+ 业务 proto option 使用字段 `puc`;应用 DSL 使用 `business_unit`。两者必须逐字相等,均不允许翻译、alias 或 fallback。
37
41
 
38
42
  ## Handler
39
43
 
@@ -1,4 +1,4 @@
1
- # Saddle 0.3.0-alpha.1 正式业务 Gate
1
+ # Saddle 0.3.0-alpha.3 正式业务 Gate
2
2
 
3
3
  ## 依赖与锁
4
4
 
@@ -9,10 +9,10 @@
9
9
  name = "my-saddle-app"
10
10
  version = "0.1.0"
11
11
  edition = "2024"
12
- rust-version = "1.85"
12
+ rust-version = "1.95"
13
13
 
14
14
  [dependencies]
15
- saddle-framework = "=0.3.0-alpha.1"
15
+ saddle-framework = "=0.3.0-alpha.3"
16
16
  ```
17
17
 
18
18
  在允许访问正式 registry 的依赖准备阶段生成并提交 `Cargo.lock`。禁止 `path`、`git`、`[patch]`、source replacement 或宽松 Saddle 版本。
@@ -25,12 +25,15 @@ saddle-framework = "=0.3.0-alpha.1"
25
25
  npx --no-install saddle-03-gate --manifest-path Cargo.toml
26
26
  ```
27
27
 
28
- 命令由已安装的 `@antprofuse/saddle-skill@0.3.0-alpha.1` 提供,不需要 Saddle 源码仓库。它会检查:
28
+ 命令由已安装的 `@antprofuse/saddle-skill@0.3.0-alpha.3` 提供,不需要 Saddle 源码仓库。它会检查:
29
29
 
30
30
  - facade 精确版本、禁止直接中间件依赖和 Cargo source 旁路;
31
31
  - `Cargo.lock` 同版映射;
32
+ - 唯一 `contract_dir`、目录存在、禁止旧单文件 contract 声明;
33
+ - 目录根部 `saddle_external_function.proto` 与 Skill 权威模板逐字一致且仅出现一次,使用 option 的业务 proto 必须 direct import 根文件;
34
+ - 编译期递归扫描/import 闭包、option-only 索引、身份唯一、请求结果类型匹配,以及所有 function 均有实际 `uses`;
32
35
  - 固定 `ProfuseGwContext`、应用声明、八类技术失败与三种 certainty 均被显式引用;
33
36
  - 无 wildcard/default、第二 listener/Runtime、裸 gRPC/HTTP、动态函数或 raw invoke;
34
- - Rust 1.85 下 `cargo check --locked --offline`。
37
+ - Rust 1.95 下 `cargo check --locked --offline`。
35
38
 
36
39
  Gate 使用临时 target 并在成功、失败和信号退出时清理。任何失败均为 STOP;不得加跳过参数或改 Gate 脚本。
@@ -5,6 +5,7 @@ const fs = require("fs");
5
5
  const os = require("os");
6
6
  const path = require("path");
7
7
  const cp = require("child_process");
8
+ const canonicalSchema = fs.readFileSync(path.resolve(__dirname, "../assets/saddle_external_function.proto"));
8
9
 
9
10
  function stop(rule, detail) {
10
11
  process.stderr.write(`SADDLE_03_GATE ${rule} ${detail}\n`);
@@ -29,8 +30,8 @@ if (/^\s*\[(patch|replace)\b/m.test(cargo) || /\b(path|git|branch|tag)\s*=/.test
29
30
  stop("DEPENDENCY_SOURCE", "禁止 path/git/patch/replace 依赖旁路");
30
31
  }
31
32
  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"');
33
+ if (!facade || !/^"=0\.3\.0-alpha\.3"\s*(?:#.*)?$/.test(facade[1].trim())) {
34
+ stop("SADDLE_VERSION", '必须直接且精确依赖 saddle-framework = "=0.3.0-alpha.3"');
34
35
  }
35
36
  const internals = ["saddle-core","saddle-admission","saddle-boundary","saddle-db","saddle-macros","saddle-observability","saddle-runtime","saddle-service"];
36
37
  for (const name of internals) {
@@ -40,8 +41,8 @@ if (!fs.existsSync(lockPath)) {
40
41
  stop("LOCK", "缺少 Cargo.lock");
41
42
  } else {
42
43
  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");
44
+ if (!/name = "saddle-framework"\nversion = "0\.3\.0-alpha\.3"/.test(lock)) {
45
+ stop("LOCK", "Cargo.lock 未绑定 saddle-framework 0.3.0-alpha.3");
45
46
  }
46
47
  }
47
48
 
@@ -58,6 +59,45 @@ visit(sourceRoot);
58
59
  const source = files.map(p => fs.readFileSync(p, "utf8")).join("\n");
59
60
  if (!source.includes("application!")) stop("APPLICATION", "缺少 Saddle application! 声明");
60
61
  if (!source.includes("ProfuseGwContext")) stop("CONTEXT", "handler 必须使用固定 ProfuseGwContext");
62
+ const contractDirs = [...source.matchAll(/\bcontract_dir\s+"([^"]+)"\s*;/g)];
63
+ if (contractDirs.length !== 1) stop("CONTRACT_DIR", "必须且只能声明一个 contract_dir");
64
+ if (/\bcontract\s+"[^"]+"\s*;/.test(source)) stop("CONTRACT_DIR", "禁止旧单 proto contract 声明");
65
+ if (contractDirs.length === 1) {
66
+ const value = contractDirs[0][1];
67
+ if (path.isAbsolute(value) || value.split(/[\\/]+/).includes("..")) {
68
+ stop("CONTRACT_DIR", "contract_dir 必须是 crate 内无逃逸的相对路径");
69
+ } else {
70
+ const dir = path.resolve(root, value);
71
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
72
+ stop("CONTRACT_DIR", `目录不存在: ${value}`);
73
+ } else {
74
+ const rootSchema = path.join(dir, "saddle_external_function.proto");
75
+ if (!fs.existsSync(rootSchema) || !fs.readFileSync(rootSchema).equals(canonicalSchema)) {
76
+ stop("CONTRACT_SCHEMA", "contract_dir 根部固定协议缺失或与 Skill 权威模板不一致");
77
+ }
78
+ const protos = [];
79
+ function visitContracts(current) {
80
+ for (const ent of fs.readdirSync(current, {withFileTypes: true})) {
81
+ const p = path.join(current, ent.name);
82
+ if (ent.isDirectory()) visitContracts(p);
83
+ else if (ent.isFile() && p.endsWith(".proto")) protos.push(p);
84
+ }
85
+ }
86
+ visitContracts(dir);
87
+ if (protos.filter(p => path.basename(p) === "saddle_external_function.proto").length !== 1) {
88
+ stop("CONTRACT_SCHEMA", "固定协议必须且只能位于 contract_dir 根部");
89
+ }
90
+ for (const proto of protos) {
91
+ if (proto === rootSchema) continue;
92
+ const body = fs.readFileSync(proto, "utf8");
93
+ if (body.includes("saddle.profusecontract.external_function") &&
94
+ !/^\s*import\s+"saddle_external_function\.proto"\s*;/m.test(body)) {
95
+ stop("CONTRACT_IMPORT", `${path.relative(dir, proto)} 必须 direct import 根部固定协议`);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
61
101
  for (const code of ["FunctionNotFound","FunctionRequestInvalid","CapacityRejected","DeadlineExceeded","DependencyUnavailable","ContractResultInvalid","InternalFailure","TransportFailure"]) {
62
102
  if (!source.includes(code)) stop("TECHNICAL_FAILURE", `缺少显式分支 ${code}`);
63
103
  }
@@ -75,10 +115,10 @@ if (process.exitCode) process.exit(process.exitCode);
75
115
  const target = fs.mkdtempSync(path.join(os.tmpdir(), "saddle-03-gate-"));
76
116
  try {
77
117
  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 编译失败");
118
+ const result = cp.spawnSync("cargo", ["+1.95.0", "check", "--manifest-path", manifest, "--locked", "--offline"], {env, stdio: "inherit"});
119
+ if (result.error || result.status !== 0) stop("COMPILER", "Rust 1.95 locked/offline 编译失败");
80
120
  } finally {
81
121
  fs.rmSync(target, {recursive: true, force: true});
82
122
  }
83
123
  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");
124
+ process.stdout.write("SADDLE_03_GATE PASS; VERSION=0.3.0-alpha.3; LOCKED_OFFLINE; BUSINESS_BOUNDARIES=CLOSED\n");