// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package helpers import ( "context" "fmt" "strings" "github.com/spf13/cobra" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" ) // contractConfirmSafetyAnnotation marks leaves whose RunE was wrapped by // DeclareLeafMetadata with the same SafetySpec published to ContractFinal. const contractConfirmSafetyAnnotation = "dws.contract.confirm_safety" // contractValidateAnnotation marks leaves that installed DeclareLeafMetadata // Validate. Validate runs inside the same RunE wrapper as ConfirmSafety (not // PreRunE), so direct RunE / proxySubCmd call sites cannot skip it. const contractValidateAnnotation = "dws.contract.validate" // contractConfirmDeferredAnnotation marks user_required leaves that defer // ConfirmSafety to the first deps.Caller.CallTool (no Validate). Local-checkable // commands without an MCP caller must use Validate instead. const contractConfirmDeferredAnnotation = "dws.contract.confirm_deferred" // leaf.go 是叶子命令的统一构建框架(命令框架的 Leaf 门面)。 // // 两种正式模式(共用 LeafSpec 词汇,产出同一 ContractFinal): // // - 完全托管模式 NewLeafCommand:声明 + 执行都归 corecmd(flag 注册、 // 参数投影、ConfirmSafety、派发)。新命令默认走此模式。 // - 声明元数据模式 DeclareLeafMetadata:声明 Safety + Contract(AttachContract), // 不注册 flag、不接管参数投影;可选 Validate 与 ConfirmSafety 同挂在 // RunE 包装器内(Validate 在前,不是 PreRunE——直接调 RunE / // proxySubCmd 会跳过 PreRunE)。当 Safety.Confirmation=user_required 时 // 用**同一份** SafetySpec 包一层 ConfirmSafety,保证执行门禁与 Catalog // 同源。用于执行体必须冻结的既有命令补声明,是迁移态。 // // 每个 API 各自声明: // // - 禁止参数化命令工厂用 use/short/tool 拼出多个真 API 的 // *cobra.Command(含共享 Use/Short/Long/Example/RunE/DeclareLeafMetadata)。 // - 允许私有 RunE 执行 helper(不返回 *cobra.Command),以及 Safety / // Interface 字面量缩写(声明调用点仍须写在每个命令旁)。 // - hint/proxy 重定向不是业务叶子,不要求 ContractFinal。 // // 声明 vs 执行(框架规则,详见 RFC §5.0 / 同源文档 §1.2): // // - 声明 = LeafSpec 数据字段:Flags、Constraints、Safety、ConstParams、 // Use/Short/Long/Example。经 FromLeafSpec → corecmd.New 注册并 // 嵌入 dws.schema.*。 // - 执行 = Validate / Call / RunE / PostMount。钩子消费已装配参数,不得 // 发明 CLI 表面(业务 flag/params)。 // - 标注 = 声明字段装不下时的显式注解(如 write-guard 的 runtime_gate)。 // // LeafSpec 把共性收敛为声明式结构:命令声明 flag 集合与绑定,框架统一校验、 // 装配与投影。默认派发走 MCP 直连;非 MCP 命令通过 Call 注入派发器。复杂 // 命令可用 RunE 逃生舱。 // // 收敛纪律(Phase 2):flag 注册、有效值回退链、required/约束、Safety 确认、 // toolArgs 装配、Schema 投影与帮助渲染均在 internal/corecmd;本文件只做 // LeafSpec→CommandSpec 映射与 dispatch 闭包。等价性由 catalog 漂移门禁与 // leaf/risk/约束单测共同兜底。 // // 迁移纪律:迁入 LeafSpec 时 flag 名、默认值、usage、MarkFlagRequired、 // required 错误格式、toolArgs 键与值必须逐字保持一致。 // LeafFlagKind 是 flag 的值类型(corecmd.FlagKind 的别名)。 type LeafFlagKind = corecmd.FlagKind const ( // LeafString 字符串 flag(默认)。 LeafString = corecmd.KindString // LeafInt 整型 flag;仅在值 != 0 时进入 toolArgs(putInt 语义)。 LeafInt = corecmd.KindInt // LeafBool 布尔 flag;仅在用户显式提供(Changed)时进入 toolArgs,显式 // false 也下发;不参与别名/env 回退链。 LeafBool = corecmd.KindBool // LeafStringSlice 字符串列表 flag;仅在存在非空元素时进入 toolArgs,元素 // 恒 TrimSpace 后过滤空串。 LeafStringSlice = corecmd.KindStringSlice ) // LeafFlag 声明一个 flag 的注册方式与到 MCP toolArgs 的绑定 // (corecmd.FlagSpec 的别名,字段含义见 command 定义)。 type LeafFlag = corecmd.FlagSpec // LeafConstraintKind 是跨 flag 关系约束的类型(corecmd.ConstraintKind 的 // 别名)。取值与 shortcut 框架的 ConstraintKind 逐字一致。 type LeafConstraintKind = corecmd.ConstraintKind const ( // LeafAtLeastOne 要求 Flags 至少提供一个。 LeafAtLeastOne = corecmd.AtLeastOne // LeafExactlyOne 要求 Flags 必须且只能提供一个。 LeafExactlyOne = corecmd.ExactlyOne // LeafMutuallyExclusive 允许 Flags 中最多提供一个。 LeafMutuallyExclusive = corecmd.MutuallyExclusive ) // LeafConstraint 声明一组 flag 的关系约束(corecmd.Constraint 的别名)。框架 // 在 required 校验之后、Validate 钩子之前统一执行;「是否提供」的判定复用有效 // 值回退链(显式主 flag → 别名 → env),即只传兼容别名同样视为已提供。约束 // 同时投影到 Agent Runtime Schema 并渲染进 --help 的「参数约束」段。 type LeafConstraint = corecmd.Constraint // LeafContract 是叶子 Contract 声明(corecmd.ContractDecl 别名)。 // 嵌套字段直接使用 contract.*(InterfaceSpec / ParamDecl / SelectionSpec 等), // 不再保留平行 Decl 类型。 type LeafContract = corecmd.ContractDecl // LeafSpec 是命令框架的 Leaf 声明门面(映射为 corecmd.Spec)。 // // 声明面 = Contract 最终数据源:Flags(含 parameter 字段)、Constraints、 // Safety、ConstParams、Use/Short/Long/Example、Contract(Selection/Interface/…)。 // Catalog 组装透传 ContractFinal;声明路径不再引入评审并行字段。 // // 执行面(不算声明):Validate、Call、RunE、PostMount;Server/Tool 仅路由。 type LeafSpec struct { Use string Short string Long string Example string OutputRollout output.RolloutState // Server 非空时走 callMCPToolOnServer(显式 server 路由),否则走 // callMCPTool(按 product 路由)。Call 非空时两者都被忽略。不是 CLI 声明。 Server string Tool string Flags []LeafFlag // Constraints 是跨 flag 的关系约束(至少一个 / 恰好一个 / 互斥),由 // command 统一校验并投影到 Runtime Schema 与 --help。复杂的条件式校验 // 仍放 Validate 钩子(钩子本身不是约束声明)。 Constraints []LeafConstraint // Safety 直接使用 Agent Runtime Schema 的安全模型。Confirmation 驱动 // 运行时确认,其余字段原样进入 Schema;字段之间不做机械推导。 Safety contract.SafetySpec // ConfirmFirst 为 true 时确认门先于 required/约束/Validate 校验执行 //(devapp 旧版写守卫语义:写命令未带 --yes 时快速失败 // confirmation_required,与参数完整性无关)。默认 false 保持 shortcut // 顺序(先校验,后端调用前再确认)。 ConfirmFirst bool // ConstParams 是与 flag 无关的固定载荷(如 precheckOnly),在 flag 装配 // 之后并入 toolArgs。载荷声明,不上用户 flag 表;从不满足 Required。 ConstParams map[string]any // Contract 是叶子 ContractFinal 声明(identity/selection/interface/dry_run/…)。 Contract LeafContract // Call 是执行体:非空时替代默认 MCP 派发。toolArgs 已由 Flags/ConstParams // 装配完成;Call 不应再写业务参数。分页等横切由领域工具处理,不进声明。 Call func(cmd *cobra.Command, tool string, args map[string]any) error ResultCall func(cmd *cobra.Command, tool string, args map[string]any) (output.CommandResult, error) // Validate 是编排钩子(条件式校验),不是声明面。单 flag 转换用 // LeafFlag.Transform;可声明的互斥/至少一个应写 Constraints。 Validate func(cmd *cobra.Command, args []string) error // RunE 非空时完全自定义执行体(逃生舱);表面事实仍须 Flags/Contract 声明。 RunE func(cmd *cobra.Command, args []string) error // PostMount 是挂载收尾钩子(领域工具等),不是声明面。 // 业务 flag 必须写在 Flags;分页由领域工具注入。 PostMount func(cmd *cobra.Command) } // NewLeafCommand 是命令框架的「完全托管模式」:经 FromLeafSpec 归一为 // corecmd.Spec 后交由统一构建器 corecmd.New 编排(flag 注册、 // 约束声明检查、Schema 投影、帮助渲染、required/约束校验、Safety 确认、 // toolArgs 装配)。本函数只保留 LeafSpec→CommandSpec 的映射与 MCP dispatch // 闭包(callMCPTool/OnServer/Call)。所有 LeafSpec 命令(含 devapp 全部叶子) // 由此统一流经 command 单一 spec 路径。 func NewLeafCommand(spec LeafSpec) *cobra.Command { return corecmd.New(FromLeafSpec(spec)) } // DeclareLeafMetadata 是命令框架的「声明元数据模式」:把 LeafSpec 的声明面 // (Safety + Contract)挂到既有命令上——不注册 flag、不接管参数投影。可选的 // Validate 与 ConfirmSafety 同挂在 RunE 包装器内(Validate 在前),保证 // 本地可判定校验发生在确认之前(RFC §5.1 / §5.6),且直接调 RunE / // proxySubCmd 委派不会跳过校验。 // // user_required 确认时机: // - 提供 Validate:Validate → ConfirmSafety → 原 RunE(本地副作用命令) // - 未提供 Validate:原 RunE 先跑(含缺参校验),ConfirmSafety 推迟到 // 首次 deps.Caller.CallTool。无 Caller 时回退 ConfirmSafety → 原 RunE。 // 有 Caller 但 RunE 成功返回且从未 CallTool:fail-closed(禁止「成功 // 返回却未确认」——此时副作用可能已发生,事后 Confirm 太晚)。本地 // 副作用叶必须补 Validate,或把副作用放进 gated CallTool。 // // 该模式是迁移态而非终态:命令具备条件时应升级为 NewLeafCommand。传入 // Flags/Constraints/ConstParams/Call/RunE/PostMount 或空 Contract 会 panic, // 防止误用成半接管(Validate 是唯一允许的执行钩子)。 func DeclareLeafMetadata(cmd *cobra.Command, spec LeafSpec) *cobra.Command { if cmd == nil { panic("DeclareLeafMetadata: cmd is nil") } name := cmd.Name() if len(spec.Flags) > 0 { panic(fmt.Sprintf("DeclareLeafMetadata(%q): Flags must be empty (metadata-only mode)", name)) } if len(spec.Constraints) > 0 { panic(fmt.Sprintf("DeclareLeafMetadata(%q): Constraints must be empty (metadata-only mode)", name)) } if len(spec.ConstParams) > 0 { panic(fmt.Sprintf("DeclareLeafMetadata(%q): ConstParams must be empty (metadata-only mode)", name)) } if spec.Call != nil { panic(fmt.Sprintf("DeclareLeafMetadata(%q): Call must be nil (metadata-only mode)", name)) } if spec.ResultCall != nil { panic(fmt.Sprintf("DeclareLeafMetadata(%q): ResultCall must be nil (metadata-only mode)", name)) } if spec.RunE != nil { panic(fmt.Sprintf("DeclareLeafMetadata(%q): RunE must be nil (metadata-only mode)", name)) } if spec.PostMount != nil { panic(fmt.Sprintf("DeclareLeafMetadata(%q): PostMount must be nil (metadata-only mode)", name)) } if spec.ConfirmFirst { panic(fmt.Sprintf("DeclareLeafMetadata(%q): ConfirmFirst must be false (metadata-only mode)", name)) } if spec.Server != "" || spec.Tool != "" { panic(fmt.Sprintf("DeclareLeafMetadata(%q): Server/Tool must be empty (metadata-only mode)", name)) } if spec.Contract.Empty() { panic(fmt.Sprintf("DeclareLeafMetadata(%q): Contract is required", name)) } corecmd.AttachContract(cmd, spec.Safety, spec.Contract, cmd.Short, cmd.Long) if spec.OutputRollout != "" { output.SetCommandRollout(cmd, spec.OutputRollout) } confirm := strings.TrimSpace(spec.Safety.Confirmation) == "user_required" if spec.Validate == nil && !confirm { return cmd } // cmd.Annotations is always non-nil here: AttachContract above registers the // runtime-contract annotation on every declared leaf via the cli seam. rt := &contractRuntime{validate: spec.Validate, confirm: confirm} if confirm { rt.safety = spec.Safety cmd.Annotations[contractConfirmSafetyAnnotation] = "true" if spec.Validate == nil { cmd.Annotations[contractConfirmDeferredAnnotation] = "true" } } if spec.Validate != nil { cmd.Annotations[contractValidateAnnotation] = "true" } storeContractRuntime(cmd, rt) installContractRunEPipeline(cmd, rt) return cmd } // installContractRunEPipeline wraps RunE so Validate and ConfirmSafety share // one layer (Validate first). Idempotent for the confirm annotation. func installContractRunEPipeline(cmd *cobra.Command, rt *contractRuntime) { if cmd == nil || rt == nil { panic("installContractRunEPipeline: nil cmd/runtime") } inner := cmd.RunE if inner == nil { panic(fmt.Sprintf("installContractRunEPipeline(%q): RunE is nil", cmd.Name())) } cmd.RunE = func(c *cobra.Command, args []string) error { if rt.validate != nil { if err := rt.validate(c, args); err != nil { return err } } if !rt.confirm { return inner(c, args) } // OR across flagsets (root persistent --dry-run must not be shadowed by // a leaf-local --dry-run defaulting to false). if corecmd.BoolFlag(c, "dry-run") || (deps != nil && deps.Caller != nil && deps.Caller.DryRun()) { return inner(c, args) } if rt.validate != nil { // Local-checkable validation already passed; confirm then execute. if err := corecmd.ConfirmSafety(c, rt.safety); err != nil { return err } return inner(c, args) } // No Validate: defer confirm to first deps.Caller.CallTool so RunE-local // mustFlag checks can fail first. Without a caller, fall back to // confirm-then-run (commands on this path should add Validate). if deps == nil || deps.Caller == nil { if err := corecmd.ConfirmSafety(c, rt.safety); err != nil { return err } return inner(c, args) } prev := deps.Caller gate := &contractConfirmCaller{inner: prev, cmd: c, safety: rt.safety} deps.Caller = wrapContractConfirmCaller(gate, prev) defer func() { deps.Caller = prev }() if err := inner(c, args); err != nil { return err } if !gate.confirmed { // Fail closed: RunE already returned successfully. A post-RunE // ConfirmSafety cannot undo local side effects, and --yes would // falsely green-light them after the fact. Side-effect leaves must // declare Validate (confirm-before-RunE) or dispatch through the // gated CallTool path. Dry-run already short-circuits above (before // the deferred confirm wrapper is installed), so it cannot reach // this fail-closed branch. return fmt.Errorf("contract: user_required confirmation was never obtained via CallTool for %q; add Validate for local side effects or dispatch through deps.Caller.CallTool", c.Name()) } return nil } } // contractConfirmCaller defers ConfirmSafety until the first CallTool, so // DeclareLeafMetadata RunE can validate flags first. type contractConfirmCaller struct { inner edition.ToolCaller cmd *cobra.Command safety contract.SafetySpec confirmed bool } func wrapContractConfirmCaller(gate *contractConfirmCaller, inner edition.ToolCaller) edition.ToolCaller { if read, ok := inner.(edition.ReadToolCaller); ok { return &contractConfirmReadCaller{contractConfirmCaller: gate, read: read} } return gate } func (c *contractConfirmCaller) CallTool(ctx context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { if !c.confirmed { if err := corecmd.ConfirmSafety(c.cmd, c.safety); err != nil { return nil, err } c.confirmed = true } return c.inner.CallTool(ctx, productID, toolName, args) } func (c *contractConfirmCaller) Format() string { return c.inner.Format() } func (c *contractConfirmCaller) DryRun() bool { return c.inner.DryRun() } func (c *contractConfirmCaller) Fields() string { return c.inner.Fields() } func (c *contractConfirmCaller) JQ() string { return c.inner.JQ() } // contractConfirmReadCaller preserves optional ReadToolCaller without gating // reads behind ConfirmSafety (RFC allows pre-confirm reads when needed). type contractConfirmReadCaller struct { *contractConfirmCaller read edition.ReadToolCaller } func (c *contractConfirmReadCaller) CallReadTool(ctx context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) { return c.read.CallReadTool(ctx, productID, toolName, args) } // HasContractConfirmSafety reports whether DeclareLeafMetadata installed the // ConfirmSafety wrapper for a user_required SafetySpec. func HasContractConfirmSafety(cmd *cobra.Command) bool { return cmd != nil && cmd.Annotations != nil && cmd.Annotations[contractConfirmSafetyAnnotation] == "true" } // HasContractValidate reports whether DeclareLeafMetadata installed a Validate // hook (confirm-after-Validate mode on the RunE wrapper). func HasContractValidate(cmd *cobra.Command) bool { return cmd != nil && cmd.Annotations != nil && cmd.Annotations[contractValidateAnnotation] == "true" } // HasContractConfirmDeferred reports whether ConfirmSafety is deferred to the // first deps.Caller.CallTool (no Validate on the leaf). func HasContractConfirmDeferred(cmd *cobra.Command) bool { return cmd != nil && cmd.Annotations != nil && cmd.Annotations[contractConfirmDeferredAnnotation] == "true" } // FromLeafSpec 把 LeafSpec 归一为统一的 corecmd.Spec。契约字段 // (Flags/Constraints/Safety)与编排钩子(Validate/PostMount/RunE)直接透传; // dispatch 收敛为一个闭包:Call 优先,其次显式 Server 路由,最后按 product // 自动路由。RunE 逃生舱存在时不设 Dispatch(与旧行为一致)。 func FromLeafSpec(spec LeafSpec) corecmd.Spec { if spec.Call != nil && spec.ResultCall != nil { panic(fmt.Sprintf("command %q must not declare both Call and ResultCall", spec.Use)) } cs := corecmd.Spec{ Use: spec.Use, Short: spec.Short, Long: spec.Long, Example: spec.Example, OutputRollout: spec.OutputRollout, Flags: spec.Flags, Constraints: spec.Constraints, Safety: spec.Safety, ConfirmFirst: spec.ConfirmFirst, ConstParams: spec.ConstParams, Contract: spec.Contract, Validate: spec.Validate, PostMount: spec.PostMount, RunE: spec.RunE, } if spec.RunE == nil { if spec.ResultCall != nil { cs.ResultInvoke = func(c *corecmd.Ctx, toolArgs map[string]any) (output.CommandResult, error) { return spec.ResultCall(c.Command(), spec.Tool, toolArgs) } } else { cs.Invoke = func(c *corecmd.Ctx, toolArgs map[string]any) error { if spec.Call != nil { return spec.Call(c.Command(), spec.Tool, toolArgs) } if spec.Server != "" { return callMCPToolOnServer(spec.Server, spec.Tool, toolArgs) } return callMCPTool(spec.Tool, toolArgs) } } } return cs }