{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://github.com/anthropics/auto-mobile/schemas/test-plan.schema.json",
  "title": "AutoMobile Test Plan",
  "description": "Schema for AutoMobile test plan YAML files used with executePlan and JUnit Runner",
  "type": "object",
  "required": ["name", "steps"],
  "additionalProperties": false,
  "properties": {
    "name": {
      "type": "string",
      "description": "Unique identifier for the test plan",
      "minLength": 1
    },
    "description": {
      "type": "string",
      "description": "Human-readable description of what this test plan does"
    },
    "platform": {
      "type": "string",
      "enum": ["android", "ios"],
      "description": "Optional platform hint for plan execution"
    },
    "devices": {
      "type": "array",
      "description": "List of device labels or device definitions for multi-device test execution. Required when using device parameters or criticalSection.",
      "items": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1,
            "pattern": "\\S",
            "description": "Device label; must contain at least one non-whitespace character (a whitespace-only label like \" \" satisfies minLength but is rejected by the daemon's validateDevicesField, which trims labels before checking emptiness -- #6215 review)."
          },
          {
            "$ref": "#/$defs/planDevice"
          }
        ]
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "steps": {
      "type": "array",
      "description": "Ordered list of tool execution steps",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/planStep"
      }
    },
    "mcpVersion": {
      "type": "string",
      "description": "MCP server version this plan was created with",
      "pattern": "^\\d+\\.\\d+\\.\\d+$"
    },
    "generated": {
      "type": "string",
      "description": "DEPRECATED: ISO 8601 timestamp (legacy field, use metadata.createdAt instead)",
      "format": "date-time"
    },
    "appId": {
      "type": "string",
      "description": "DEPRECATED: Application bundle ID (legacy field, use metadata.appId instead)"
    },
    "parameters": {
      "type": "object",
      "description": "DEPRECATED: Plan parameters (legacy field)",
      "additionalProperties": true
    },
    "secretParameters": {
      "type": "array",
      "description": "Names of parameter keys whose substituted values are sensitive (tokens, passwords, PII). Their values are redacted from any context sent to a third-party LLM during AI-assisted recovery (issue #6029); the values still reach the local daemon unredacted so the plan can execute.",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "uniqueItems": true
    },
    "metadata": {
      "type": "object",
      "description": "Additional metadata about the plan",
      "properties": {
        "createdAt": {
          "type": "string",
          "description": "ISO 8601 timestamp of plan creation",
          "format": "date-time"
        },
        "version": {
          "type": "string",
          "description": "Plan format version",
          "pattern": "^\\d+\\.\\d+\\.\\d+$"
        },
        "appId": {
          "type": "string",
          "description": "Application bundle ID or package name"
        },
        "sessionId": {
          "type": "string",
          "description": "Session identifier for plan execution"
        },
        "toolCallCount": {
          "type": "integer",
          "description": "Number of tool calls in original session",
          "minimum": 0
        },
        "duration": {
          "type": "number",
          "description": "Duration of original session in milliseconds",
          "minimum": 0
        },
        "generatedFromToolCalls": {
          "type": "boolean",
          "description": "Whether this plan was auto-generated from tool call logs"
        },
        "experiments": {
          "type": "array",
          "description": "Active experiments during plan creation",
          "items": {
            "type": "string"
          }
        },
        "treatments": {
          "type": "object",
          "description": "Experiment treatment assignments",
          "additionalProperties": {
            "type": "string"
          }
        },
        "featureFlags": {
          "type": "object",
          "description": "Feature flag values during plan creation",
          "additionalProperties": true
        }
      },
      "additionalProperties": true
    }
  },
  "$defs": {
    "planDevice": {
      "type": "object",
      "required": ["label", "platform"],
      "additionalProperties": false,
      "properties": {
        "label": {
          "type": "string",
          "description": "Device label for multi-device routing; must contain at least one non-whitespace character.",
          "minLength": 1,
          "pattern": "\\S"
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Target platform for this device"
        },
        "simulatorType": {
          "type": "string",
          "description": "iOS simulator type (e.g., iPhone 15 Pro)"
        },
        "iosVersion": {
          "type": "string",
          "description": "iOS version for simulator selection"
        }
      }
    },
    "tapAtGeometry": {
      "type": "object",
      "additionalProperties": false,
      "required": ["platform", "deviceWidth", "deviceHeight", "orientation", "x", "y"],
      "properties": {
        "platform": {
          "enum": ["android", "ios"]
        },
        "deviceWidth": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "deviceHeight": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "orientation": {
          "type": "integer",
          "minimum": 0,
          "maximum": 3
        },
        "x": {
          "type": "number",
          "minimum": 0
        },
        "y": {
          "type": "number",
          "minimum": 0
        }
      }
    },
    "planStep": {
      "type": "object",
      "required": ["tool"],
      "additionalProperties": true,
      "properties": {
        "geometry": { "$ref": "#/$defs/tapAtGeometry" },
        "tool": {
          "type": "string",
          "description": "Name of the MCP tool to execute",
          "minLength": 1
        },
        "params": {
          "description": "Tool-specific parameters (can also be specified as top-level properties)"
        },
        "device": {
          "description": "Device label indicating which device this step runs on (required in multi-device plans for non-device-agnostic tools). Type-constrained in allOf below, gated on whether params.device overrides it (params-wins precedence)."
        },
        "label": {
          "type": "string",
          "description": "Human-readable description of what this step does"
        },
        "optional": {
          "type": "boolean",
          "description": "Best-effort step: if the tool fails (returns success:false, an observe waitFor times out, or throws), the executor logs it, records the step as skipped, and continues instead of aborting the plan. Use for steps that guard against intermittent UI (e.g. dismissing a dialog that may or may not be present)."
        },
        "description": {
          "type": "string",
          "description": "DEPRECATED: Use 'label' instead (legacy field)"
        },
        "expectations": {
          "type": "array",
          "description": "Assertions to validate after step execution",
          "items": {
            "$ref": "#/$defs/expectation"
          }
        }
      },
      "description": "A step can have tool-specific parameters either in a 'params' object or as top-level properties (e.g., 'id', 'text', 'appId', 'direction', etc.)",
      "allOf": [
        {
          "$comment": "Generic (all tools): params.device (when present) overrides inline device at normalization (PlanNormalizer's `{ ...inlineParams, ...paramsFromStep }` merge, params precedence), so the inline sibling is discarded and must NOT be type-checked in that case -- e.g. a nested criticalSection/barrier sub-step with an invalid inline device but a valid params.device must not be false-rejected. Conversely, when params DOES declare device, its own value must still be validated (non-blank string) -- most tools have no tool-specific params schema to catch an invalid params.device otherwise, so a plan with e.g. params.device: 456 would normalize to an invalid device and only fail at the daemon (#6215 review).",
          "if": {
            "required": ["params"],
            "properties": { "params": { "type": "object", "required": ["device"] } }
          },
          "then": {
            "properties": {
              "params": {
                "properties": {
                  "device": {
                    "type": "string",
                    "description": "Device label indicating which device this step runs on (required in multi-device plans for non-device-agnostic tools)",
                    "minLength": 1,
                    "pattern": "\\S"
                  }
                }
              }
            }
          },
          "else": {
            "properties": {
              "device": {
                "type": "string",
                "description": "Device label indicating which device this step runs on (required in multi-device plans for non-device-agnostic tools)",
                "minLength": 1,
                "pattern": "\\S"
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "dragAndDrop"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/dragAndDropParams"
              },
              "source": {
                "$ref": "#/$defs/dragAndDropSelector"
              },
              "target": {
                "$ref": "#/$defs/dragAndDropSelector"
              },
              "pressDurationMs": {
                "type": "number",
                "description": "Press duration ms (min: 600, max: 3000, default: 600)",
                "minimum": 600,
                "maximum": 3000
              },
              "dragDurationMs": {
                "type": "number",
                "description": "Drag duration ms (min: 300, max: 1000, default: 300)",
                "minimum": 300,
                "maximum": 1000
              },
              "holdDurationMs": {
                "type": "number",
                "description": "Hold duration ms (min: 100, max: 3000, default: 100)",
                "minimum": 100,
                "maximum": 3000
              },
              "platform": {
                "type": "string",
                "enum": ["android", "ios"],
                "description": "Platform"
              },
              "sessionUuid": {
                "type": "string",
                "description": "Session UUID for device targeting"
              },
              "keepScreenAwake": {
                "type": "boolean",
                "description": "Keep physical Android devices awake during the session (default: true)"
              }
            },
            "allOf": [
              {
                "anyOf": [
                  {
                    "required": ["source"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["source"]
                      }
                    },
                    "required": ["params"]
                  }
                ]
              },
              {
                "anyOf": [
                  {
                    "required": ["target"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["target"]
                      }
                    },
                    "required": ["params"]
                  }
                ]
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "highlight"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/highlightParams"
              },
              "description": {
                "type": "string",
                "description": "Optional highlight description"
              },
              "id": {
                "type": "string",
                "description": "Element resource ID / accessibility identifier"
              },
              "text": {
                "type": "string",
                "description": "Element text content to match"
              },
              "container": {
                "$ref": "#/$defs/highlightContainer"
              },
              "containerOf": {
                "type": "boolean",
                "description": "Whether to highlight the container of the selected element"
              },
              "shape": {
                "$ref": "#/$defs/highlightShape"
              },
              "selectionStrategy": {
                "type": "string",
                "enum": ["first", "random"],
                "description": "Element selection strategy when multiple matches are found (default: first)"
              },
              "platform": {
                "type": "string",
                "enum": ["android", "ios"],
                "description": "Platform"
              },
              "timeoutMs": {
                "type": "number",
                "description": "Highlight request timeout ms (default: 5000)"
              },
              "sessionUuid": {
                "type": "string",
                "description": "Session UUID for device targeting"
              },
              "keepScreenAwake": {
                "type": "boolean",
                "description": "Keep physical Android devices awake during the session (default: true)"
              },
              "elementId": {
                "type": "string",
                "description": "Element resource ID / accessibility identifier"
              }
            },
            "allOf": [
              {
                "anyOf": [
                  {
                    "required": ["shape"]
                  },
                  {
                    "required": ["id"]
                  },
                  {
                    "required": ["elementId"]
                  },
                  {
                    "required": ["text"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["shape"]
                      }
                    },
                    "required": ["params"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["id"]
                      }
                    },
                    "required": ["params"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["text"]
                      }
                    },
                    "required": ["params"]
                  },
                  {
                    "properties": {
                      "params": {
                        "required": ["elementId"]
                      }
                    },
                    "required": ["params"]
                  }
                ]
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "setAppPermissions"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/setAppPermissionsParams"
              },
              "appId": {
                "type": "string",
                "description": "App package ID or bundle identifier",
                "minLength": 1
              },
              "action": {
                "type": "string",
                "enum": ["grant", "revoke", "reset"],
                "description": "Permission action. Defaults to grant. Android reset requires permissions=['all'] and resets runtime permissions device-wide."
              },
              "permissions": {
                "type": "array",
                "description": "Runtime permissions or simulator privacy services to change; Android reset accepts only 'all'; physical iOS accepts it too",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "minLength": 1
                }
              },
              "userId": {
                "type": "integer",
                "minimum": 0,
                "description": "Android user ID for grant/revoke, not reset"
              },
              "notificationsEnabled": {
                "type": "boolean",
                "description": "Android notification state, independent of POST_NOTIFICATIONS"
              },
              "notificationPolicyAccess": {
                "type": "boolean",
                "description": "Android only: set notification policy / DND access"
              },
              "scheduleExactAlarm": {
                "type": "string",
                "enum": ["allow", "deny"],
                "description": "Android only: set UID-level SCHEDULE_EXACT_ALARM appop"
              },
              "platform": {
                "type": "string",
                "enum": ["android", "ios"],
                "description": "Platform"
              },
              "sessionUuid": {
                "type": "string",
                "description": "Session UUID for device targeting"
              },
              "keepScreenAwake": {
                "type": "boolean",
                "description": "Keep physical Android devices awake during the session (default: true)"
              },
              "device": {
                "type": "string",
                "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
              },
              "deviceId": {
                "type": "string",
                "description": "Device ID override"
              }
            },
            "anyOf": [
              {
                "required": ["appId", "permissions"]
              },
              {
                "required": ["appId", "notificationsEnabled"]
              },
              {
                "required": ["appId", "notificationPolicyAccess"]
              },
              {
                "required": ["appId", "scheduleExactAlarm"]
              },
              {
                "required": ["params"]
              }
            ],
            "allOf": [
              {
                "if": {
                  "properties": {
                    "action": {
                      "const": "reset"
                    }
                  },
                  "required": ["action"]
                },
                "then": {
                  "required": ["permissions"],
                  "not": {
                    "required": ["userId"]
                  }
                }
              },
              {
                "if": {
                  "properties": {
                    "action": {
                      "const": "reset"
                    },
                    "platform": {
                      "const": "android"
                    }
                  },
                  "required": ["action", "platform"]
                },
                "then": {
                  "properties": {
                    "permissions": {
                      "type": "array",
                      "minItems": 1,
                      "maxItems": 1,
                      "items": {
                        "const": "all"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "getAppPermissions"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/getAppPermissionsParams"
              },
              "appId": {
                "type": "string",
                "description": "App package ID or bundle identifier",
                "minLength": 1
              },
              "permissions": {
                "type": "array",
                "description": "Optional permission names or simulator privacy services to query",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "minLength": 1
                }
              },
              "platform": {
                "type": "string",
                "enum": ["android", "ios"],
                "description": "Platform"
              },
              "sessionUuid": {
                "type": "string",
                "description": "Session UUID for device targeting"
              },
              "keepScreenAwake": {
                "type": "boolean",
                "description": "Keep physical Android devices awake during the session (default: true)"
              },
              "device": {
                "type": "string",
                "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
              },
              "deviceId": {
                "type": "string",
                "description": "Device ID override"
              }
            },
            "anyOf": [
              {
                "required": ["appId"]
              },
              {
                "required": ["params"]
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "getNotificationPolicy"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/getNotificationPolicyParams"
              },
              "appId": {
                "type": "string",
                "description": "App package ID or bundle identifier",
                "minLength": 1
              }
            },
            "anyOf": [
              {
                "required": ["appId"]
              },
              {
                "required": ["params"]
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "setNotificationPolicy"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/setNotificationPolicyParams"
              },
              "appId": {
                "type": "string",
                "description": "App package ID or bundle identifier",
                "minLength": 1
              },
              "policyAccess": {
                "type": "boolean",
                "description": "Android only: allow or revoke app notification policy / DND access"
              }
            },
            "anyOf": [
              {
                "required": ["appId", "policyAccess"]
              },
              {
                "required": ["params"]
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "getDeviceState"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/getDeviceStateParams"
              },
              "include": {
                "type": "array",
                "minItems": 1,
                "description": "Optional device state fields to read",
                "items": {
                  "type": "string",
                  "enum": [
                    "doNotDisturb",
                    "connectivity",
                    "biometrics",
                    "networkCondition",
                    "clock"
                  ]
                }
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "tool": {
                "const": "setDeviceState"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "properties": {
              "params": {
                "$ref": "#/$defs/setDeviceStateParams"
              }
            },
            "anyOf": [
              {
                "required": ["doNotDisturb"]
              },
              {
                "required": ["biometrics"]
              },
              {
                "required": ["connectivity"]
              },
              {
                "required": ["location"]
              },
              {
                "required": ["networkCondition"]
              },
              {
                "required": ["clock"]
              },
              {
                "required": ["cameraPoster"]
              },
              {
                "required": ["screenLock"]
              },
              {
                "required": ["power"]
              },
              {
                "required": ["deviceCondition"]
              },
              {
                "required": ["params"]
              }
            ],
            "allOf": [
              {
                "$comment": "params.networkCondition (when present) fully overrides an inline networkCondition at normalization (PlanNormalizer, params precedence), so the inline value is discarded at execution. Apply the strict networkConditionStateInput rules to the inline form ONLY when params.networkCondition is absent, otherwise a valid params reset would false-reject the discarded inline value (#6090 review).",
                "if": {
                  "required": ["params"],
                  "properties": {
                    "params": {
                      "required": ["networkCondition"]
                    }
                  }
                },
                "then": true,
                "else": {
                  "properties": {
                    "networkCondition": {
                      "$ref": "#/$defs/networkConditionStateInput"
                    }
                  }
                }
              },
              {
                "$comment": "params.doNotDisturb (when present) fully overrides an inline doNotDisturb at normalization (PlanNormalizer, params precedence), so the inline value is discarded at execution. Apply the strict doNotDisturbStateInput rules to the inline form ONLY when params.doNotDisturb is absent, otherwise a valid params override would false-reject the discarded inline value (#6112).",
                "if": {
                  "required": ["params"],
                  "properties": {
                    "params": {
                      "required": ["doNotDisturb"]
                    }
                  }
                },
                "then": true,
                "else": {
                  "properties": {
                    "doNotDisturb": {
                      "$ref": "#/$defs/doNotDisturbStateInput"
                    }
                  }
                }
              },
              {
                "$comment": "params.biometrics (when present) fully overrides an inline biometrics at normalization (PlanNormalizer, params precedence), so the inline value is discarded at execution. Apply the strict biometricStateInput rules to the inline form ONLY when params.biometrics is absent, otherwise a valid params override would false-reject the discarded inline value (#6112).",
                "if": {
                  "required": ["params"],
                  "properties": {
                    "params": {
                      "required": ["biometrics"]
                    }
                  }
                },
                "then": true,
                "else": {
                  "properties": {
                    "biometrics": {
                      "$ref": "#/$defs/biometricStateInput"
                    }
                  }
                }
              },
              {
                "$comment": "params.clock overrides inline clock at normalization; validate only the effective inline value when params.clock is absent.",
                "if": {
                  "required": ["params"],
                  "properties": {
                    "params": {
                      "required": ["clock"]
                    }
                  }
                },
                "then": true,
                "else": {
                  "properties": {
                    "clock": {
                      "$ref": "#/$defs/deviceClockStateInput"
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "$comment": "criticalSection accepts lock/deviceCount/steps/timeout either inline on the step (like dragAndDrop's source/target), nested under params, or SPLIT across both (e.g. lock inline, deviceCount in params) -- PlanNormalizer merges inline fields and params together before execution, so a field counts as present wherever it appears. Each required field is validated against that merged union independently, not as an all-inline-or-all-params choice. Additionally, when params declares a field, params WINS at normalization (PlanNormalizer's `{ ...inlineParams, ...paramsFromStep }` merge, same precedence #6090/#6107 established for networkCondition/doNotDisturb) -- so the inline sibling is discarded and must NOT be type-checked in that case, or a plan with an invalid-but-overridden inline value would be false-rejected (#6215 review).",
          "if": {
            "properties": {
              "tool": {
                "const": "criticalSection"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "allOf": [
              {
                "$comment": "Apply criticalSectionParams' shape ONLY when params is actually an object. PlanNormalizer.isRecord treats a non-object params (e.g. null) as absent ({}), so an inline-form step with params: null must still validate via its inline fields below, not fail the unconditional object-shaped $ref (#6215 review).",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object" } }
                },
                "then": {
                  "properties": {
                    "params": {
                      "$ref": "#/$defs/criticalSectionParams"
                    }
                  }
                }
              },
              {
                "$comment": "__lockNamespace is a reserved internal field injected by PlanExecutor at runtime to scope a plan's lock state -- an authored plan must never set it, inline or under params. Rejected explicitly here because criticalSectionParams' additionalProperties stays true, so simply omitting it from the declared properties would not block it. The params branch requires params to be an object before trusting its 'required' check -- required vacuously passes against a non-object value like params: null, which would otherwise misfire this rejection on every step with params: null (#6215 review).",
                "not": {
                  "anyOf": [
                    { "required": ["__lockNamespace"] },
                    {
                      "required": ["params"],
                      "properties": {
                        "params": { "type": "object", "required": ["__lockNamespace"] }
                      }
                    }
                  ]
                }
              },
              {
                "anyOf": [
                  { "required": ["lock"] },
                  {
                    "required": ["params"],
                    "properties": { "params": { "type": "object", "required": ["lock"] } }
                  }
                ]
              },
              {
                "anyOf": [
                  { "required": ["deviceCount"] },
                  {
                    "required": ["params"],
                    "properties": { "params": { "type": "object", "required": ["deviceCount"] } }
                  }
                ]
              },
              {
                "anyOf": [
                  { "required": ["steps"] },
                  {
                    "required": ["params"],
                    "properties": { "params": { "type": "object", "required": ["steps"] } }
                  }
                ]
              },
              {
                "$comment": "params.lock (when present) overrides inline lock at normalization; validate the inline form only when params.lock is absent, otherwise a valid params override would false-reject the discarded inline value.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["lock"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "lock": {
                      "type": "string",
                      "description": "Global lock identifier for synchronization across devices",
                      "minLength": 1
                    }
                  }
                }
              },
              {
                "$comment": "params.deviceCount override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["deviceCount"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "deviceCount": {
                      "type": "integer",
                      "description": "Expected number of devices that must reach the barrier before execution proceeds",
                      "minimum": 1
                    }
                  }
                }
              },
              {
                "$comment": "params.steps override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["steps"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "steps": {
                      "type": "array",
                      "description": "Steps to execute serially within the critical section",
                      "minItems": 1,
                      "items": {
                        "$ref": "#/$defs/planStep"
                      }
                    }
                  }
                }
              },
              {
                "$comment": "params.timeout override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["timeout"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "timeout": {
                      "type": "integer",
                      "description": "Barrier timeout in milliseconds (default: 30000ms)",
                      "exclusiveMinimum": 0,
                      "maximum": 2147483647,
                      "default": 30000
                    }
                  }
                }
              },
              {
                "$comment": "params.platform override, same rule as lock above -- addDeviceTargetingToSchema restricts this to android/ios at runtime, so the inline form must be validated only when params does not override it.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["platform"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "platform": {
                      "type": "string",
                      "enum": ["android", "ios"],
                      "description": "Platform"
                    }
                  }
                }
              },
              {
                "$comment": "params.sessionUuid override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["sessionUuid"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "sessionUuid": {
                      "type": "string",
                      "description": "Session UUID for device targeting"
                    }
                  }
                }
              },
              {
                "$comment": "params.keepScreenAwake override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["keepScreenAwake"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "keepScreenAwake": {
                      "type": "boolean",
                      "description": "Keep physical Android devices awake during the session (default: true)"
                    }
                  }
                }
              },
              {
                "$comment": "params.deviceId override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["deviceId"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "deviceId": {
                      "type": "string",
                      "description": "Device ID override"
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "$comment": "barrier accepts lock/deviceCount/timeout either inline on the step, nested under params, or split across both, same union-of-locations and params-wins-override rules as criticalSection (#6215 review).",
          "if": {
            "properties": {
              "tool": {
                "const": "barrier"
              }
            },
            "required": ["tool"]
          },
          "then": {
            "allOf": [
              {
                "$comment": "Apply barrierParams' shape ONLY when params is actually an object. PlanNormalizer.isRecord treats a non-object params (e.g. null) as absent ({}), so an inline-form step with params: null must still validate via its inline fields below, not fail the unconditional object-shaped $ref (#6215 review).",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object" } }
                },
                "then": {
                  "properties": {
                    "params": {
                      "$ref": "#/$defs/barrierParams"
                    }
                  }
                }
              },
              {
                "$comment": "__lockNamespace is a reserved internal field injected by PlanExecutor at runtime to scope a plan's lock state -- an authored plan must never set it, inline or under params. Rejected explicitly here because barrierParams' additionalProperties stays true, so simply omitting it from the declared properties would not block it. The params branch requires params to be an object before trusting its 'required' check -- required vacuously passes against a non-object value like params: null, which would otherwise misfire this rejection on every step with params: null (#6215 review).",
                "not": {
                  "anyOf": [
                    { "required": ["__lockNamespace"] },
                    {
                      "required": ["params"],
                      "properties": {
                        "params": { "type": "object", "required": ["__lockNamespace"] }
                      }
                    }
                  ]
                }
              },
              {
                "anyOf": [
                  { "required": ["lock"] },
                  {
                    "required": ["params"],
                    "properties": { "params": { "type": "object", "required": ["lock"] } }
                  }
                ]
              },
              {
                "anyOf": [
                  { "required": ["deviceCount"] },
                  {
                    "required": ["params"],
                    "properties": { "params": { "type": "object", "required": ["deviceCount"] } }
                  }
                ]
              },
              {
                "$comment": "params.lock (when present) overrides inline lock at normalization; validate the inline form only when params.lock is absent, otherwise a valid params override would false-reject the discarded inline value.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["lock"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "lock": {
                      "type": "string",
                      "description": "Shared barrier name; all devices using the same name synchronize together",
                      "minLength": 1
                    }
                  }
                }
              },
              {
                "$comment": "params.deviceCount override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["deviceCount"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "deviceCount": {
                      "type": "integer",
                      "description": "Number of devices that must arrive before the barrier lifts",
                      "minimum": 1
                    }
                  }
                }
              },
              {
                "$comment": "params.timeout override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["timeout"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "timeout": {
                      "type": "integer",
                      "description": "Barrier timeout in milliseconds (default: 30000ms)",
                      "exclusiveMinimum": 0,
                      "maximum": 2147483647,
                      "default": 30000
                    }
                  }
                }
              },
              {
                "$comment": "params.platform override, same rule as lock above -- addDeviceTargetingToSchema restricts this to android/ios at runtime, so the inline form must be validated only when params does not override it.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["platform"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "platform": {
                      "type": "string",
                      "enum": ["android", "ios"],
                      "description": "Platform"
                    }
                  }
                }
              },
              {
                "$comment": "params.sessionUuid override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["sessionUuid"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "sessionUuid": {
                      "type": "string",
                      "description": "Session UUID for device targeting"
                    }
                  }
                }
              },
              {
                "$comment": "params.keepScreenAwake override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["keepScreenAwake"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "keepScreenAwake": {
                      "type": "boolean",
                      "description": "Keep physical Android devices awake during the session (default: true)"
                    }
                  }
                }
              },
              {
                "$comment": "params.deviceId override, same rule as lock above.",
                "if": {
                  "required": ["params"],
                  "properties": { "params": { "type": "object", "required": ["deviceId"] } }
                },
                "then": true,
                "else": {
                  "properties": {
                    "deviceId": {
                      "type": "string",
                      "description": "Device ID override"
                    }
                  }
                }
              }
            ]
          }
        }
      ]
    },
    "dragAndDropSelector": {
      "type": "object",
      "properties": {
        "text": {
          "type": "string",
          "description": "Element text",
          "minLength": 1
        },
        "elementId": {
          "type": "string",
          "description": "Element ID",
          "minLength": 1
        },
        "container": {
          "description": "Nested container scope for this endpoint; outermost container resolves first",
          "$ref": "#/$defs/dragAndDropContainer"
        },
        "selectionStrategy": {
          "description": "Endpoint selection strategy (default: first); unique requires one match at every unindexed scope level and at the target",
          "type": "string",
          "enum": ["first", "random", "unique"]
        }
      },
      "additionalProperties": false,
      "description": "Selector for dragAndDrop elements",
      "oneOf": [
        {
          "required": ["text"]
        },
        {
          "required": ["elementId"]
        }
      ]
    },
    "dragAndDropParams": {
      "type": "object",
      "properties": {
        "source": {
          "$ref": "#/$defs/dragAndDropSelector",
          "description": "Source element"
        },
        "target": {
          "$ref": "#/$defs/dragAndDropSelector",
          "description": "Target element"
        },
        "pressDurationMs": {
          "type": "number",
          "description": "Press duration ms (min: 600, max: 3000, default: 600)",
          "minimum": 600,
          "maximum": 3000
        },
        "dragDurationMs": {
          "type": "number",
          "description": "Drag duration ms (min: 300, max: 1000, default: 300)",
          "minimum": 300,
          "maximum": 1000
        },
        "holdDurationMs": {
          "type": "number",
          "description": "Hold duration ms (min: 100, max: 3000, default: 100)",
          "minimum": 100,
          "maximum": 3000
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
        }
      },
      "additionalProperties": true,
      "description": "Parameters for dragAndDrop"
    },
    "highlightBounds": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number",
          "description": "Bounds x-coordinate"
        },
        "y": {
          "type": "number",
          "description": "Bounds y-coordinate"
        },
        "width": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Bounds width"
        },
        "height": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Bounds height"
        },
        "sourceWidth": {
          "type": ["number", "null"],
          "exclusiveMinimum": 0,
          "description": "Optional source width for coordinate normalization"
        },
        "sourceHeight": {
          "type": ["number", "null"],
          "exclusiveMinimum": 0,
          "description": "Optional source height for coordinate normalization"
        }
      },
      "required": ["x", "y", "width", "height"],
      "additionalProperties": false,
      "description": "Highlight bounds"
    },
    "highlightStyle": {
      "type": ["object", "null"],
      "properties": {
        "strokeColor": {
          "type": "string",
          "description": "Stroke color (hex or CSS color)"
        },
        "strokeWidth": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Stroke width"
        },
        "fillColor": {
          "type": "string",
          "description": "Fill color (hex or CSS color)"
        },
        "dashPattern": {
          "type": "array",
          "items": {
            "type": "number"
          },
          "minItems": 1,
          "description": "Dash pattern lengths"
        }
      },
      "additionalProperties": false,
      "description": "Highlight style"
    },
    "highlightShape": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["circle"],
          "description": "Highlight shape type"
        },
        "bounds": {
          "$ref": "#/$defs/highlightBounds"
        }
      },
      "required": ["type", "bounds"],
      "additionalProperties": false,
      "description": "Highlight shape definition"
    },
    "highlightSelector": {
      "type": "object",
      "properties": {
        "text": {
          "type": "string",
          "description": "Element text",
          "minLength": 1
        },
        "id": {
          "type": "string",
          "description": "Element ID",
          "minLength": 1
        }
      },
      "additionalProperties": false,
      "description": "Selector for highlight elements",
      "oneOf": [
        {
          "required": ["text"]
        },
        {
          "required": ["id"]
        }
      ]
    },
    "highlightContainer": {
      "type": "object",
      "properties": {
        "elementId": {
          "type": "string",
          "description": "Container element ID",
          "minLength": 1
        },
        "text": {
          "type": "string",
          "description": "Container text",
          "minLength": 1
        }
      },
      "additionalProperties": false,
      "description": "Container selector to scope highlight search",
      "oneOf": [
        {
          "required": ["elementId"]
        },
        {
          "required": ["text"]
        }
      ]
    },
    "highlightParams": {
      "type": "object",
      "properties": {
        "description": {
          "type": "string",
          "description": "Optional highlight description"
        },
        "id": {
          "type": "string",
          "description": "Element resource ID / accessibility identifier"
        },
        "text": {
          "type": "string",
          "description": "Element text content to match"
        },
        "container": {
          "$ref": "#/$defs/highlightContainer"
        },
        "containerOf": {
          "type": "boolean",
          "description": "Whether to highlight the container of the selected element"
        },
        "shape": {
          "$ref": "#/$defs/highlightShape"
        },
        "selectionStrategy": {
          "type": "string",
          "enum": ["first", "random"],
          "description": "Element selection strategy when multiple matches are found (default: first)"
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "timeoutMs": {
          "type": "number",
          "description": "Highlight request timeout ms (default: 5000)"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
        },
        "deviceId": {
          "type": "string",
          "description": "Device ID override"
        },
        "elementId": {
          "type": "string",
          "description": "Element resource ID / accessibility identifier"
        }
      },
      "additionalProperties": true,
      "description": "Parameters for highlight"
    },
    "doNotDisturbStateInput": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Enable or disable Do Not Disturb"
        },
        "mode": {
          "type": "string",
          "enum": ["off", "none", "priority", "alarms"],
          "description": "Do Not Disturb mode"
        }
      },
      "anyOf": [
        {
          "required": ["enabled"]
        },
        {
          "required": ["mode"]
        }
      ],
      "description": "Do Not Disturb state input"
    },
    "biometricStateInput": {
      "type": "object",
      "additionalProperties": true,
      "required": ["enrollment"],
      "properties": {
        "enrollment": {
          "type": "string",
          "enum": ["enrolled", "not_enrolled"],
          "description": "iOS Simulator biometric enrollment state"
        }
      },
      "description": "iOS Simulator biometric state input"
    },
    "getDeviceStateParams": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "include": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "enum": ["doNotDisturb", "connectivity", "biometrics", "networkCondition", "clock"]
          },
          "description": "Optional device state fields to read"
        }
      },
      "description": "Parameters for getDeviceState"
    },
    "setDeviceStateParams": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "doNotDisturb": {
          "$ref": "#/$defs/doNotDisturbStateInput"
        },
        "biometrics": {
          "$ref": "#/$defs/biometricStateInput"
        },
        "networkCondition": {
          "$ref": "#/$defs/networkConditionStateInput"
        },
        "clock": {
          "$ref": "#/$defs/deviceClockStateInput"
        },
        "cameraPoster": {
          "description": "Set (mode image or qr) or clear a virtual-scene back-camera poster on a running Android emulator via the emulator console. The default camera pose may not face the poster.",
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "mode": { "type": "string", "const": "image" },
                "path": { "type": "string", "minLength": 1 },
                "surface": { "type": "string", "enum": ["wall", "table"] }
              },
              "required": ["mode", "path"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "mode": { "type": "string", "const": "qr" },
                "text": { "type": "string", "minLength": 1 },
                "surface": { "type": "string", "enum": ["wall", "table"] }
              },
              "required": ["mode", "text"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "mode": { "type": "string", "const": "clear" },
                "surface": { "type": "string", "enum": ["wall", "table"] }
              },
              "required": ["mode"],
              "additionalProperties": false
            }
          ]
        },
        "connectivity": {
          "description": "Android connectivity toggles to apply.",
          "type": "object",
          "properties": {
            "airplaneMode": {
              "description": "Enable or disable Airplane mode on Android.",
              "type": "boolean"
            },
            "wifiEnabled": {
              "description": "Enable or disable Wi-Fi on Android.",
              "type": "boolean"
            },
            "bluetoothEnabled": {
              "description": "Enable or disable Bluetooth on Android.",
              "type": "boolean"
            },
            "locationEnabled": {
              "description": "Enable or disable Location on Android.",
              "type": "boolean"
            }
          },
          "additionalProperties": false
        },
        "screenLock": {
          "description": "Android only. Set a lock-screen PIN, password, or pattern (digits 1-9) with locksettings, or clear one with type none and currentCredential. A recorded plan has the credential replaced by <redacted>: set it by hand before a replay.",
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "type": { "type": "string", "const": "pin" },
                "credential": { "type": "string", "pattern": "^\\d{4,16}$" }
              },
              "required": ["type", "credential"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "type": { "type": "string", "const": "password" },
                "credential": {
                  "type": "string",
                  "pattern": "^(?!-)[\\x20-\\x26\\x28-\\x7e]{4,16}$"
                }
              },
              "required": ["type", "credential"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "type": { "type": "string", "const": "pattern" },
                "credential": { "type": "string", "pattern": "^[1-9]{4,9}$" }
              },
              "required": ["type", "credential"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "type": { "type": "string", "const": "none" },
                "currentCredential": {
                  "type": "string",
                  "pattern": "^(?!-)[\\x20-\\x26\\x28-\\x7e]{4,16}$"
                }
              },
              "required": ["type", "currentCredential"],
              "additionalProperties": false
            }
          ]
        },
        "power": {
          "description": "Android only. Fake battery (level, charging ac|usb|wireless|none, reset), battery saver, Doze (off|light|deep; light/deep unplug the battery and turn the screen off first), an app's standby bucket and inactive state, Data Saver and thermal status (0-6 or reset). Battery, Doze and thermal faking is refused on physical devices. A session restores every changed field on release.",
          "type": "object",
          "properties": {
            "battery": {
              "type": "object",
              "properties": {
                "level": { "type": "integer", "minimum": 0, "maximum": 100 },
                "charging": { "type": "string", "enum": ["ac", "usb", "wireless", "none"] },
                "reset": { "type": "boolean" }
              },
              "additionalProperties": false
            },
            "batterySaver": { "type": "boolean" },
            "doze": { "type": "string", "enum": ["off", "light", "deep"] },
            "standbyBucket": {
              "type": "object",
              "properties": {
                "appId": {
                  "type": "string",
                  "pattern": "^[A-Za-z][A-Za-z0-9_]*(?:\\.[A-Za-z][A-Za-z0-9_]*)+$"
                },
                "bucket": {
                  "type": "string",
                  "enum": ["active", "working_set", "frequent", "rare", "restricted"]
                }
              },
              "required": ["appId", "bucket"],
              "additionalProperties": false
            },
            "appInactive": {
              "type": "object",
              "properties": {
                "appId": {
                  "type": "string",
                  "pattern": "^[A-Za-z][A-Za-z0-9_]*(?:\\.[A-Za-z][A-Za-z0-9_]*)+$"
                },
                "inactive": { "type": "boolean" }
              },
              "required": ["appId", "inactive"],
              "additionalProperties": false
            },
            "dataSaver": { "type": "boolean" },
            "thermalStatus": {
              "anyOf": [
                { "type": "integer", "minimum": 0, "maximum": 6 },
                { "type": "string", "const": "reset" }
              ]
            }
          },
          "additionalProperties": false
        },
        "location": {
          "description": "Set a static fix, start a timed route, or stop route playback on an Android emulator or iOS Simulator.",
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "mode": {
                  "description": "Apply one static coordinate.",
                  "type": "string",
                  "const": "static"
                },
                "latitude": {
                  "type": "number",
                  "minimum": -90,
                  "maximum": 90
                },
                "longitude": {
                  "type": "number",
                  "minimum": -180,
                  "maximum": 180
                }
              },
              "required": ["mode", "latitude", "longitude"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "mode": {
                  "type": "string",
                  "const": "route"
                },
                "waypoints": {
                  "minItems": 2,
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "latitude": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "longitude": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      },
                      "altitude": {
                        "type": "number"
                      }
                    },
                    "required": ["latitude", "longitude"],
                    "additionalProperties": false
                  }
                },
                "loop": {
                  "type": "boolean"
                },
                "updateIntervalMs": {
                  "type": "integer",
                  "minimum": 200,
                  "maximum": 60000
                },
                "speedMetersPerSecond": {
                  "type": "number",
                  "exclusiveMinimum": 0
                }
              },
              "required": ["mode", "waypoints", "speedMetersPerSecond"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "mode": {
                  "type": "string",
                  "const": "route"
                },
                "waypoints": {
                  "minItems": 2,
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "latitude": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "longitude": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      },
                      "altitude": {
                        "type": "number"
                      }
                    },
                    "required": ["latitude", "longitude"],
                    "additionalProperties": false
                  }
                },
                "loop": {
                  "type": "boolean"
                },
                "updateIntervalMs": {
                  "type": "integer",
                  "minimum": 200,
                  "maximum": 60000
                },
                "durationMs": {
                  "type": "number",
                  "exclusiveMinimum": 0
                }
              },
              "required": ["mode", "waypoints", "durationMs"],
              "additionalProperties": false
            },
            {
              "type": "object",
              "properties": {
                "mode": {
                  "type": "string",
                  "const": "stop"
                }
              },
              "required": ["mode"],
              "additionalProperties": false
            }
          ]
        },
        "deviceCondition": {
          "description": "Session-scoped root/integrity/fault test fixtures (behind the 'device-condition' feature flag). Sent in its own setDeviceState call.",
          "type": "object",
          "properties": {
            "version": { "type": "number", "const": 1 },
            "rootState": { "type": "string", "enum": ["rooted-detectable", "unset"] },
            "integrityState": { "type": "string", "const": "unset" },
            "faults": {
              "type": "object",
              "properties": {
                "battery": { "type": "string", "enum": ["unplug", "low", "full", "unset"] },
                "lowPower": { "type": "string", "enum": ["on", "unset"] },
                "doze": { "type": "string", "enum": ["on", "unset"] },
                "thermal": { "type": "string", "enum": ["throttling", "critical", "unset"] },
                "storage": { "type": "string", "enum": ["low", "full", "unset"] },
                "dontKeepActivities": { "type": "string", "enum": ["on", "unset"] },
                "revokePermission": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        },
                        "permission": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9_.]*$" }
                      },
                      "required": ["package", "permission"],
                      "additionalProperties": false
                    }
                  ]
                },
                "disablePackage": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        }
                      },
                      "required": ["package"],
                      "additionalProperties": false
                    }
                  ]
                },
                "corruptAppFile": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        },
                        "path": {
                          "type": "string",
                          "pattern": "^(?!\\.{1,2}(?:/|$))[A-Za-z0-9_.-]+(?:/(?!\\.{1,2}(?:/|$))[A-Za-z0-9_.-]+)*$"
                        },
                        "mode": { "type": "string", "enum": ["overwrite", "truncate"] }
                      },
                      "required": ["package", "path", "mode"],
                      "additionalProperties": false
                    }
                  ]
                },
                "killApp": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        },
                        "mode": { "type": "string", "enum": ["background", "forceStop"] }
                      },
                      "required": ["package", "mode"],
                      "additionalProperties": false
                    }
                  ]
                },
                "trimMemory": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        },
                        "level": {
                          "type": "string",
                          "enum": [
                            "HIDDEN",
                            "RUNNING_MODERATE",
                            "BACKGROUND",
                            "RUNNING_LOW",
                            "MODERATE",
                            "RUNNING_CRITICAL",
                            "COMPLETE"
                          ]
                        }
                      },
                      "required": ["package", "level"],
                      "additionalProperties": false
                    }
                  ]
                },
                "clearPackageData": {
                  "oneOf": [
                    { "type": "string", "const": "unset" },
                    {
                      "type": "object",
                      "properties": {
                        "package": {
                          "type": "string",
                          "pattern": "^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)+$"
                        }
                      },
                      "required": ["package"],
                      "additionalProperties": false
                    }
                  ]
                }
              },
              "additionalProperties": false
            },
            "expiresInSeconds": { "type": "integer", "minimum": 1, "maximum": 86400 },
            "reset": { "type": "boolean", "const": true }
          },
          "additionalProperties": false
        }
      },
      "anyOf": [
        {
          "required": ["doNotDisturb"]
        },
        {
          "required": ["biometrics"]
        },
        {
          "required": ["networkCondition"]
        },
        {
          "required": ["clock"]
        },
        {
          "required": ["connectivity"]
        },
        {
          "required": ["location"]
        },
        {
          "required": ["cameraPoster"]
        },
        {
          "required": ["screenLock"]
        },
        {
          "required": ["power"]
        },
        {
          "required": ["deviceCondition"]
        }
      ],
      "description": "Parameters for setDeviceState"
    },
    "deviceClockStateInput": {
      "description": "Set the real clock within 2000-01-01T00:00:00Z .. 2100-01-01T00:00:00Z, advance by integer byMs >= 1000 with second-level precision, or reset to host time and original auto_time (1 without a recorded slot). Rootable Android emulators only.",
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "const": "set"
            },
            "instant": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-]\\d{2}:\\d{2})))$"
            }
          },
          "required": ["mode", "instant"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "const": "advance"
            },
            "byMs": {
              "type": "integer",
              "minimum": 1000,
              "maximum": 315360000000
            }
          },
          "required": ["mode", "byMs"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "const": "reset"
            }
          },
          "required": ["mode"],
          "additionalProperties": false
        }
      ]
    },
    "networkConditionStateInput": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "profile": {
          "type": "string",
          "enum": ["none", "offline", "veryBad", "2g", "3g", "4g"],
          "description": "Device-wide network profile (Android emulator only)"
        },
        "cancel": {
          "type": "boolean",
          "description": "Reset to normal connectivity"
        },
        "reset": {
          "type": "boolean",
          "description": "Alias of cancel"
        },
        "delayMs": {
          "type": "number",
          "minimum": 0,
          "description": "Override added latency in milliseconds"
        },
        "downloadKbps": {
          "type": "number",
          "minimum": 0,
          "description": "Override download cap in kbps (0 = unlimited)"
        },
        "uploadKbps": {
          "type": "number",
          "minimum": 0,
          "description": "Override upload cap in kbps (0 = unlimited)"
        },
        "packetLossPercent": {
          "type": "number",
          "minimum": 0,
          "maximum": 100,
          "description": "Documented target packet loss"
        },
        "expiresInSeconds": {
          "type": "number",
          "minimum": 0,
          "maximum": 2147483,
          "description": "TTL in seconds. When set on a degrading request, a timer resets the device to normal connectivity after it elapses, independent of session lifetime; session release/expiry also restores connectivity, whichever comes first. Capped at 2147483s (~24.8 days) to fit the timer's 32-bit limit."
        }
      },
      "anyOf": [
        {
          "required": ["profile"]
        },
        {
          "required": ["cancel"],
          "properties": { "cancel": { "const": true } }
        },
        {
          "required": ["reset"],
          "properties": { "reset": { "const": true } }
        },
        {
          "required": ["delayMs"],
          "properties": { "delayMs": { "not": { "const": 0 } } }
        },
        {
          "required": ["downloadKbps"],
          "properties": { "downloadKbps": { "not": { "const": 0 } } }
        },
        {
          "required": ["uploadKbps"],
          "properties": { "uploadKbps": { "not": { "const": 0 } } }
        },
        {
          "required": ["packetLossPercent"],
          "properties": { "packetLossPercent": { "not": { "const": 0 } } }
        }
      ],
      "if": {
        "required": ["profile"],
        "properties": { "profile": { "const": "offline" } },
        "not": {
          "anyOf": [
            {
              "required": ["cancel"],
              "properties": { "cancel": { "const": true } }
            },
            {
              "required": ["reset"],
              "properties": { "reset": { "const": true } }
            }
          ]
        }
      },
      "then": {
        "not": {
          "anyOf": [
            { "required": ["delayMs"] },
            { "required": ["downloadKbps"] },
            { "required": ["uploadKbps"] }
          ]
        }
      },
      "description": "Device-wide network condition input (Android emulator)"
    },
    "getNotificationPolicyParams": {
      "type": "object",
      "required": ["appId"],
      "additionalProperties": true,
      "properties": {
        "appId": {
          "type": "string",
          "minLength": 1,
          "description": "App package ID or bundle identifier"
        }
      },
      "description": "Parameters for getNotificationPolicy"
    },
    "setNotificationPolicyParams": {
      "type": "object",
      "required": ["appId", "policyAccess"],
      "additionalProperties": true,
      "properties": {
        "appId": {
          "type": "string",
          "minLength": 1,
          "description": "App package ID or bundle identifier"
        },
        "policyAccess": {
          "type": "boolean",
          "description": "Android only: allow or revoke app notification policy / DND access"
        }
      },
      "description": "Parameters for setNotificationPolicy"
    },
    "setAppPermissionsParams": {
      "type": "object",
      "required": ["appId"],
      "additionalProperties": true,
      "properties": {
        "appId": {
          "type": "string",
          "minLength": 1,
          "description": "App package ID or bundle identifier"
        },
        "action": {
          "type": "string",
          "enum": ["grant", "revoke", "reset"],
          "description": "Permission action. Defaults to grant. Android reset requires permissions=['all'] and resets runtime permissions device-wide."
        },
        "permissions": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "Runtime permissions or simulator privacy services to change; Android reset accepts only 'all'; physical iOS accepts it too"
        },
        "userId": {
          "type": "integer",
          "minimum": 0,
          "description": "Android user ID for grant/revoke, not reset"
        },
        "notificationsEnabled": {
          "type": "boolean",
          "description": "Android notification state, independent of POST_NOTIFICATIONS"
        },
        "notificationPolicyAccess": {
          "type": "boolean",
          "description": "Android only: set notification policy / DND access"
        },
        "scheduleExactAlarm": {
          "type": "string",
          "enum": ["allow", "deny"],
          "description": "Android only: set UID-level SCHEDULE_EXACT_ALARM appop"
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans"
        },
        "deviceId": {
          "type": "string",
          "description": "Device ID override"
        }
      },
      "anyOf": [
        {
          "required": ["notificationsEnabled"]
        },
        {
          "required": ["permissions"]
        },
        {
          "required": ["notificationPolicyAccess"]
        },
        {
          "required": ["scheduleExactAlarm"]
        }
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "action": {
                "const": "reset"
              }
            },
            "required": ["action"]
          },
          "then": {
            "required": ["permissions"],
            "not": {
              "required": ["userId"]
            }
          }
        },
        {
          "if": {
            "properties": {
              "action": {
                "const": "reset"
              },
              "platform": {
                "const": "android"
              }
            },
            "required": ["action", "platform"]
          },
          "then": {
            "properties": {
              "permissions": {
                "type": "array",
                "minItems": 1,
                "maxItems": 1,
                "items": {
                  "const": "all"
                }
              }
            }
          }
        }
      ],
      "description": "Parameters for setAppPermissions"
    },
    "getAppPermissionsParams": {
      "type": "object",
      "required": ["appId"],
      "additionalProperties": true,
      "properties": {
        "appId": {
          "type": "string",
          "minLength": 1,
          "description": "App package ID or bundle identifier"
        },
        "permissions": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "Optional permission names or simulator privacy services to query"
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans"
        },
        "deviceId": {
          "type": "string",
          "description": "Device ID override"
        }
      },
      "description": "Parameters for getAppPermissions"
    },
    "expectation": {
      "type": "object",
      "required": ["type"],
      "additionalProperties": true,
      "properties": {
        "type": {
          "type": "string",
          "description": "Type of assertion to perform"
        },
        "selector": {
          "type": "object",
          "description": "Selector for the element to check",
          "properties": {
            "testTag": {
              "type": "string"
            },
            "text": {
              "type": "string"
            },
            "contentDescription": {
              "type": "string"
            },
            "resourceId": {
              "type": "string"
            },
            "className": {
              "type": "string"
            }
          }
        },
        "text": {
          "type": "string",
          "description": "Text to check for presence/absence"
        }
      }
    },
    "criticalSectionParams": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "lock": {
          "type": "string",
          "description": "Global lock identifier for synchronization across devices",
          "minLength": 1
        },
        "steps": {
          "type": "array",
          "description": "Steps to execute serially within the critical section",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/planStep"
          }
        },
        "deviceCount": {
          "type": "integer",
          "description": "Expected number of devices that must reach the barrier before execution proceeds",
          "minimum": 1
        },
        "timeout": {
          "type": "integer",
          "description": "Barrier timeout in milliseconds (default: 30000ms)",
          "exclusiveMinimum": 0,
          "maximum": 2147483647,
          "default": 30000
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
        },
        "deviceId": {
          "type": "string",
          "description": "Device ID override"
        }
      },
      "description": "Parameters for criticalSection tool that provides barrier synchronization and mutex-based serial execution across multiple devices. additionalProperties stays true for any genuinely-unknown extra field, but the device-targeting fields injected by addDeviceTargetingToSchema (device, sessionUuid, platform, deviceId, keepScreenAwake) are explicitly typed so a malformed value (e.g. platform: windows) is rejected here instead of only failing at execution (#6215 review). lock/steps/deviceCount are intentionally NOT required here: a plan may declare any of them inline on the step instead of nested here, and PlanNormalizer merges the two -- the planStep if/then block enforces required-ness against that merged union, not against this def in isolation."
    },
    "barrierParams": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "lock": {
          "type": "string",
          "description": "Shared barrier name; all devices using the same name synchronize together",
          "minLength": 1
        },
        "deviceCount": {
          "type": "integer",
          "description": "Number of devices that must arrive before the barrier lifts",
          "minimum": 1
        },
        "timeout": {
          "type": "integer",
          "description": "Barrier timeout in milliseconds (default: 30000ms)",
          "exclusiveMinimum": 0,
          "maximum": 2147483647,
          "default": 30000
        },
        "platform": {
          "type": "string",
          "enum": ["android", "ios"],
          "description": "Platform"
        },
        "sessionUuid": {
          "type": "string",
          "description": "Session UUID for device targeting"
        },
        "keepScreenAwake": {
          "type": "boolean",
          "description": "Keep physical Android devices awake during the session (default: true)"
        },
        "device": {
          "type": "string",
          "description": "Device label for multi-device plans (e.g., \"A\", \"B\")"
        },
        "deviceId": {
          "type": "string",
          "description": "Device ID override"
        }
      },
      "description": "Parameters for barrier tool: a pure synchronization point with no serialized section. additionalProperties stays true for any genuinely-unknown extra field, but the device-targeting fields injected by addDeviceTargetingToSchema (device, sessionUuid, platform, deviceId, keepScreenAwake) are explicitly typed so a malformed value (e.g. platform: windows) is rejected here instead of only failing at execution (#6215 review). lock/deviceCount are intentionally NOT required here for the same reason as criticalSectionParams -- required-ness is enforced against the inline+params union in the planStep if/then block. __lockNamespace is deliberately NOT declared here (and is explicitly rejected in the criticalSection/barrier if/then blocks below): it is injected internally by PlanExecutor at runtime to scope a plan's lock state, and an authored plan must never set it -- two participants authoring different truthy __lockNamespace values for the same lock would pass every per-lock feasibility check yet wait in separate namespaced barriers at runtime until timeout."
    },
    "dragAndDropContainer": {
      "anyOf": [
        {
          "type": "object",
          "properties": {
            "elementId": {
              "description": "Container resource ID",
              "type": "string",
              "minLength": 1
            },
            "index": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "selectionStrategy": {
              "type": "string",
              "enum": ["first", "random", "unique"]
            },
            "container": {
              "$ref": "#/$defs/dragAndDropContainer"
            }
          },
          "required": ["elementId"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "text": {
              "description": "Container text",
              "type": "string",
              "minLength": 1
            },
            "index": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "selectionStrategy": {
              "type": "string",
              "enum": ["first", "random", "unique"]
            },
            "container": {
              "$ref": "#/$defs/dragAndDropContainer"
            }
          },
          "required": ["text"],
          "additionalProperties": false
        }
      ]
    }
  }
}
