{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "conformance-profile.json",
  "title": "ConformanceProfile",
  "description": "Input to conformance/run.py: how to invoke a CLI and which probes to run against it.",
  "type": "object",
  "required": ["schema_version", "tool", "command", "timeout_seconds", "probes"],
  "additionalProperties": false,
  "properties": {
    "schema_version": {
      "type": "string",
      "enum": ["1.0"],
      "description": "Version of this profile format."
    },
    "tool": {
      "type": "string",
      "minLength": 1,
      "description": "Display name of the CLI under test."
    },
    "command": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string", "minLength": 1 },
      "description": "Invocation prefix. A first element containing a slash resolves against the profile's directory; a bare name resolves through PATH."
    },
    "timeout_seconds": {
      "type": "number",
      "exclusiveMinimum": 0,
      "maximum": 120,
      "description": "Per-run limit. A run that exceeds it is killed and counts as a hang."
    },
    "manifest": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Arguments after the prefix that print the manifest. Omit when the CLI has none; manifest_valid is then skipped."
    },
    "argument_order": {
      "$ref": "#/definitions/ArgumentOrder",
      "description": "One read invocation and a global option to move around it. Omit to skip argument_order."
    },
    "probes": {
      "type": "array",
      "minItems": 1,
      "items": { "$ref": "#/definitions/Probe" },
      "description": "Invocations to run. Include at least one read probe; add invalid, destructive, and stream probes to enable those checks."
    }
  },
  "definitions": {
    "ArgumentOrder": {
      "type": "object",
      "required": ["command_path", "local_args", "global_flag", "value", "alternate_value"],
      "additionalProperties": false,
      "properties": {
        "command_path": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "Command path of a side-effect-free command, such as [\"deployments\", \"list\"]."
        },
        "local_args": {
          "type": "array",
          "minItems": 2,
          "items": { "type": "string" },
          "description": "A command-local option and its value, such as [\"--limit\", \"5\"]. The kit places it before and after the global option."
        },
        "global_flag": {
          "type": "string",
          "pattern": "^--[a-z][a-z0-9-]*$",
          "description": "Long name of a global option, usually --format."
        },
        "value": {
          "type": "string",
          "minLength": 1,
          "description": "Value that makes stdout a ResponseEnvelope, usually json."
        },
        "alternate_value": {
          "type": "string",
          "minLength": 1,
          "description": "Another accepted value whose stdout differs from value's, such as plain. Detects a value overwritten by a subcommand default."
        },
        "positional": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[^-]",
          "description": "A positional argument command_path accepts. The kit then places local_args after it, and local_args must change the result."
        }
      }
    },
    "Probe": {
      "type": "object",
      "required": ["name", "argv", "kind"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Unique label shown in evidence."
        },
        "argv": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Arguments after the command prefix. Never include confirmation flags such as --yes."
        },
        "kind": {
          "type": "string",
          "enum": ["read", "destructive", "invalid", "stream"],
          "description": "read: side-effect free. destructive: expected to refuse without confirmation. invalid: expected to exit 2. stream: a side-effect-free command whose stdout is a JSONL stream (REQ-O-004)."
        },
        "dry_run_flag": {
          "type": "string",
          "pattern": "^--?[a-z][a-z0-9-]*$",
          "description": "Flag appended to preview a destructive probe. Required when kind is destructive; not allowed when kind is stream."
        },
        "deadline_seconds": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 120,
          "description": "Stream probes only: limit for the whole stream, from start to exit. Defaults to timeout_seconds. A stream still running at the deadline is killed and fails."
        },
        "signal": {
          "type": "string",
          "enum": ["INT"],
          "description": "Stream probes only: signal sent to the process after after_lines lines of stdout. Requires after_lines."
        },
        "after_lines": {
          "type": "integer",
          "minimum": 1,
          "description": "Stream probes only: number of stdout lines read before the kit sends signal. Requires signal."
        }
      },
      "dependencies": {
        "signal": ["after_lines"],
        "after_lines": ["signal"]
      },
      "allOf": [
        {
          "if": { "properties": { "kind": { "const": "destructive" } } },
          "then": { "required": ["dry_run_flag"] }
        },
        {
          "if": { "properties": { "kind": { "const": "stream" } } },
          "then": { "not": { "required": ["dry_run_flag"] } },
          "else": {
            "not": {
              "anyOf": [
                { "required": ["deadline_seconds"] },
                { "required": ["signal"] },
                { "required": ["after_lines"] }
              ]
            }
          }
        }
      ]
    }
  }
}
