{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/PerryLink/dsh-permission-rules/main/docs/rules-format.schema.json",
  "title": "dsh-permission-rules rule file",
  "description": "Declarative allow/deny/ask permission rules for DeepSeek Harness, evaluated in file order with first-match-wins semantics.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "rules": {
      "description": "The ordered rule list; omitted or empty means an empty rule set (everything passes through).",
      "type": "array",
      "items": { "$ref": "#/$defs/rule" }
    }
  },
  "$defs": {
    "rule": {
      "type": "object",
      "additionalProperties": false,
      "required": ["action", "reason"],
      "properties": {
        "match": { "$ref": "#/$defs/match" },
        "action": {
          "description": "What the rule does to a matching tool call.",
          "enum": ["allow", "deny", "ask"]
        },
        "reason": {
          "description": "Non-empty explanation; deny/ask reasons become model-visible tool results or approval reasons.",
          "type": "string",
          "minLength": 1
        },
        "enabled": {
          "description": "false keeps the rule visible but inert (displayed as disabled).",
          "type": "boolean"
        },
        "description": {
          "description": "Optional one-line explanation shown by /rules.",
          "type": "string",
          "minLength": 1
        },
        "tags": {
          "description": "Free-form labels shown by /rules.",
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        }
      }
    },
    "match": {
      "description": "Match dimensions combined with AND; absent dimensions impose no restriction.",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "tools": {
          "description": "Tool-name globs (always glob semantics, regardless of patternMode).",
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        },
        "agents": {
          "description": "Agent-identity selector globs (main, subagent, preset:<name>); any selector matching any candidate satisfies the dimension. Unknown identity never matches.",
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        },
        "params": {
          "description": "Param key to pattern(s). EVERY key must be present and match; a !-prefixed pattern negates. Scalars are stringified; arrays and nested objects contribute their scalar leaves.",
          "type": "object",
          "additionalProperties": {
            "oneOf": [
              { "$ref": "#/$defs/pattern" },
              {
                "type": "array",
                "items": { "$ref": "#/$defs/pattern" },
                "minItems": 1
              }
            ]
          }
        },
        "paths": {
          "description": "Workspace-relative path patterns; ANY candidate path matching ANY pattern satisfies the dimension. Candidates come from documented argument keys at any nesting depth.",
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        },
        "absent": {
          "description": "Argument keys that must be ABSENT; every listed key must be missing.",
          "type": "array",
          "items": { "type": "string", "minLength": 1 }
        },
        "when": {
          "description": "Host conditions: every listed env var must be present and match; any listed platform matches.",
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "env": {
              "type": "object",
              "additionalProperties": {
                "oneOf": [
                  { "$ref": "#/$defs/pattern" },
                  {
                    "type": "array",
                    "items": { "$ref": "#/$defs/pattern" },
                    "minItems": 1
                  }
                ]
              }
            },
            "platform": {
              "type": "array",
              "items": {
                "enum": ["aix", "android", "darwin", "freebsd", "linux", "openbsd", "sunos", "win32"]
              }
            }
          }
        },
        "argv": {
          "description": "Shell command decomposition scope: the call must carry a command-string argument whose lexical decomposition (command words, argument tokens, redirect targets, pipeline signature) satisfies every listed field. command/pipeline are globs; args/anyArg follow patternMode. args requires EVERY pattern to match some token; anyArg requires SOME token to match a pattern; a !-prefixed args/anyArg pattern negates. A block must name at least one field.",
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "command": {
              "description": "Command-word globs; ANY simple-command word matching ANY pattern satisfies the field.",
              "type": "array",
              "items": { "type": "string", "minLength": 1 }
            },
            "args": {
              "description": "Argument-token patterns; EVERY pattern must match at least one token; a !-prefixed pattern negates.",
              "type": "array",
              "items": { "$ref": "#/$defs/pattern" },
              "minItems": 1
            },
            "anyArg": {
              "description": "Argument-token patterns; ANY token matching ANY pattern satisfies the field; a !-prefixed pattern negates.",
              "type": "array",
              "items": { "$ref": "#/$defs/pattern" },
              "minItems": 1
            },
            "pipeline": {
              "description": "Pipeline-signature globs matched against the command words joined by `|` (e.g. curl|sh); ANY pattern matching satisfies the field.",
              "type": "array",
              "items": { "type": "string", "minLength": 1 }
            }
          }
        },
        "network": {
          "description": "Network scope (a network rule): the call must carry a URL candidate (a web-tool URL argument, or a URL embedded in bash/pwsh command text) — or, at the proxy layer, a connection target — satisfying every listed dimension. domains are subdomain-inclusive unless wildcarded; ips accept IPv4/IPv6 literals, globs, and IPv4 CIDRs; ports accept \"*\", one port, or an inclusive low-high range (numeric YAML ports accepted); schemes are http/https. A network block must name at least one dimension.",
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "domains": {
              "description": "Domain patterns; ANY matching the target host satisfies the dimension.",
              "type": "array",
              "items": { "type": "string", "minLength": 1 }
            },
            "ips": {
              "description": "IP literals, globs, or IPv4 CIDRs; an IPv4-mapped IPv6 literal (::ffff:169.254.169.254, ::ffff:a9fe:a9fe) is unmapped to its dotted IPv4 form before matching, and a mapped pattern also covers the plain IPv4 spelling; ANY matching the target address satisfies the dimension.",
              "type": "array",
              "items": { "type": "string", "minLength": 1 }
            },
            "ports": {
              "description": "\"*\", one port, or an inclusive low-high range; ANY matching the effective port satisfies the dimension.",
              "type": "array",
              "items": {
                "oneOf": [
                  { "type": "string", "minLength": 1 },
                  { "type": "integer", "minimum": 0, "maximum": 65535 }
                ]
              }
            },
            "schemes": {
              "description": "Target scheme must be one of these.",
              "type": "array",
              "items": { "enum": ["http", "https"] }
            }
          }
        }
      }
    },
    "pattern": {
      "oneOf": [
        { "type": "string", "minLength": 1 },
        { "type": "number" },
        { "type": "boolean" }
      ]
    }
  }
}
