{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.anguskit.com/schema/anguscopilot/1.0.0/workflow.json",
  "title": "AngusCopilot Workflow Specification",
  "description": "AngusCopilot Workflow 顶层 Spec（specification: anguscopilot-workflow/1.0.0，§5.6）。",

  "$comment": "P3·1.5 扩展命名空间约定（评审 12 号）：spec 的两类外部扩展点：(a) 以 `x-` 打头的自由键 —— 只在明确用 patternProperties 声明的位置生效（top-level / spec / settings / trigger / Node / edges[i] / RetrySpec / FailureHandler），承担『向前兼容的规范扩展点』语义、跨 minor 版本保证保留；(b) Node.config / trigger.config / spec.variables 等『节点作者自由 payload』位置，走 additionalProperties=true 全放行，任何键都保留但不承担规范级兼容承诺（节点 executor 自己解释）。第三方作者写扩展时：跨版本兼容/被规范识别 → 用 `x-my-thing`；纯节点内部约定 → 直接在 config 里写。以后新增 spec 字段时注意在两侧都补上说明，别让第三方误以为 config 里的自定义键也享受同级别兼容承诺。",

  "type": "object",
  "required": ["specification", "info", "spec"],

  "properties": {
    "specification": {
      "$ref": "common.json#/$defs/SpecificationField",
      "pattern": "^anguscopilot-workflow\\/(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:alpha|beta|rc)(?:\\.(?:0|[1-9]\\d*))?)?$"
    },

    "info": { "$ref": "info.json" },

    "spec": {
      "type": "object",
      "required": ["id", "nodes"],
      "properties": {

        "id": {
          "description": "工作流唯一 ID（租户内）。",
          "$ref": "common.json#/$defs/Identifier"
        },

        "description": { "type": "string", "maxLength": 2048 },

        "trigger": {
          "description": "工作流触发器。Agent 内联调用时可省略。",
          "type": "object",
          "required": ["type"],
          "properties": {
            "type": {
              "type": "string",
              "enum": ["MANUAL", "WEBHOOK", "CRON", "MQ_EVENT", "AGENT_INLINE"]
            },
            "config": { "type": "object", "additionalProperties": true }
          },
          "patternProperties": { "^x-": true },
          "unevaluatedProperties": false,

          "$comment": "评审 12 号 P1.1：按 type 分派 config 子模式，把'CRON 忘配 cronExpression / WEBHOOK 忘配 path / MQ_EVENT 忘配 topic'这类问题在 schema 层就拦住（此前只能等运行时 CronTriggerService/MqEventTriggerService register() 抛 IllegalArgumentException）。字段名严格对齐引擎侧真实读取的字段。",
          "allOf": [
            {
              "if": { "properties": { "type": { "const": "WEBHOOK" } }, "required": ["type"] },
              "then": {
                "properties": {
                  "config": {
                    "type": "object",
                    "required": ["path"],
                    "properties": {
                      "path": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1024,
                        "pattern": "^/[A-Za-z0-9._~/\\-]*$",
                        "description": "Webhook 路径，必须以 / 开头。"
                      },
                      "method": {
                        "type": "string",
                        "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
                        "default": "POST"
                      },
                      "secretRef": {
                        "type": "string",
                        "pattern": "^(vault|env|aws|aliyun|gcp|azure|file):/.+$",
                        "description": "指向 HMAC-SHA256 签名密钥的引用（C-5）；未指定则不做签名校验但记 WARN。"
                      }
                    }
                  }
                }
              }
            },
            {
              "if": { "properties": { "type": { "const": "CRON" } }, "required": ["type"] },
              "then": {
                "properties": {
                  "config": {
                    "type": "object",
                    "required": ["cronExpression"],
                    "properties": {
                      "cronExpression": {
                        "type": "string",
                        "minLength": 9,
                        "maxLength": 256,
                        "description": "Spring CronTrigger 兼容表达式（5-7 字段），例如 '0 0/30 * * * ?'。"
                      },
                      "timeZone": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "IANA 时区标识（例如 Asia/Shanghai），缺省使用 JVM 默认时区。"
                      }
                    }
                  }
                }
              }
            },
            {
              "if": { "properties": { "type": { "const": "MQ_EVENT" } }, "required": ["type"] },
              "then": {
                "properties": {
                  "config": {
                    "type": "object",
                    "required": ["topic"],
                    "properties": {
                      "topic": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "AngusInfra queue topic（对齐 MqEventTriggerService 真实读取字段）。"
                      },
                      "eventType": {
                        "type": "string",
                        "maxLength": 256,
                        "description": "可选事件类型过滤，匹配消息 headers.eventType。"
                      }
                    }
                  }
                }
              }
            },
            {
              "if": {
                "properties": { "type": { "enum": ["MANUAL", "AGENT_INLINE"] } },
                "required": ["type"]
              },
              "then": {
                "$comment": "MANUAL 由用户/API 显式触发，AGENT_INLINE 由 autonomy.level=WORKFLOW 的 Agent 桥接触发，两者都不要求 config 字段；显式声明允许为空以便与前 3 种形成互斥。"
              }
            }
          ]
        },

        "variables": {
          "description": "工作流全局变量初始值。可被 SET_VARIABLE 节点改写。",
          "type": "object",
          "additionalProperties": true
        },

        "settings": {
          "description": "工作流全局设置。",
          "type": "object",
          "properties": {
            "maxExecutionSeconds": {
              "type": "integer",
              "minimum": 1,
              "maximum": 86400,
              "default": 600
            },
            "maxNestingDepth": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 20,
              "description": "SubWorkflow 嵌套深度上限（与 MAX_GLOBAL_DEPTH 一致）。"
            },
            "onFailure": {
              "type": "string",
              "enum": ["STOP", "CONTINUE", "ROLLBACK"],
              "default": "STOP"
            },
            "defaultRetry": { "$ref": "#/$defs/RetrySpec" }
          },
          "patternProperties": { "^x-": true },
          "unevaluatedProperties": false
        },

        "nodes": {
          "description": "工作流节点。必须恰好包含一个 START 节点与至少一个 END 节点。",
          "type": "array",
          "minItems": 2,
          "items": { "$ref": "#/$defs/Node" },
          "allOf": [
            {
              "$comment": "START 节点有且仅有一个（与 NodeType.START 的 JavaDoc 对齐）。",
              "contains": { "properties": { "type": { "const": "START" } }, "required": ["type"] },
              "minContains": 1,
              "maxContains": 1
            },
            {
              "$comment": "END 节点至少一个。",
              "contains": { "properties": { "type": { "const": "END" } }, "required": ["type"] },
              "minContains": 1
            }
          ]
        },

        "edges": {
          "description": "节点连边（DAG）。同一 (from,to) 二元组可有多条边以表达并行分支。",
          "type": "array",
          "items": {
            "type": "object",
            "required": ["from", "to"],
            "properties": {
              "from": { "$ref": "common.json#/$defs/Identifier" },
              "to":   { "$ref": "common.json#/$defs/Identifier" },
              "condition": {
                "description": "边条件表达式（CONDITION/SWITCH 节点出边）。评审 12 号 P2.1.3 更正：真源实现 ExpressionEngine 并非 SpEL——它是一个仅支持 ${namespace.path} 变量替换的自定义语言，命名空间限于 input/variables|vars/nodes/context/spec/memory/secrets 六种，语法上无法表达类型引用 T(...)/方法调用 .foo()/构造器 new Foo()，因此不存在 SpEL 常见的注入面。语义上'条件真值'定义为求值结果的 Truthy：非空字符串/非零数字/true/非空 map/非空 list 均为真。",
                "type": "string",
                "maxLength": 4096
              },
              "branch": {
                "description": "PARALLEL 节点的命名分支。",
                "$ref": "common.json#/$defs/Identifier"
              }
            },
            "patternProperties": { "^x-": true },
            "unevaluatedProperties": false
          }
        }
      },
      "patternProperties": { "^x-": true },
      "unevaluatedProperties": false
    }
  },

  "patternProperties": { "^x-": true },
  "unevaluatedProperties": false,

  "$defs": {

    "RetrySpec": {
      "type": "object",
      "properties": {
        "maxAttempts":      { "type": "integer", "minimum": 1, "maximum": 10, "default": 1 },
        "initialBackoffMs": { "type": "integer", "minimum": 0, "default": 1000 },
        "multiplier":       { "type": "number",  "minimum": 1.0, "default": 2.0 },
        "maxBackoffMs":     { "type": "integer", "minimum": 0, "default": 30000 }
      },
      "patternProperties": { "^x-": true },
      "unevaluatedProperties": false
    },

    "FailureHandler": {
      "type": "object",
      "required": ["strategy"],
      "properties": {
        "strategy": {
          "type": "string",
          "enum": ["STOP", "SKIP", "FALLBACK", "GOTO"]
        },
        "fallbackNodeId": { "$ref": "common.json#/$defs/Identifier" },
        "gotoNodeId":     { "$ref": "common.json#/$defs/Identifier" },
        "defaultOutput":  { "type": "object", "additionalProperties": true }
      },
      "patternProperties": { "^x-": true },
      "unevaluatedProperties": false,

      "$comment": "FALLBACK 必须给 fallbackNodeId；GOTO 必须给 gotoNodeId。",
      "allOf": [
        {
          "if": { "properties": { "strategy": { "const": "FALLBACK" } }, "required": ["strategy"] },
          "then": { "required": ["fallbackNodeId"] }
        },
        {
          "if": { "properties": { "strategy": { "const": "GOTO" } }, "required": ["strategy"] },
          "then": { "required": ["gotoNodeId"] }
        }
      ]
    },

    "Node": {
      "type": "object",
      "required": ["id", "type"],
      "properties": {
        "id":   { "$ref": "common.json#/$defs/Identifier" },
        "name": { "type": "string", "maxLength": 256 },
        "type": {
          "type": "string",
          "enum": [
            "START", "END",
            "LLM", "AGENT", "CAPABILITY", "HTTP", "CODE",
            "CONDITION", "SWITCH", "LOOP", "WHILE", "PARALLEL",
            "WAIT", "SUB_WORKFLOW", "SET_VARIABLE", "KNOWLEDGE", "INTEGRATION"
          ]
        },
        "config": {
          "description": "节点配置（schema 取决于 type；详见各节点子文档）。",
          "type": "object",
          "additionalProperties": true
        },
        "retry":          { "$ref": "#/$defs/RetrySpec" },
        "timeoutSeconds": { "type": "integer", "minimum": 1, "maximum": 86400 },
        "onFailure":      { "$ref": "#/$defs/FailureHandler" },
        "onTimeout":      { "$ref": "#/$defs/FailureHandler" }
      },
      "patternProperties": { "^x-": true },
      "unevaluatedProperties": false,

      "$comment": "除 START/END 之外的所有节点都要求显式 config；WAIT/SUB_WORKFLOW/CAPABILITY/AGENT/HTTP/CODE 等节点还需满足下方专属子模式。",
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "enum": ["LLM", "AGENT", "CAPABILITY", "HTTP", "CODE",
                         "CONDITION", "SWITCH", "LOOP", "WHILE", "PARALLEL",
                         "WAIT", "SUB_WORKFLOW", "SET_VARIABLE", "KNOWLEDGE", "INTEGRATION"]
              }
            },
            "required": ["type"]
          },
          "then": { "required": ["config"] }
        },
        {
          "if": { "properties": { "type": { "const": "INTEGRATION" } }, "required": ["type"] },
          "then": {
            "$comment": "P2 集成连接器节点（B-2 schema 修复：引擎 IntegrationNodeExecutor 早就完整实现，但 schema 之前一直缺 INTEGRATION 枚举值，导致走 schema 校验的提交路径无法使用这个类型）",
            "properties": {
              "config": {
                "type": "object",
                "required": ["connector"],
                "properties": {
                  "connector":  { "type": "string", "minLength": 1, "maxLength": 256 },
                  "operation":  { "type": "string", "maxLength": 256 },
                  "inputs":     { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "WAIT" } }, "required": ["type"] },
          "then": {
            "properties": {
              "config": {
                "type": "object",
                "required": ["waitType"],
                "properties": {
                  "waitType":       { "type": "string", "enum": ["DELAY", "APPROVAL", "EVENT"] },
                  "timeoutSeconds": { "type": "integer", "minimum": 0 },
                  "approvers": {
                    "type": "array",
                    "items": { "type": "string", "minLength": 1, "maxLength": 256 },
                    "uniqueItems": true,
                    "minItems": 1
                  },
                  "eventType": { "type": "string", "minLength": 1, "maxLength": 256 }
                },
                "allOf": [
                  {
                    "$comment": "DELAY 必须提供 timeoutSeconds>=1，否则等待无语义。",
                    "if": { "properties": { "waitType": { "const": "DELAY" } }, "required": ["waitType"] },
                    "then": {
                      "required": ["timeoutSeconds"],
                      "properties": { "timeoutSeconds": { "type": "integer", "minimum": 1 } }
                    }
                  },
                  {
                    "$comment": "APPROVAL 必须给出至少一个审批人。",
                    "if": { "properties": { "waitType": { "const": "APPROVAL" } }, "required": ["waitType"] },
                    "then": { "required": ["approvers"] }
                  },
                  {
                    "$comment": "EVENT 必须指定非空 eventType。",
                    "if": { "properties": { "waitType": { "const": "EVENT" } }, "required": ["waitType"] },
                    "then": { "required": ["eventType"] }
                  }
                ]
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "SUB_WORKFLOW" } }, "required": ["type"] },
          "then": {
            "properties": {
              "config": {
                "type": "object",
                "required": ["workflowRef"],
                "properties": {
                  "workflowRef": {
                    "type": "string",
                    "pattern": "^workflow:\\/\\/[^\\s]+$"
                  },
                  "inputMapping": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "CAPABILITY" } }, "required": ["type"] },
          "then": {
            "properties": {
              "config": {
                "type": "object",
                "required": ["ref"],
                "properties": {
                  "ref":   { "$ref": "common.json#/$defs/CapabilityRef" },
                  "input": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "AGENT" } }, "required": ["type"] },
          "then": {
            "$comment": "评审更正（错误码/节点清单评审）：此前 required 是 agentId，但真源实现 AgentNodeExecutor.execute() 实际按 (name, version, scope) 三元组查 AgentRuntime，从未读取过 agentId 字段；前端 catalog.tsx 的 ai.agent 节点与 flow-converter 早已对齐 name/version/scope，唯独本 schema 落后于实现。已改为要求 name+version，agentId 更名保留为已弃用别名不再收录。",
            "properties": {
              "config": {
                "type": "object",
                "required": ["name", "version"],
                "properties": {
                  "name":        { "type": "string", "minLength": 1, "maxLength": 256, "description": "Agent 名称（AgentRuntime.findByName 的查找键之一）。" },
                  "version":     { "type": "string", "minLength": 1, "maxLength": 64, "description": "Agent 版本（精确匹配，非 semver range）。" },
                  "scope":       { "type": "string", "enum": ["PUBLIC", "WORKSPACE", "PERSONAL"], "description": "可选，缺省 WORKSPACE。" },
                  "tenantId":    { "type": "string", "description": "可选，缺省取执行上下文 tenantId。" },
                  "workspaceId": { "type": "string", "description": "可选，缺省取运行时上下文 workspaceId。" },
                  "input":       { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "HTTP" } }, "required": ["type"] },
          "then": {
            "properties": {
              "config": {
                "type": "object",
                "required": ["url", "method"],
                "properties": {
                  "url":     { "type": "string", "format": "uri", "maxLength": 4096 },
                  "method":  { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] },
                  "headers": { "type": "object", "additionalProperties": { "type": "string" } },
                  "body":    {}
                }
              }
            }
          }
        },
        {
          "if": { "properties": { "type": { "const": "CODE" } }, "required": ["type"] },
          "then": {
            "$comment": "CODE 节点默认永久禁用（OWASP A03）：angus-copilot-runtime 内置的 DisabledCodeNodeExecutor 无条件返回失败（ANGUS-WORKFLOW-7010），不存在任何配置属性可以启用它。唯一的启用方式是由私有化/可信部署方自行实现并注册一个沙箱化的 NodeExecutor（supportedType()==CODE）覆盖默认实现——这是代码级扩展点，不是配置开关。",
            "properties": {
              "config": {
                "type": "object",
                "required": ["language", "script"],
                "properties": {
                  "language": { "type": "string", "enum": ["javascript", "groovy"] },
                  "script":   { "type": "string", "maxLength": 65536 }
                }
              }
            }
          }
        }
      ]
    }
  }
}
