refactor(cmdcore): align leaf safety with schema contract

This commit is contained in:
玉澜
2026-07-30 23:03:32 +08:00
parent 621229fca2
commit 340f01e95c
22 changed files with 510 additions and 560 deletions
+29 -60
View File
@@ -27,25 +27,19 @@
│ └────────────────────────────────────────┘ │
│ │
│ ┌─── 安全模型 ──────────────────────────────────────────────────┐ │
│ │ Safety cli.SafetySpec │ │
│ │ ├─ Effect (read / write / destructive) │ │
│ │ ├─ Risk (low / medium / high) │ │
│ │ ├─ Confirmation (not_required / user_required) ──▶ 运行时门 │ │
│ │ └─ Idempotency (idempotent / retryable / …) │ │
│ │ │ │
│ │ Risk (运行时) Safety (Schema) │ │
│ │ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ read │─────▶│ read │ low/idempotent │ │
│ │ │ write │─────▶│ write │ medium/unknown │ │
│ │ │ high-risk- │─────▶│ destructive │ high/unknown │ │
│ │ │ write │ │ │ │ │
│ │ └──────────────┘ │ high-write │ high/unknown │ │
│ │ │ └───────────────┘ │ │
│ │ │ SafetyDefault() ▲ │ │
│ │ └────────────────────┘ (Safety 为空时的推导) │ │
│ │ │ │
│ │ ConfirmFirst: bool (确认门顺序) │ │
│ │ 四字段彼此独立;同一值同时供运行时与 Schema 使用 │ │
│ │ ConfirmFirst: bool (只控制确认门顺序) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Schema 声明 (Agent 可见的元数据) ──────────────────────────┐ │
│ │ SchemaDecl │ │
│ │ ├─ Title / Description │ │
│ │ ├─ SafetyDecl {Effect, Risk, Confirmation, Idempotency} │ │
│ │ ├─ DryRunDecl {PreviewKind, RemoteReads} │ │
│ │ ├─ InterfaceDecl {Mode, Availability, Reason, Ref} │ │
│ │ ├─ SelectionDecl {AgentSummary, UseWhen, AvoidWhen, │ │
@@ -74,11 +68,12 @@ CommandSpec ──── NewCommand() ────▶ cobra.Command ────
│ │ │
构建时检查: 注册产物: 执行上下文:
• validateDispatchDecl • Flags + Aliases • Str(name)
• validateSchemaDecl • Annotations (Schema) • Int(name)
• RegisterFlags • Long (约束 help) • Bool(name)
• ValidateConstraintDecls • RunE (管线) • StrSlice(name)
• embedContractIntoSchema • Changed(name)
• AnnotateConstraints • DryRun() / Yes()
• validateSafetySpec • Annotations (Schema) • Int(name)
• validateSchemaDecl • Long (约束 help) • Bool(name)
• RegisterFlags • RunE (管线) • StrSlice(name)
• ValidateConstraintDecls • Changed(name)
• embedContractIntoSchema • DryRun() / Yes()
• AnnotateConstraints
• PostMount
```
@@ -89,59 +84,33 @@ CommandSpec ──── NewCommand() ────▶ cobra.Command ────
| **CommandSpec** | struct | 一个命令的完整契约(声明 + 执行) |
| **FlagSpec** | struct | 一个参数的注册、回退链、绑定规则 |
| **Constraint** | struct | 参数间的关系约束 |
| **Risk** | enum | 运行时确认行为(是否提示用户) |
| **Safety** | enum | Schema 安全元数据等级(Agent 决策依据) |
| **Safety** | cli.SafetySpec | 运行时与 Schema 共用的安全契约 |
| **SchemaDecl** | struct | Agent 可见的完整工具规格声明 |
| **SelectionDecl** | struct | Agent 选择该工具的语义指引 |
| **InterfaceDecl** | struct | 工具的接口模式与可用性 |
| **DryRunDecl** | struct | dry-run 能力声明 |
| **SafetyDecl** | struct | Safety 枚举展开后的细粒度覆盖 |
| **IdentityDecl** | struct | 工具在注册表中的身份标识 |
| **PositionalDecl** | struct | 有序位置参数声明 |
| **Ctx** | struct | 执行上下文(类型安全的 flag 读取) |
| **NewCommand** | func | 统一构建器(声明 → cobra.Command) |
## Risk 枚举
## SafetySpec
控制**运行时确认行为**——是否在派发前提示用户确认。
`CommandSpec.Safety` 直接复用 `cli.SafetySpec`,没有 cmdcore 自定义 Risk/Safety 枚举,也没有 `SafetyDecl` 覆盖层:
| 值 | 含义 | 运行时行为 |
|----|------|-----------|
| `""` / `read` | 只读操作 | 不提示 |
| `write` | 变更状态 | 未加 `--yes` 时提示确认 |
| `high-risk-write` | 破坏性/不可逆 | 未加 `--yes` 时提示确认 |
## Safety 枚举
控制 **Schema 元数据等级**——告诉 Agent 操作对系统的影响程度。
| 值 | effect | risk | confirmation | idempotency |
|----|--------|------|--------------|-------------|
| `read` | read | low | not_required | idempotent |
| `write` | write | medium | user_required | unknown |
| `high-write` | write | high | user_required | unknown |
| `destructive` | destructive | high | user_required | unknown |
## Safety 优先级链
三层解析,先命中先生效:
```
SafetyDecl 显式字段 > Safety 枚举 > Risk.SafetyDefault()
(最高: 精确覆盖) (中: 等级快捷) (最低: 默认推导)
```go
Safety: cli.SafetySpec{
Effect: "write",
Risk: "high",
Confirmation: "user_required",
Idempotency: "unknown",
}
```
- **SafetyDecl 显式字段**:`SchemaDecl.Safety` 中非空字段直接使用
- **Safety 枚举**:`CommandSpec.Safety` 非空时按表展开填充空字段
- **Risk.SafetyDefault()**:Safety 为空时从 Risk 推导
推导映射(`Risk.SafetyDefault()`):
```
read → SafetyRead
write → SafetyWrite
high-risk-write → SafetyDestructive
```
- `Confirmation == "user_required"` 时执行确认;`--yes` 和 `--dry-run` 可跳过交互。
- `Effect`、`Risk`、`Idempotency` 不参与确认决策,也不会改写 `Confirmation`。
- 任意一个字段非空时,四个字段必须全部显式声明;构建时拒绝部分声明。
- 完全空值仅作为历史只读默认,最终发布为 `read/low/not_required/idempotent`。
## FlagSpec 有效值回退链
@@ -226,7 +195,7 @@ DryRunDecl{
|--------|----------|-------------|
| **Invoke** | 单步 MCP/后端调用 | 框架完成 required→constraint→validate→buildArgs→confirm,传入装配好的 toolArgs |
| **Orchestrate** | 多步编排 | 框架完成 required→constraint→validate→confirm,传入 Ctx 自行组装调用 |
| **RunE** | 逃生舱 | 框架只注册 flag 和 Schema,执行完全自定义 |
| **RunE** | 逃生舱 | 框架仍执行 Safety 确认,具体业务执行完全自定义 |
## 设计不变量
@@ -234,5 +203,5 @@ DryRunDecl{
2. **声明面绝不调用后端**——cmdcore 是 dispatch-agnostic
3. **执行面绝不发明 CLI 表面**——业务 flag 必须在 Flags 声明
4. **构建时拦截 > 运行时报错**——声明错误 panic 在注册阶段
5. **Safety 和 Risk 独立但可组合**——运行时行为与 Schema 元数据各管一摊
5. **SafetySpec 是单一事实源**——Confirmation 驱动运行时,其余字段原样进入 Schema
6. **声明即 review**——代码中的 Schema 经 code review 后直接投影,不依赖外部 hint 文件
+37 -36
View File
@@ -10,10 +10,10 @@
命令框架将 CLI 命令的**声明**与**执行**分离:
- **声明面** — 数据字段描述命令是什么(flag、约束、风险等级、Schema 元数据)
- **声明面** — 数据字段描述命令是什么(flag、约束、SafetySpec、Schema 元数据)
- **执行面** — 钩子函数描述命令做什么(校验、派发、编排)
框架负责:flag 注册、有效值回退链、required/约束校验、Risk 写确认、toolArgs 装配、Agent Runtime Schema 投影。
框架负责:flag 注册、有效值回退链、required/约束校验、SafetySpec 确认、toolArgs 装配、Agent Runtime Schema 投影。
## 核心类型
@@ -30,8 +30,7 @@ type CommandSpec struct {
Example string
Flags []FlagSpec
Constraints []Constraint
Risk Risk // 运行时确认行为
Safety Safety // Schema 元数据等级
Safety cli.SafetySpec // 运行时与 Schema 的单一安全来源
ConfirmFirst bool // 确认门先于参数校验
ConstParams map[string]any
Schema SchemaDecl // 完整 ToolSpec 载荷
@@ -47,32 +46,29 @@ type CommandSpec struct {
}
```
### Risk 与 Safety(独立枚举)
### SafetySpec(单一安全来源)
两个正交维度,组合使用:
`CommandSpec.Safety` 直接使用现有 Agent Runtime Schema 的 `cli.SafetySpec`:
| 枚举 | 职责 | 取值 |
|------|------|------|
| **Risk** | 运行时确认行为(是否弹 yes/no) | `read` / `write` / `high-risk-write` |
| **Safety** | Schema 元数据等级(Agent 看到的影响描述) | `read` / `write` / `high-write` / `destructive` |
| 字段 | 职责 |
|------|------|
| `Effect` | 操作影响:read / write / destructive |
| `Risk` | 风险等级:low / medium / high |
| `Confirmation` | 是否需要用户确认:not_required / user_required |
| `Idempotency` | 幂等性:idempotent / retryable / non_idempotent / unknown |
Safety 为空时,通过 `Risk.SafetyDefault()` 方法推导:
| Risk | → Safety 默认 | Schema 展开 |
|------|--------------|-------------|
| (空) / `read` | `read` | effect:read, risk:low, confirmation:not_required, idempotency:idempotent |
| `write` | `write` | effect:write, risk:medium, confirmation:user_required, idempotency:unknown |
| `high-risk-write` | `destructive` | effect:destructive, risk:high, confirmation:user_required, idempotency:unknown |
显式设置 Safety 可以覆盖默认推导:
四个字段彼此独立。框架只读取 `Confirmation` 决定运行时确认,其余字段原样发布到 Schema,不从一个字段机械推导另一个。非空 SafetySpec 必须一次声明完整:
```go
// create:运行时 high-risk 确认,但 Schema 标记为 high-write(不可逆但非破坏性)
Risk: LeafRiskHighWrite,
Safety: LeafSafetyHighWrite, // write/high 而非 destructive/high
Safety: cli.SafetySpec{
Effect: "write",
Risk: "high",
Confirmation: "user_required",
Idempotency: "unknown",
},
```
优先级链:`explicit SafetyDecl fields > Safety enum > Risk.SafetyDefault()`
完全空值保留历史只读默认 `read/low/not_required/idempotent`;不存在 Risk/Safety 枚举或覆盖优先级链。
### FlagSpec
@@ -123,19 +119,20 @@ flag 解析按以下顺序取值(先命中先生效):
`cmdcore.NewCommand(spec)` 执行以下构建时检查(失败则 panic):
1. **validateDispatchDecl** — 恰好一个执行体(Invoke/Orchestrate/RunE)
2. **validateSchemaDecl** — Schema 声明完整性(Description、AgentSummary、UseWhen、AvoidWhen、Examples、Safety、Interface)
3. **RegisterFlags** — flag + alias 注册到 cobra
4. **ValidateConstraintDecls** — 约束引用的 flag 必须存在
5. **embedContractIntoSchema** — 投影到 dws.schema.* annotations
6. **AnnotateConstraints** — 约束渲染到 --help
7. **PostMount** — 调用方的挂载收尾钩子
2. **validateSafetySpec** — 非空 SafetySpec 的四个独立字段必须完整
3. **validateSchemaDecl** — Schema 声明完整性(Description、AgentSummary、UseWhen、AvoidWhen、Examples、Interface)
4. **RegisterFlags** — flag + alias 注册到 cobra
5. **ValidateConstraintDecls** — 约束引用的 flag 必须存在
6. **embedContractIntoSchema** — 投影到 dws.schema.* annotations
7. **AnnotateConstraints** — 约束渲染到 --help
8. **PostMount** — 调用方的挂载收尾钩子
## 运行时流程
生成的 `RunE` 按以下顺序执行:
```
[ConfirmFirst? → ConfirmRisk] ← devapp 遗留语义:先确认后校验
[ConfirmFirst? → ConfirmSafety] ← 可选:先确认后校验
│
▼
ValidateRequired ← 有效值回退链校验
@@ -153,7 +150,7 @@ BuildArgs ← flag → toolArgs 装配
ConstParams 合并
│
▼
[!ConfirmFirst? → ConfirmRisk] ← 默认顺序:校验后确认
[!ConfirmFirst? → ConfirmSafety] ← 默认顺序:校验后确认
│
▼
Invoke(ctx, toolArgs) ← 单步派发
@@ -170,8 +167,10 @@ func newDevAppCreateCommand(runner executor.Runner) *cobra.Command {
Use: "create",
Short: "创建开放平台企业内部应用",
Tool: devAppCreateTool,
Risk: LeafRiskHighWrite,
Safety: LeafSafetyHighWrite,
Safety: cli.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
ConfirmFirst: true,
Flags: []LeafFlag{
{Name: "name", Usage: "应用名称 (必填)", Bind: "name",
@@ -208,6 +207,8 @@ spec := FromShortcut(Shortcut{
})
```
Shortcut 本次仍保留自身的 `Risk`。未接入 live mount 的 adapter 只在边界将它展开成完整 `cli.SafetySpec`;cmdcore/Leaf 不再保留该枚举。
## 文件结构
| 文件 | 职责 |
@@ -230,8 +231,8 @@ spec := FromShortcut(Shortcut{
## 设计原则
1. **声明 vs 执行分离** — Flags/Constraints/Risk/Safety/Schema 是声明;Invoke/Validate/PostMount 是执行
1. **声明 vs 执行分离** — Flags/Constraints/Safety/Schema 是声明;Invoke/Validate/PostMount 是执行
2. **单一数据源** — 一份声明驱动 --help、Schema、catalog、runtime 校验
3. **框架推导 > 手写** — Safety 从 Risk 推导、Example 从 Schema.Selection.Examples 继承
3. **安全字段不互推** — Confirmation 单独驱动确认,Effect/Risk/Idempotency 原样发布
4. **构建时拦截 > 运行时报错** — 声明不完整在命令注册时 panic,不等到用户触发
5. **向后兼容** — type alias 让现有 LeafSpec 调用方零改动
5. **边界兼容** — Shortcut 暂由 adapter 转换,Leaf 直接声明 SafetySpec
+11 -10
View File
@@ -20,18 +20,17 @@
```
CommandSpec (声明) → NewCommand() → cobra.Command
│
├── Risk enum (运行时确认)
├── Safety enum (Schema 元数据)
├── cli.SafetySpec (运行时 + Schema 单一安全来源)
├── FlagSpec[] (参数 + 回退链 + 绑定)
├── Constraint[] (互斥/至少一个)
├── SchemaDecl (Agent Selection/Safety/DryRun/Interface)
├── SchemaDecl (Agent Selection/DryRun/Interface)
│
└── Invoke / Orchestrate / RunE (执行)
```
**核心特点**:
- 声明与执行严格分离
- Risk(运行时)和 Safety(Schema)独立枚举
- SafetySpec 四个独立字段直接对齐 Agent Runtime Schema
- 有效值回退链:flag → alias → env → default
- 框架统一校验、装配、确认、投影
- Schema 从代码声明直接投影,无外部 hint 文件
@@ -82,7 +81,7 @@ API Discovery → 代码生成 → surface command
|------|-------------|----------|-----|
| 参数别名 + 环境变量回退 | ✅ FlagSpec.Aliases + EnvVar | ❌ 无 | ❌ 无 |
| 声明式约束 (互斥/至少一个) | ✅ Constraint[] | ❌ 只有 Validate hook | ✅ argparse group |
| Risk/Safety 分离 | ✅ 两个独立枚举 | ❌ 只有 Risk | ❌ 无 |
| 安全契约 | ✅ SafetySpec(effect/risk/confirmation/idempotency) | Risk | 无 |
| Schema 投影 (Agent metadata) | ✅ SchemaDecl 内建 | ⚠️ 运行时 introspection | ❌ 外挂 |
| 参数绑定 (flag name → API key) | ✅ FlagSpec.Bind | ❌ 手写 | ✅ 自动映射 |
| ConstParams (固定载荷) | ✅ | ❌ 手写在 Execute | ✅ 隐式 |
@@ -104,7 +103,7 @@ API Discovery → 代码生成 → surface command
|------|-------------|----------|-----|
| 工具发现 | `dws schema --all` (静态 catalog) | `--print-schema` (运行时) | API Discovery |
| 选择指引 | SelectionDecl (UseWhen/AvoidWhen) | Description + Tips | 无 |
| 安全声明 | Safety enum + SafetyDecl | Risk string | 无 |
| 安全声明 | cli.SafetySpec 直接声明 | Risk string | 无 |
| dry-run 能力声明 | DryRunDecl (reviewed) | DryRun hook 存在性 | 无 |
| 接口模式 | InterfaceDecl (local/pinned/composite) | 隐式 (全部 REST) | 隐式 (全部 REST) |
@@ -128,7 +127,7 @@ GWS: API Discovery JSON → 代码生成 → surface command
| 选择 | 理由 | 对比 |
|------|------|------|
| 框架装配参数 | 消除 N 个命令各写一份 toolArgs 装配 | lark-cli 每个 Execute 手动取 flag 值 |
| Risk + Safety 分离 | 运行时行为与 Schema 元数据独立演进 | lark-cli 只有 Risk 一个维度 |
| SafetySpec 单一来源 | confirmation 驱动运行时,其余字段原样发布且互不推导 | lark-cli 只有 Risk 一个维度 |
| 声明式约束 | 构建时校验合法性 + 投影到 Schema + 渲染帮助 | lark-cli 约束隐藏在 Validate 逻辑里 |
| Schema 构建时投影 | 离线 catalog 支持 Agent 批量发现 | lark-cli 需要逐个命令 introspection |
| 有效值回退链 | flag → alias → env 统一语义 | lark-cli 别名是独立 Flag 手动关联 |
@@ -170,8 +169,10 @@ NewLeafCommand(LeafSpec{
Use: "create",
Short: "创建应用",
Tool: "create_dev_app",
Risk: LeafRiskHighWrite,
Safety: LeafSafetyHighWrite,
Safety: cli.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
ConfirmFirst: true,
Flags: []LeafFlag{
{Name: "name", Usage: "应用名称", Bind: "name",
@@ -228,7 +229,7 @@ var CalendarCreate = common.Shortcut{
| 场景 | 最适合 | 原因 |
|------|--------|------|
| MCP 后端 + Agent Schema 投影 | **DWS cmdcore** | 内建 Schema 声明、Safety 分级、离线 catalog |
| MCP 后端 + Agent Schema 投影 | **DWS cmdcore** | 内建 Schema 声明、SafetySpec 契约、离线 catalog |
| REST API 直连 + OAuth scope 管理 | **lark-cli** | CallAPITyped + scope 预检 + DryRun API 计划 |
| API-first 大规模 surface 生成 | **GWS gcloud** | Discovery 驱动,一份 Schema 生成一切 |
| 多步编排 (跨服务链式调用) | **lark-cli** / DWS Orchestrate | lark 的 CallAPITyped 链式 + DWS 的 Orchestrate |
+28 -22
View File
@@ -12,7 +12,7 @@
1. cobra 实际注册的 flags / required / defaults;
2. `--help` 的 Flags 与「参数约束」段;
3. **嵌入后的** `dws schema` / Catalog(`schema_catalog` / agent metadata 生成物)中的 parameters、关系约束,以及显式 `Risk` 时的 confirmation/effect。
3. **嵌入后的** `dws schema` / Catalog(`schema_catalog` / agent metadata 生成物)中的 parameters、关系约束和 SafetySpec。
嵌入机制(已落地):
@@ -23,12 +23,12 @@ CommandSpec
dws.schema.contract=cmdcore
dws.schema.property / type / required (per flag)
dws.schema.constraints
dws.schema.risk (仅当 Risk 字段非空)
→ Schema 组装读取 native_annotation + Contract Risk overlay
→ RegisterRuntimeContractFinal(SafetySpec + SchemaDecl)
→ Schema 组装透传 Contract Final
→ go:embed schema_catalog / schema_agent_metadata
```
空 `Risk` **不**写入 `dws.schema.risk`,避免把默读盖掉今日 write-guard 叶子的 reviewed Safety。
cmdcore/Leaf 不再写 `dws.schema.risk`;SafetySpec 走类型化 Final 载荷,不使用字符串枚举注解。
### 1.1 硬规则:声明 = 最终数据源(Schema 透传)
@@ -38,7 +38,7 @@ CommandSpec
- 在声明体系里再挂「评审字段」并行权威;
- 用 hints/registry 盖写已声明字段。
迁移期未迁完的叶子可暂走旧组装路径;**新声明面不含 review_reason / reviewed 字段**。写命令未设 `Risk`/`Schema.Safety` 时,过渡期仍可用 `runtime_gate` annotate(`HOM-S2`)。
迁移期未迁完的叶子可暂走旧组装路径;**新声明面不含 review_reason / reviewed 字段**。写命令未设 `Safety` 时,过渡期仍可用 `runtime_gate` annotate(`HOM-S2`)。
**不采用**路径 B(以 `schema_mcp_metadata` 生成全部 CLI flag/help/schema)作为主权威。钉钉 MCP meta 不是飞书 OAPI:粒度与 CLI 特有语义(二选一、OmitEmpty、ConstParams、write guard)无法从裸 meta 推出;强行生成会违反「Schema 描述 CLI,不制造 CLI」。
@@ -68,7 +68,7 @@ CommandSpec
|---|---|---|---|
| `Flags[]`(`FlagSpec` / `LeafFlag`) | 用户可见参数面:名、类型、默认、必填、usage | 注册 cobra flag;装配 toolArgs | `dws.schema.property` / `type` / `required`;`--help` Flags |
| `Constraints[]` | 跨 flag 关系:`at_least_one` / `exactly_one` / `mutually_exclusive` | `ValidateConstraints` | `dws.schema.constraints`;`--help`「参数约束」 |
| `Risk`(**非空**) | 副作用与确认:`write` / `high-risk-write`(或显式 `read`) | `ConfirmRisk`(`--yes` / `--dry-run` 跳过) | `dws.schema.risk` → Safety overlay(`HOM-S1`) |
| `Safety`(`cli.SafetySpec`) | effect/risk/confirmation/idempotency 四个独立事实 | `confirmation=user_required` 时 `ConfirmSafety`;`--yes` / `--dry-run` 跳过 | 同一个 SafetySpec 原样进入 Contract Final(`HOM-S1`) |
| `ConstParams` | 固定载荷(不上 flag 表) | 并入 toolArgs;不满足 Required | **不**投影为用户 parameter |
| `Use` / `Short` / `Long` / `Example` | 命令身份文案与示例 | cobra 自身 | help;identity 仍以 registry 为准 |
@@ -88,30 +88,37 @@ CommandSpec
| `Validate` | 条件式/领域校验钩子 | 不得在此 `Flags().String(...)` 注册业务 flag;不得只靠钩子表达「必填/互斥」而不写 `Flags`/`Constraints` |
| `Call` / `Invoke` / `Orchestrate` | 执行体 | 不得 `params[k]=…` 装配业务参数(应在 `Flags`/`ConstParams`) |
| `PostMount` | 挂载后收尾(annotate、领域工具注入) | 不得注册业务 flag;分页等横切由领域工具注入并可走 annotate |
| `RunE` | 逃生舱(整段手写) | 表面事实仍须 Flags 声明,或对确认语义做 §1.3 标注 |
| `RunE` | 逃生舱(整段手写) | 表面事实仍须 Flags 声明;框架仍按 Safety 执行确认 |
| `Server` / `Tool` | MCP 路由 | 不构成 CLI parameter 声明 |
#### 1.2.3 最小声明示例
```go
// 读:无确认语义 → Risk 可空(不写 dws.schema.risk)
// 读:Safety 四字段显式对齐 Schema
NewLeafCommand(LeafSpec{
Use: "get", Short: "…", Tool: "…",
Safety: cli.SafetySpec{
Effect: "read", Risk: "low",
Confirmation: "not_required", Idempotency: "idempotent",
},
Flags: []LeafFlag{
{Name: "unified-app-id", Usage: "…", Bind: "unifiedAppId", Trim: true, Required: true},
},
Call: devAppCall(runner), PostMount: devAppMeta(tool),
})
// 写(升格 Risk = 声明确认):ConfirmRisk + Schema Safety 同源
// 写:同一个 SafetySpec 同时驱动确认与 Schema
NewLeafCommand(LeafSpec{
Use: "publish", Short: "…", Tool: "…",
Flags: []LeafFlag{ /* … */ },
Risk: LeafRiskWrite, // 声明:非空 → dws.schema.risk=write
Safety: cli.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "user_required", Idempotency: "unknown",
},
Call: devAppCall(runner), PostMount: devAppMeta(tool),
})
// 写(今日未升格 Risk):确认走 annotate,见 §1.3 —— 不是「声明了写」
// 迁移期旧写命令:确认走 annotate,见 §1.3 —— 不是新声明面
NewLeafCommand(LeafSpec{
Use: "create", /* Flags… */,
Validate: func(cmd *cobra.Command, _ []string) error {
@@ -122,7 +129,7 @@ NewLeafCommand(LeafSpec{
})
```
**空 `Risk` 的含义**:运行时等同只读确认(不提示),且 **不**嵌入 `dws.schema.risk`。因此「会改状态却留空 Risk」**不是**合法声明;必须要么写非空 `Risk`,要么按 §1.3 标注 gate。
**空 `Safety` 的含义**:cmdcore 为兼容旧只读叶保留 `read/low/not_required/idempotent` 默认。因此「会改状态却留空 Safety」**不是**合法声明;新 Leaf 必须写完整 SafetySpec,未迁移旧路径则按 §1.3 标注 gate。
### 1.3 人工标注(annotate):声明的补充通道
@@ -130,10 +137,10 @@ NewLeafCommand(LeafSpec{
| 标注手段 | 典型值 | 何时用 |
|---|---|---|
| `cli.AnnotateRuntimeGate` / `devAppMetaWrite` | `dws.schema.runtime_gate=devAppRequireWriteGuard` | 写命令走 write-guard、尚未 `Risk` 升格(`HOM-S2`) |
| `cli.AnnotateRuntimeRisk` | `dws.schema.risk=…` | 非 Leaf 构建路径需手写嵌入 Risk(Leaf 应优先字段声明) |
| `cli.AnnotateRuntimeGate` / `devAppMetaWrite` | `dws.schema.runtime_gate=devAppRequireWriteGuard` | 尚未迁移到 SafetySpec 的旧写命令(`HOM-S2`) |
| `cli.AnnotateRuntimeRisk` | `dws.schema.risk=…` | Shortcut 暂存的旧兼容路径;cmdcore/Leaf 禁止新增 |
| `cli.AnnotateRuntimeFlag` / Constraints | 与 embed 同形 | 手写 cobra 叶补齐表面(长期应迁入 Contract) |
| reviewed `schema_hints/metadata` Safety | effect/risk/confirmation | 迁移期遗留;受管命令收敛后让位于 Risk/gate |
| reviewed `schema_hints/metadata` Safety | effect/risk/confirmation | 迁移期遗留;受管命令收敛后让位于 Safety/gate |
标注与声明冲突时:**Contract 声明胜**(路径 A)。标注不得发明未注册的 CLI flag。
@@ -149,8 +156,7 @@ NewLeafCommand(LeafSpec{
| Parameters.`interface_*` | 评审源 | MCP meta / bindings;**不造 flag** |
| Constraints | **声明** | `Constraints` |
| Positionals | **声明** 或显式 annotate | 目标 `Args`;禁止推断 |
| Safety.`effect/risk/confirmation` | **声明**非空 `Risk` **或** `runtime_gate` 标注(或迁移期 reviewed Safety) | 空 Risk 不嵌入 |
| Safety.`idempotency` | 评审源 | hints metadata |
| Safety.`effect/risk/confirmation/idempotency` | **声明**完整 `Safety`,或迁移期 `runtime_gate` / reviewed Safety | 四字段独立;不得互相推导 |
| DryRun | 评审源 | dry-run capabilities registry |
| Interface | 评审源 | MCP + agent metadata |
| Selection | 评审源 | `schema_hints/selection` |
@@ -169,7 +175,7 @@ NewLeafCommand(LeafSpec{
| use_when / avoid_when / examples / agent_summary 文案 | `schema_hints/selection` | Schema selection |
| RPC tool 形状、`interface_ref`、interface 描述 | `schema_mcp_metadata` + `schema_parameter_bindings` | Schema `interface_*` 字段;**不得创建 flag** |
| 参数描述 overlay(可选) | `schema_hints/metadata` 仅补充 usage 文案 | 与 Contract/cobra 冲突时 **Contract/cobra 胜** |
| 遗留 Safety 文案(迁移期) | 今日仍可读 `schema_hints/metadata` safety | 受管命令收敛后以 Contract.Risk / runtime_gate 为准(见 §4) |
| 遗留 Safety 文案(迁移期) | 今日仍可读 `schema_hints/metadata` safety | 受管命令收敛后以 Contract.Safety / runtime_gate 为准(见 §4) |
| dry-run 正能力 | reviewed dry-run registry | Schema `dry_run` |
| positionals | Contract Args / 显式 annotate | Schema `positionals` |
| FieldProvenance | 组装派生 | Schema provenance(与值一致) |
@@ -180,9 +186,9 @@ Identity 与 selection **刻意不**由 Contract 取代(RFC 决策 8 / schema
已具备:
- Flags / ConstParams / Constraints → 注册、校验、`ConstraintHelp`、运行时 `ConfirmRisk`(cmdcore);
- Flags / ConstParams / Constraints → 注册、校验与 `ConstraintHelp`;SafetySpec → 运行时 `ConfirmSafety`(cmdcore);
- Call / Execute 作为执行体;业务参数不得在 Call 内装配(helpers 门禁);
- **Contract → Schema 注解嵌入**(`embedContractIntoSchema`):`dws.schema.contract` / property / type / required / constraints;显式 Risk → `dws.schema.risk` 并在 Schema 组装时 overlay Safety。
- **Contract → Schema 嵌入**:参数/约束写原生 annotation,SafetySpec 与 SchemaDecl 注册为类型化 Contract Final 并由 Schema 组装透传。
仍缺:
@@ -192,8 +198,8 @@ Identity 与 selection **刻意不**由 Contract 取代(RFC 决策 8 / schema
已落地(写命令确认语义,`HOM-S2` 起点):
- `AnnotateRuntimeGate` / `dws.schema.runtime_gate`;Leaf `PostMount: devAppMetaWrite`;手写 delete/robot 等同路径显式标注;
- Schema 组装在无 Contract Risk 但有 gate 时 overlay `confirmation=user_required`(`applyContractGateToSafety`);
- AST/mount 测试:Validate 含 `devAppRequireWriteGuard` ⇒ PostMount 必须 `devAppMetaWrite`;挂载叶须 Risk 或 runtime_gate。
- Schema 组装在无 Contract Safety 但有 gate 时 overlay `confirmation=user_required`(`applyContractGateToSafety`);
- AST/mount 测试:新 Leaf 须声明完整 SafetySpec;尚未迁移的旧路径须有 runtime_gate。
## 4. Schema 投影与 Safety 门禁规划
+8 -8
View File
@@ -59,7 +59,7 @@ v4 的变更,全部来自第一轮独立评审并经过代码级验证:
13. 每一次受管的后端/输出副作用都必须由调用级许可(permit)把关;不要把裸输入流或裸后端 helper 暴露给受管 Handler。
14. 保持遗留行为 bug-for-bug 兼容,**除非**该行为本身是正确性缺陷。§3.4 的非交互确认要在差分门禁能够要求它之前修好,因为兼容门禁会保护它所度量的一切。
15. 把「没有可用的交互输入」当作一种独立的确认结果,与接受和拒绝都不同。
16. **flag / help / schema 参数面同源取路径 A,且 Contract 必须嵌入 Schema**:Contract(LeafSpec 门面)经 `cmdcore.NewCommand` 写入 cobra Schema 注解,供 catalog/agent metadata 组装消费;MCP meta 不得创建 CLI flag;1:1 MCP 透传仅作可选子集。**硬规则**:每份 help/Schema 事实须 **声明 OR 人工标注**,禁止纯推断。**声明** = `LeafSpec`/`CommandSpec` 数据字段(`Flags`/`Constraints`/非空 `Risk`/…),钩子不算声明;未设 `Risk` 的写命令须 `runtime_gate` 标注。见 [`flag-help-schema-homology.md`](flag-help-schema-homology.md) §1.1–§1.3。
16. **flag / help / schema 参数面同源取路径 A,且 Contract 必须嵌入 Schema**:Contract(LeafSpec 门面)经 `cmdcore.NewCommand` 写入 cobra Schema 注解并注册类型化 Final,供 catalog/agent metadata 组装消费;MCP meta 不得创建 CLI flag;1:1 MCP 透传仅作可选子集。**硬规则**:每份 help/Schema 事实须 **声明 OR 人工标注**,禁止纯推断。**声明** = `LeafSpec`/`CommandSpec` 数据字段(`Flags`/`Constraints`/`Safety`/…),钩子不算声明;未设完整 SafetySpec 的旧写命令须 `runtime_gate` 标注。见 [`flag-help-schema-homology.md`](flag-help-schema-homology.md) §1.1–§1.3。
16. 让 dry-run 输出与 `@file`/stdin 输入成为声明式的契约能力,而不是逐 handler 的代码,这样覆盖率就是框架的属性,而不依赖作者自觉。
17. 从 M1 起要求新命令使用新核心,使框架在 Shortcut 分批迁移之前就已改善代码库。
18. 把 LeafSpec 已上线的 `Bind → Call → callMCPTool` 路径视为「声明可以完成执行」的存在性证明:27 个源声明 / 28 条上线命令,生产环境 `RunE` 用量为零。Shortcut 应当获得同样的绑定词汇,而不是把每个 `Execute` 都包成 Handler。
@@ -156,7 +156,7 @@ return answer == "yes" || answer == "y"
影响范围与归类:
- 全部 168 个 write/high-risk Shortcut 受影响;read 命令从不提示。
- `cmdcore.ConfirmRisk` 已经读取 `cmd.InOrStdin()` 而非进程 stdin,因此它是可测试的;但它套用同一条「EOF 等于拒绝」的规则,所以一旦非交互父进程关闭输入,它受影响的程度完全相同。
- `cmdcore.ConfirmSafety` 读取 `cmd.InOrStdin()` 而非进程 stdin,因此它是可测试的;EOF 会显式返回 `confirmation_required`,不会把未执行误报为成功。
- 这**不是** §5.9 的拒绝策略问题。那个问题是「真实拒绝应当产生哪个退出码」。这里没有任何人拒绝过:拒绝分支在 CLI 最常被使用的那种环境里默认就可达。
- 由于这是用户可见的行为变更,它走 §9 的 approved delta 流程。该流程不依赖 Contract、解析器或 mcpbind,因此这项修复不受新核心的进度约束。
- **因此它作为独立 hotfix(§8 中的 H0)先于本 RFC 的实现序列发布,而不是排在架构工作的第四个 PR。** 这是一个已上线的数据完整性级缺陷:168 条写命令在 agent/CI 环境下静默丢弃写操作并报告成功。把它排在一套架构改造之后,等于让缺陷的存续时间取决于一个与它无关的项目的进度。本 RFC 记录它、给出修复形态与门禁,但**不持有它的排期**;即使本 RFC 被否决或推迟,H0 也应照发。
@@ -304,13 +304,13 @@ Definition(仅声明;不可编译)
1. 业务 flag 只出现在 `Flags`(或领域工具注入 + 同源允许的 annotate),不在 `PostMount`/`Validate` 里 `Flags().String`;
2. 业务参数只由 `Flags`/`ConstParams` 装配,不在 `Call`/`Execute` 里 `params[k]=…`;
3. 写副作用:要么非空 `Risk`(框架 `ConfirmRisk` + Schema overlay),要么显式 `runtime_gate` 标注(今日 write-guard);二者皆无则不合格;
3. 写副作用:新 Leaf 声明完整 `SafetySpec`(框架 `ConfirmSafety` + Schema Final);未迁移旧路径显式标注 `runtime_gate`;二者皆无则不合格;
4. Schema `ToolSpec` 全字段组均落在 §5.0.4 表中某一权威格,禁止无主字段。
#### 5.0.3 与目标 `Contract` 的对应
```text
今日 LeafSpec / CommandSpec.Flags|Constraints|Risk|…
今日 LeafSpec / CommandSpec.Flags|Constraints|Safety|…
≈ 目标 Definition.Contract
今日 Call / Invoke / Orchestrate / RunE
≈ 目标 mcpbind 派发 或 Handler(形态 1/2/3)
@@ -335,7 +335,7 @@ Definition(仅声明;不可编译)
| | `interface_description`, `interface_type`, `interface_default` | 评审源(interface) | `schema_mcp_metadata` + bindings | 否;**不得创建 CLI flag**(`HOM-I1`) |
| **Constraints** | `require_one_of`, `mutually_exclusive`, `require_together` | **声明** | `Constraints` → `AnnotateConstraints` | **是** |
| **Positionals** | 位置参数名/必填/说明 | **声明** 或显式 annotate | 目标 `Args`/`PositionalSpec`;今日少量 cobra Args + 注解 | 受管命令应声明,禁止推断 |
| **Safety** | `effect`, `risk`, `confirmation` | **声明**(非空 `Risk`)**或标注**(`runtime_gate`)或迁移期 reviewed Safety | `Risk` / `AnnotateRuntimeGate` / `schema_hints/metadata` safety | 写路径必须 declare 或 annotate;空 Risk 不嵌入 |
| **Safety** | `effect`, `risk`, `confirmation`, `idempotency` | **声明**(完整 `cli.SafetySpec`)**或标注**(`runtime_gate`)或迁移期 reviewed Safety | `Safety` / `AnnotateRuntimeGate` / `schema_hints/metadata` safety | 四字段独立;confirmation 单独驱动运行时 |
| | `idempotency` | 评审源(或未来 Contract) | reviewed metadata | 今日非框架声明;不得推断 |
| | `effect_source` / provenance | 组装派生物 | resolver 写入 `FieldProvenance` | 派生,不手写 |
| **DryRun** | `preview_kind`, `remote_reads` | 评审源 | `schema_dry_run_capabilities`(正能力声明) | 否;无条目 ≠ 推断「不支持」之外的假能力 |
@@ -350,7 +350,7 @@ Definition(仅声明;不可编译)
冲突规则(路径 A):
- parameters 的 type / required / default / name 集合:**Contract/cobra 胜** hints 与 MCP;
- Safety 的 confirmation/effect:**`Schema.Safety`(Final 声明)胜**;否则非空 Contract `Risk` 胜;再否则 `runtime_gate` 胜「假装 not_required」;最末迁移期 reviewed Safety。声明 Safety 存在时 gate 标注被忽略(声明 > 标注,§5.10);
- Safety 的 effect/risk/confirmation/idempotency:完整 Contract `Safety`(Final 声明)胜;否则 `runtime_gate` 可把旧路径的 confirmation 升为 user_required;最末迁移期 reviewed Safety。Safety 各字段之间不得互推(声明 > 标注,§5.10);
- Identity:**绑定 entry 胜**——Contract 声明必须与之一致,不一致组装报错,不得改写 canonical path;Selection 已声明则声明胜;Interface 不得发明 RPC。
### 5.1 Definition、Contract 与 Handler
@@ -980,7 +980,7 @@ func WithProjector(ResponseProjector) BindOption
- 编码通过 `Param[T]` 捕获的类型键经 `cmdcore.Get[T]` 读取;它从不接收原始字符串或公开 `any` 值。
- 包含/省略通过 `IncludeMode` 显式表达,并与编码正交。flag kind 从不静默决定是否发送 `0`、`false` 或空值。Shortcut 模式 `if rt.Changed(name) { params[…] = … }` 映射为 `WhenExplicit` / `WhenSupplied`;它不得继续以手写形式留在形态 1 候选内。
- 仅载荷默认值留在绑定中,且从不满足 CLI required 规则。
- `Bind` 拒绝 nil/typed-nil 的 Caller 或 ResultSink、未知键、重复后端键、类型不匹配、非法 include/effect 模式、Contract Risk/后端 effect 冲突以及不兼容的默认值。
- `Bind` 拒绝 nil/typed-nil 的 Caller 或 ResultSink、未知键、重复后端键、类型不匹配、非法 include/effect 模式、Contract Safety.Effect/后端 effect 冲突以及不兼容的默认值。
- 绑定选项有不透明内部,且对内置全函数编码器与类型化载荷默认值仅有封闭、具名的构造函数。没有公开的 `func(string) (any, error)` 转换钩子。可失败的用户输入转换属于 Resolve 或 Validator,因此在 Risk 之前完成。
- 「全函数」指编码器对任意 T 值都不返回用户错误、也不 panic;内置编码器对其输入域有穷尽测试。
- 在成功的 Bind、cmdcore Compile 与 Resolve 之后,在派发器内物化载荷不能产生用户校验错误。它仍可能浮现内部不变量失败、后端失败或输出错误。
@@ -1146,7 +1146,7 @@ cmd.RunE = func(cmd *cobra.Command, args []string) error {
| 非 Shortcut 受管定义的 Cobra 命令 Hidden | 可执行 Contract | 挂载的命令可见性与声明匹配 |
| Shortcut 列表成员资格与语义 disposition | 经评审的 Shortcut 可见性解析器 | public/all 列表成员资格与经评审决策匹配 |
| Runtime Schema / Agent 暴露 | 经评审的 CommandRegistry 加精确排除 | 每个暴露叶子解析到活 Contract;排除显式且不重叠 |
| 运行时 Risk 与确认 | 可执行 Contract(非空 `Risk`)或显式 annotate(如 `runtime_gate`);见 §5.0 | 不得靠推断;空 Risk 不嵌入 schema risk;写命令须 Risk 或 gate;Safety 组装优先级 **`Schema.Safety`(Final 显式字段)> `CommandSpec.Safety` 枚举 tier > `Risk.SafetyDefault()` > `runtime_gate`**,高优先命中即忽略低优先;tier 填充含 idempotency(读 idempotent / 写 unknown),故仅声明 Risk/Safety 枚举即自足。**边界**:tier 恒可推导意味着写命令忘声明 `Risk` 会静默发布 read tier(且失去运行时确认)——写命令必须声明 `Risk`;`ConfirmFirst` 无 `Risk` 属 authoring 错误,构造期 panic |
| Safety 与运行时确认 | 可执行 Contract 的完整 `cli.SafetySpec`,或迁移期显式 annotate(如 `runtime_gate`);见 §5.0 | `confirmation` 单独驱动运行时门,`effect` / `risk` / `idempotency` 原样发布,禁止跨字段机械推导;任一 Safety 字段非空时四字段必须齐全,否则构造期 panic;`ConfirmFirst` 只在 `confirmation=user_required` 时合法 |
| 后端 product/tool/载荷绑定 | mcpbind + 后端元数据 | 每个绑定引用真实的 flag/属性 |
| Agent 选择文案(`use_when`、`avoid_when`、摘要) | 经评审的 hints/catalog | 身份解析到活契约 |