{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://farmslot.io/schemas/recipe-v1.schema.json",
  "title": "Farmslot Recipe Protocol v1",
  "description": "A parameterized, composable workflow that executes actions or other recipes and records explicit proof claims.",
  "type": "object",
  "required": ["$schema", "workflow"],
  "properties": {
    "$schema": {
      "const": "https://farmslot.io/schemas/recipe-v1.schema.json",
      "description": "Canonical Recipe Protocol v1 schema URL used by editors and validators."
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "description": "Short human-readable recipe name."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "description": "What the recipe does and the outcome it is designed to prove."
    },
    "paramsSchema": {
      "$ref": "#/$defs/paramsSchema",
      "description": "Optional JSON Schema for caller-supplied recipe parameters."
    },
    "proofTargets": {
      "type": "array",
      "description": "Claims this recipe can prove; workflow nodes link to them through proves.",
      "items": {
        "type": "object",
        "required": ["id", "claim"],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "description": "Stable identifier referenced by workflow node proves entries."
          },
          "claim": {
            "type": "string",
            "minLength": 1,
            "description": "Human-readable statement that successful linked nodes establish."
          }
        },
        "additionalProperties": false
      }
    },
    "workflow": {
      "$ref": "#/$defs/workflow",
      "description": "Executable graph containing the entry node, optional teardown node, and named nodes."
    }
  },
  "additionalProperties": false,
  "$defs": {
    "paramsSchema": {
      "type": "object",
      "required": ["type", "additionalProperties"],
      "properties": {
        "type": {
          "const": "object",
          "description": "Recipe parameters are always supplied as one object."
        },
        "additionalProperties": {
          "const": false,
          "description": "Reject caller parameters that the recipe does not declare."
        },
        "required": {
          "type": "array",
          "description": "Parameter names callers must provide when no default satisfies them.",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true
        },
        "properties": {
          "type": "object",
          "description": "Named parameter definitions available to callers and call nodes.",
          "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
          "additionalProperties": { "$ref": "#/$defs/paramSchema" }
        }
      },
      "additionalProperties": false
    },
    "paramSchema": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "description": "Allowed JSON value type or types for this parameter.",
          "oneOf": [
            { "enum": ["string", "number", "integer", "boolean", "object", "array"] },
            {
              "type": "array",
              "items": { "enum": ["string", "number", "integer", "boolean", "object", "array"] },
              "minItems": 1,
              "uniqueItems": true
            }
          ]
        },
        "description": {
          "type": "string",
          "description": "Human-readable parameter help shown by recipe discovery and editors."
        },
        "default": {
          "description": "Value used when the caller omits this parameter."
        },
        "enum": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "description": "Exact values accepted for this parameter."
        },
        "additionalProperties": {
          "type": "boolean",
          "description": "Whether an object parameter may contain undeclared keys."
        },
        "required": {
          "type": "array",
          "description": "Required keys within an object parameter.",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true
        },
        "properties": {
          "type": "object",
          "description": "Named child schemas for an object parameter.",
          "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
          "additionalProperties": { "$ref": "#/$defs/paramSchema" }
        },
        "items": {
          "$ref": "#/$defs/paramSchema",
          "description": "Schema applied to each item of an array parameter."
        },
        "minimum": {
          "type": "number",
          "description": "Inclusive lower bound for numeric parameters."
        },
        "minItems": {
          "type": "integer",
          "minimum": 0,
          "description": "Minimum number of entries required for array parameters."
        },
        "maxItems": {
          "type": "integer",
          "minimum": 0,
          "description": "Maximum number of entries allowed for array parameters."
        }
      },
      "additionalProperties": false
    },
    "proofLinks": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "uniqueItems": true
    },
    "transition": {
      "oneOf": [
        {
          "required": ["next"],
          "properties": {
            "next": {
              "type": "string",
              "minLength": 1,
              "description": "Node id to execute after this node succeeds."
            }
          },
          "not": { "anyOf": [{ "required": ["cases"] }, { "required": ["default"] }] }
        },
        {
          "required": ["cases", "default"],
          "properties": {
            "cases": {
              "type": "object",
              "minProperties": 1,
              "additionalProperties": { "type": "string", "minLength": 1 },
              "description": "Maps an action result case to the next node id."
            },
            "default": {
              "type": "string",
              "minLength": 1,
              "description": "Node id used when no declared result case matches."
            }
          },
          "not": { "required": ["next"] }
        }
      ]
    },
    "actionNode": {
      "type": "object",
      "required": ["action", "intent"],
      "properties": {
        "action": {
          "type": "string",
          "minLength": 1,
          "not": { "enum": ["call", "end"] },
          "description": "Registered action name to execute; action-specific fields are declared by its manifest."
        },
        "intent": {
          "type": "string",
          "minLength": 1,
          "description": "Concise human-facing reason for performing this step."
        },
        "next": {
          "type": "string",
          "minLength": 1,
          "description": "Node id to execute after this node succeeds."
        },
        "cases": {
          "type": "object",
          "minProperties": 1,
          "additionalProperties": { "type": "string", "minLength": 1 },
          "description": "Maps an action result case to the next node id."
        },
        "default": {
          "type": "string",
          "minLength": 1,
          "description": "Node id used when no declared result case matches."
        },
        "proves": {
          "$ref": "#/$defs/proofLinks",
          "description": "Proof target ids established by successful execution of this node."
        },
        "visual_review": {
          "$ref": "#/$defs/visualReviewRelationship",
          "description": "Optional visual-review hierarchy and related-surface links for ui.capture_surface and ui.screenshot nodes."
        }
      },
      "allOf": [{ "$ref": "#/$defs/transition" }],
      "additionalProperties": true
    },
    "visualReviewRelationship": {
      "type": "object",
      "properties": {
        "parent": {
          "type": "string",
          "minLength": 1,
          "description": "Parent visual capture node id within this recipe."
        },
        "navigation": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "minLength": 1,
                "description": "Source visual capture node id for this observed navigation path."
              },
              "kind": {
                "type": "string",
                "enum": ["tab", "push", "in-place", "modal", "replace"],
                "description": "How the destination surface is presented from the source."
              }
            },
            "required": ["from", "kind"],
            "additionalProperties": false
          },
          "description": "Observed navigation paths into this captured surface."
        },
        "related": {
          "type": "array",
          "items": { "type": "string", "minLength": 1 },
          "uniqueItems": true,
          "description": "Related visual capture node ids within this recipe."
        }
      },
      "additionalProperties": false
    },
    "callNode": {
      "type": "object",
      "required": ["action", "intent", "ref", "next"],
      "properties": {
        "action": {
          "const": "call",
          "description": "Selects recipe composition instead of an action adapter."
        },
        "intent": {
          "type": "string",
          "minLength": 1,
          "description": "Concise human-facing reason for invoking the referenced recipe."
        },
        "ref": {
          "type": "string",
          "minLength": 1,
          "not": { "pattern": "\\{\\{(?:params|outputs)\\.[A-Za-z0-9_.-]+\\}\\}" },
          "description": "Static recipe id resolved from configured recipe libraries."
        },
        "params": {
          "type": "object",
          "description": "Parameters passed to the referenced recipe; values may use supported parameter templates."
        },
        "proves": {
          "$ref": "#/$defs/proofLinks",
          "description": "Proof target ids established by successful completion of the called recipe."
        },
        "next": {
          "type": "string",
          "minLength": 1,
          "description": "Node id to execute after the called recipe succeeds."
        }
      },
      "additionalProperties": false
    },
    "endNode": {
      "type": "object",
      "required": ["action", "status"],
      "properties": {
        "action": {
          "const": "end",
          "description": "Terminates the current recipe."
        },
        "status": {
          "enum": ["pass", "fail", "unknown"],
          "description": "Terminal recipe verdict returned by this path."
        }
      },
      "additionalProperties": false
    },
    "workflowNode": {
      "oneOf": [
        { "$ref": "#/$defs/actionNode" },
        { "$ref": "#/$defs/callNode" },
        { "$ref": "#/$defs/endNode" }
      ]
    },
    "workflow": {
      "type": "object",
      "required": ["entry", "nodes"],
      "properties": {
        "entry": {
          "type": "string",
          "minLength": 1,
          "description": "Node id where normal recipe execution starts."
        },
        "teardown": {
          "type": "string",
          "minLength": 1,
          "description": "Optional node id executed once after the main graph exits to restore deterministic state."
        },
        "nodes": {
          "type": "object",
          "minProperties": 1,
          "propertyNames": { "pattern": "^[A-Za-z0-9_-]+$" },
          "additionalProperties": { "$ref": "#/$defs/workflowNode" },
          "description": "Named action, recipe-call, and terminal nodes in the executable graph."
        }
      },
      "additionalProperties": false
    }
  }
}
