{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://lexega.com/schemas/v1/exceptions.schema.json",
  "definitions": {
    "ExceptionGrant": {
      "type": "object",
      "required": [
        "approved_at",
        "approved_by",
        "exception_id",
        "policy_id",
        "reason",
        "rule_id",
        "scope",
        "ticket"
      ],
      "properties": {
        "approved_at": {
          "type": "string",
          "format": "date-time"
        },
        "approved_by": {
          "type": "string"
        },
        "created_by": {
          "type": [
            "string",
            "null"
          ]
        },
        "exception_id": {
          "type": "string"
        },
        "policy_id": {
          "type": "string"
        },
        "reason": {
          "type": "string"
        },
        "rule_id": {
          "type": "string"
        },
        "scope": {
          "description": "Scope constraint for this exception. Required field. Use `global` for blanket exceptions (must be explicit). Use `scoped` for constrained exceptions (must have at least one constraint).",
          "allOf": [
            {
              "$ref": "#/definitions/ExceptionScope"
            }
          ]
        },
        "ticket": {
          "type": "string"
        }
      }
    },
    "ExceptionScope": {
      "description": "Exception scope - explicitly typed to prevent accidental blanket exceptions.\n\n# YAML Formats\n\n## Global (blanket exception - matches all contexts) ```yaml scope: global: expires_at: \"2025-12-31T23:59:59Z\"  # optional ```\n\n## Scoped (constrained - must have at least one constraint) ```yaml scope: scoped: envs: [\"dev\", \"staging\"] repos: [\"myorg/myrepo\"] path_patterns: [\"**/test_*.sql\"] expires_at: \"2025-12-31T23:59:59Z\" ```",
      "oneOf": [
        {
          "description": "Blanket exception - matches all environments, repos, paths, and roles. Use with caution. Requires explicit declaration to prevent accidents.",
          "type": "object",
          "required": [
            "global"
          ],
          "properties": {
            "global": {
              "type": "object",
              "properties": {
                "expires_at": {
                  "description": "Optional expiration timestamp (UTC). Exception is invalid after this time.",
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          },
          "additionalProperties": false
        },
        {
          "description": "Scoped exception - constrained to specific environments, repos, paths, or roles. At least one constraint field should be specified (validated at load time).",
          "type": "object",
          "required": [
            "scoped"
          ],
          "properties": {
            "scoped": {
              "$ref": "#/definitions/ScopedConstraints"
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "ScopedConstraints": {
      "description": "Constraints for a scoped exception. At least one field should be non-empty.",
      "type": "object",
      "properties": {
        "envs": {
          "description": "Environment names (case-insensitive match)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "expires_at": {
          "description": "Optional expiration timestamp (UTC). Exception is invalid after this time.",
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "path_patterns": {
          "description": "Glob patterns for paths (e.g., \"**/test_*.sql\", \"src/**/*.sql\")",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "path_prefixes": {
          "description": "Path prefixes (case-insensitive prefix match)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "paths": {
          "description": "Exact file paths (case-insensitive match)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "repo_prefixes": {
          "description": "Repository name prefixes (case-insensitive prefix match)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "repos": {
          "description": "Exact repository names (case-insensitive match)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "roles": {
          "description": "Snowflake roles (case-insensitive match against USE ROLE in SQL)",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      }
    }
  },
  "title": "ExceptionBundle",
  "type": "object",
  "required": [
    "exceptions",
    "schema_version"
  ],
  "properties": {
    "exceptions": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/ExceptionGrant"
      }
    },
    "schema_version": {
      "type": "integer",
      "format": "uint32",
      "minimum": 0.0,
      "const": 1,
      "description": "Exception bundle schema version (must be 1)"
    }
  }
}