{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://lexega.com/schemas/v1/policy.schema.json",
  "definitions": {
    "Policy": {
      "description": "A policy entry that references a rule ID and defines what action to take.\n\nPolicies do NOT define detection logic - that belongs in builtin or custom rules. A policy only says: \"when rule X fires, take action Y\".",
      "type": "object",
      "required": [
        "action",
        "rule_id"
      ],
      "properties": {
        "action": {
          "description": "What action to take when the rule fires",
          "allOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            }
          ]
        },
        "description": {
          "description": "Human-readable description of this policy entry (why the org has this policy)",
          "type": [
            "string",
            "null"
          ]
        },
        "envs": {
          "description": "Environment filter (e.g., [\"prod\", \"staging\"]) If not specified, policy applies to all environments",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "message_override": {
          "description": "Override the rule's message",
          "type": [
            "string",
            "null"
          ]
        },
        "requires_exception": {
          "description": "If true and action=block, an exception is required to proceed",
          "default": false,
          "type": "boolean"
        },
        "rule_id": {
          "description": "The rule ID to match (from builtin_rules.yaml, custom rules, or hardcoded signals). Examples: \"SNW-STG-ENC-OFF\", \"DIFF-WRITE-WHERE-RMV\", \"DML-WRITE-UNBOUNDED\", \"my-custom-rule\"",
          "type": "string"
        },
        "severity_override": {
          "description": "Override the rule's default severity",
          "anyOf": [
            {
              "$ref": "#/definitions/RiskLevel"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    },
    "PolicyAction": {
      "type": "string",
      "enum": [
        "allow",
        "block",
        "warn"
      ]
    },
    "RiskLevel": {
      "description": "Risk severity levels",
      "oneOf": [
        {
          "type": "string",
          "enum": [
            "Low",
            "Medium",
            "High",
            "Critical"
          ]
        },
        {
          "description": "Informational - not a risk, just tracking/awareness",
          "type": "string",
          "enum": [
            "Info"
          ]
        }
      ]
    },
    "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"
          }
        }
      }
    },
    "SeverityActions": {
      "description": "Severity-based fallback actions for signals without explicit policy rules. Checked after explicit `policies` entries but before `default_action`.\n\nExample (simple global): ```yaml severity_actions: - critical: block high: warn ```\n\nExample (with scoping): ```yaml severity_actions: - critical: block          # Default for all paths high: warn - scope: path_patterns: [\"tests/**\"] critical: warn            # Override for tests high: allow ```",
      "type": "object",
      "properties": {
        "critical": {
          "description": "Action for critical-severity signals (default: none, falls through)",
          "anyOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            },
            {
              "type": "null"
            }
          ]
        },
        "high": {
          "description": "Action for high-severity signals (default: none, falls through)",
          "anyOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            },
            {
              "type": "null"
            }
          ]
        },
        "info": {
          "description": "Action for info-severity signals (default: none, falls through)",
          "anyOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            },
            {
              "type": "null"
            }
          ]
        },
        "low": {
          "description": "Action for low-severity signals (default: none, falls through)",
          "anyOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            },
            {
              "type": "null"
            }
          ]
        },
        "medium": {
          "description": "Action for medium-severity signals (default: none, falls through)",
          "anyOf": [
            {
              "$ref": "#/definitions/PolicyAction"
            },
            {
              "type": "null"
            }
          ]
        },
        "scope": {
          "description": "Optional scope constraints (envs, paths, repos, etc.) If not specified, this entry applies globally as a fallback.",
          "anyOf": [
            {
              "$ref": "#/definitions/ScopedConstraints"
            },
            {
              "type": "null"
            }
          ]
        }
      }
    }
  },
  "title": "PolicyBundle",
  "type": "object",
  "required": [
    "default_action",
    "policies",
    "policy_id",
    "policy_version",
    "schema_version"
  ],
  "properties": {
    "activated_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "authored_by": {
      "description": "Lightweight provenance metadata (recommended for enterprise ops). These fields are optional and intended to point to external version control / change history.",
      "type": [
        "string",
        "null"
      ]
    },
    "default_action": {
      "$ref": "#/definitions/PolicyAction"
    },
    "generated_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "policies": {
      "description": "Policy entries that reference rule IDs from builtin or custom rules. Each entry specifies what action to take when a rule fires.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/Policy"
      }
    },
    "policy_id": {
      "type": "string"
    },
    "policy_version": {
      "type": "string"
    },
    "schema_version": {
      "type": "integer",
      "format": "uint32",
      "minimum": 0.0,
      "const": 1,
      "description": "Policy bundle schema version (must be 1)"
    },
    "severity_actions": {
      "description": "Severity-based fallback actions for signals without explicit policy rules. Checked after `policies` entries but before `default_action`.\n\nThis is a list of entries, evaluated in order. The first entry that matches the current context (via `scope`) is used. Entries without `scope` match everything (use as global fallback, typically last).\n\nExample: ```yaml severity_actions: - critical: block high: warn ```\n\nWith scoping: ```yaml severity_actions: - scope: path_patterns: [\"tests/**\"] critical: warn           # Lenient for tests - critical: block          # Default for everything else high: warn ```",
      "type": "array",
      "items": {
        "$ref": "#/definitions/SeverityActions"
      }
    },
    "source_ref": {
      "type": [
        "string",
        "null"
      ]
    }
  }
}