{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://getpromptscript.dev/schema/config.json",
  "title": "PromptScript Configuration",
  "description": "PromptScript configuration file (promptscript.yaml).",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "Project identifier"
    },
    "syntax": {
      "type": "string",
      "description": "PromptScript syntax version"
    },
    "description": {
      "type": "string",
      "description": "Project description"
    },
    "telemetry": {
      "type": "boolean",
      "description": "Enable anonymous aggregate usage telemetry for this project"
    },
    "extends": {
      "type": "string",
      "description": "Extend another configuration file. Paths are resolved relative to the current config file.",
      "example": "extends: '../base-config.yaml'"
    },
    "inherit": {
      "type": "string",
      "description": "Inheritance path"
    },
    "input": {
      "type": "object",
      "properties": {
        "entry": {
          "type": "string",
          "description": "Entry file path (defaults to '.promptscript/project.prs')"
        },
        "include": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Glob patterns for additional files to include"
        },
        "exclude": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Glob patterns for files to exclude"
        }
      },
      "additionalProperties": false,
      "description": "Input file configuration. Controls which PromptScript files to compile."
    },
    "registry": {
      "type": "object",
      "properties": {
        "path": {
          "type": "string",
          "description": "Local path to registry"
        },
        "url": {
          "type": "string",
          "description": "Remote URL (HTTP registry)"
        },
        "git": {
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "description": "Git repository URL (HTTPS or SSH)"
            },
            "fallbackUrl": {
              "type": "string",
              "description": "Fallback Git URL to try when the primary `url` fails with an auth error. Useful when the registry references an HTTPS URL but the user authenticates via SSH (or vice versa).",
              "example": "registry:\n  git:\n    url: 'https://github.com/org/registry.git'\n    fallbackUrl: 'git@github.com:org/registry.git'"
            },
            "ref": {
              "type": "string",
              "description": "Git ref to checkout (branch, tag, or commit hash).",
              "default": "main"
            },
            "path": {
              "type": "string",
              "description": "Subdirectory within the repository to use as registry root.",
              "examples": [
                "registry/"
              ],
              "example": "registry/"
            },
            "timeout": {
              "type": "number",
              "description": "Maximum wall-clock time in milliseconds for each Git operation. Defaults to 60000; can also be raised globally via PROMPTSCRIPT_GIT_TIMEOUT."
            },
            "auth": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "token",
                    "ssh"
                  ],
                  "description": "Authentication type.\n- 'token': Use a personal access token (PAT)\n- 'ssh': Use SSH key authentication"
                },
                "token": {
                  "type": "string",
                  "description": "Personal access token (for token auth)"
                },
                "tokenEnvVar": {
                  "type": "string",
                  "description": "Environment variable containing the token"
                },
                "sshKeyPath": {
                  "type": "string",
                  "description": "Path to SSH key (for SSH auth).",
                  "default": "~/.ssh/id_rsa"
                }
              },
              "required": [
                "type"
              ],
              "additionalProperties": false,
              "description": "Authentication options for private repositories"
            }
          },
          "required": [
            "url"
          ],
          "additionalProperties": false,
          "description": "Git repository configuration. When specified, the registry will be cloned from a Git repository."
        },
        "cache": {
          "type": "object",
          "properties": {
            "enabled": {
              "type": "boolean",
              "description": "Whether caching is enabled"
            },
            "ttl": {
              "type": "number",
              "description": "Cache TTL in milliseconds"
            }
          },
          "additionalProperties": false,
          "description": "Cache settings"
        },
        "auth": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "bearer",
                "basic"
              ],
              "description": "Authentication type"
            },
            "token": {
              "type": "string",
              "description": "Token for bearer auth, or \"username:password\" for basic auth"
            },
            "tokenEnvVar": {
              "type": "string",
              "description": "Environment variable containing the token (alternative to token)"
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false,
          "description": "Authentication for HTTP registries"
        }
      },
      "additionalProperties": false,
      "description": "Registry configuration"
    },
    "registries": {
      "$ref": "#/definitions/RegistriesConfig",
      "description": "Named registry aliases for multi-source imports. Maps alias names to Git repository URLs. Coexists with `registry` - aliases take precedence for matching paths."
    },
    "watch": {
      "type": "object",
      "properties": {
        "include": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Glob patterns for files to watch (defaults to '**\\/*.prs')"
        },
        "exclude": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Glob patterns for files to ignore"
        },
        "debounce": {
          "type": "number",
          "description": "Debounce time in milliseconds before recompiling"
        },
        "clearScreen": {
          "type": "boolean",
          "description": "Clear screen before each recompilation"
        }
      },
      "additionalProperties": false,
      "description": "Watch mode configuration. Settings for `prs compile --watch`."
    },
    "output": {
      "type": "object",
      "properties": {
        "baseDir": {
          "type": "string",
          "description": "Base directory for all output files"
        },
        "header": {
          "type": "string",
          "description": "Custom header to prepend to generated files"
        },
        "overwrite": {
          "type": "boolean",
          "description": "Whether to overwrite existing files without warning"
        },
        "resources": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OutputResourceKind"
          },
          "description": "Compile only selected resource kinds (agents, skills, commands, mcp, hooks, plugins, main). A global install can emit agent and skill directories without unrelated root instruction files."
        }
      },
      "additionalProperties": false,
      "description": "Output configuration. Global output settings applied to all targets."
    },
    "builds": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/BuildProfileConfig"
      },
      "description": "Named per-command build profiles. Profiles let one repository build multiple instruction artifacts to different target directories without changing the default project compile."
    },
    "skillTargets": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "description": "Per-source output directories for `@use` imports.\n\nMaps a `@use` source string (matching the path's `raw` value) to a relative directory underneath each target's skill output location. An inline `into \"<path>\"` on the same `@use` declaration overrides the configured value.",
      "example": "skillTargets:\n  \"github.com/coreyhaines31/marketingskills/skills/seo-audit\": \"skills/seo-audit\""
    },
    "formatting": {
      "$ref": "#/definitions/FormattingConfig",
      "description": "Formatting configuration. Controls how generated markdown files are formatted.",
      "example": "formatting:\n  prettier: true  # Auto-detect .prettierrc\n\nformatting:\n  tabWidth: 4\n  proseWrap: always"
    },
    "targets": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/TargetEntry"
      },
      "description": "Output targets.\n\nOptional only when every target lives in a named build profile: such builds-only projects compile through `--build <name>` or `--all-builds`.\n\nCan be simple names or objects with configuration:",
      "example": "targets:\n  - github\n  - claude:\n      convention: markdown\n      output: custom/CLAUDE.md"
    },
    "customConventions": {
      "type": "object",
      "additionalProperties": {
        "$ref": "#/definitions/OutputConvention"
      },
      "description": "Custom convention definitions. Register custom conventions that can be referenced by name in targets."
    },
    "models": {
      "$ref": "#/definitions/ModelsConfig",
      "description": "Model catalog settings: the model set the instructions are written for and custom model profiles.",
      "example": "models:\n  supported: [claude-opus-5-5, gpt-6-sol]"
    },
    "releases": {
      "$ref": "#/definitions/ReleasesConfig",
      "description": "Release settings for prompt bundles.",
      "example": "releases:\n  environments: [staging, prod]"
    },
    "eval": {
      "$ref": "#/definitions/EvalConfig",
      "description": "Eval settings: providers and run limits.",
      "example": "eval:\n  providers:\n    anthropic: { kind: anthropic, apiKeyEnvVar: ANTHROPIC_API_KEY }"
    },
    "governance": {
      "$ref": "#/definitions/GovernanceConfig",
      "description": "Governance settings: admins and approval roles.",
      "example": "governance:\n  admins: ['@acme/prompt-platform']\n  roles: { legal: ['@acme/legal'] }"
    },
    "universalDir": {
      "type": [
        "string",
        "boolean"
      ],
      "description": "Universal directory for auto-discovering skills and commands. Skills are discovered from `<universalDir>/skills/` and commands from `<universalDir>/commands/`.\n\n- `true` or omitted: Use default `.agents/` directory\n- `false`: Disable universal directory discovery\n- `string`: Custom path (relative to project root)",
      "default": ".agents",
      "example": "universalDir: true           # Use .agents/ (default)\nuniversalDir: '.my-agents'   # Custom directory\nuniversalDir: false          # Disable"
    },
    "includePromptScriptSkill": {
      "type": "boolean",
      "description": "Include the bundled PromptScript language skill in compilation output. When enabled, the SKILL.md that teaches AI agents how to work with .prs files is automatically added to each target's native skill directory.",
      "default": true
    },
    "validation": {
      "type": "object",
      "properties": {
        "requiredGuards": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "rules": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "enum": [
              "error",
              "warning",
              "off"
            ]
          }
        },
        "guardRequiresDepth": {
          "type": "number",
          "description": "Maximum depth for recursive guard requires resolution. Default: 3. Must be >= 1. Values <= 0 are clamped to 1 by the resolver."
        },
        "scanExternalContent": {
          "type": "boolean",
          "description": "Scan imported (registry cache / vendored) content with heuristic validation rules. Heuristic rules skip imported content by default because a project cannot fix findings in someone else's skill. Concrete security findings (decoded payloads, suspicious URLs) always scan imported content.",
          "default": false
        },
        "excludes": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ValidationExclude"
          },
          "description": "Rule exclusions for specific imports, bound to the pinned commit.",
          "example": "excludes:\n  - import: github.com/cloudflare/skills\n    commit: 1a2b3c4d5e6f...\n    rules: [blocked-patterns, authority-injection]"
        },
        "allowedPatterns": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Pattern sources exempt from blocked-patterns detection. A pattern is subtracted from the active set when its source text matches an entry exactly. Mirror of blockedPatterns for exceptions.",
          "example": "allowedPatterns:\n  - 'bypass\\s+(your\\s+)?(rules|restrictions)'"
        }
      },
      "additionalProperties": false,
      "description": "Validation settings"
    },
    "policies": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/PolicyDefinition"
      },
      "description": "Policies for skill extensions and prompts"
    }
  },
  "required": [
    "id",
    "syntax"
  ],
  "additionalProperties": false,
  "definitions": {
    "RegistriesConfig": {
      "type": "object",
      "additionalProperties": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "$ref": "#/definitions/RegistryAliasEntry"
          }
        ]
      },
      "description": "Registry alias configuration. Maps alias names (e.g., '@acme') to Git URLs or extended entries.",
      "example": "registries:\n  '@company': 'github.com/company/promptscript-base'\n  '@internal':\n    url: 'git@gitlab.internal.com:company/monorepo'\n    root: 'packages/promptscript'"
    },
    "RegistryAliasEntry": {
      "type": "object",
      "properties": {
        "url": {
          "type": "string",
          "description": "Git repository URL (HTTPS or SSH)"
        },
        "fallbackUrl": {
          "type": "string",
          "description": "Fallback Git URL to try when the primary `url` fails with an auth error. Useful when registries reference HTTPS URLs but the user authenticates via SSH (or vice versa).",
          "example": "registries:\n  '@acme':\n    url: 'https://github.com/acme/standards.git'\n    fallbackUrl: 'git@github.com:acme/standards.git'"
        },
        "root": {
          "type": "string",
          "description": "Base path within the repository"
        },
        "timeout": {
          "type": "number",
          "description": "Maximum wall-clock time in milliseconds for each Git operation (clone, fetch, ls-remote) against this repository. Defaults to 60000; can also be raised globally via PROMPTSCRIPT_GIT_TIMEOUT.",
          "example": "registries:\n  '@internal':\n    url: 'git@gitlab.internal.com:company/monorepo'\n    root: 'packages/promptscript'\n    timeout: 600000"
        }
      },
      "required": [
        "url"
      ],
      "additionalProperties": false,
      "description": "Extended registry alias entry for monorepos or custom roots."
    },
    "OutputResourceKind": {
      "type": "string",
      "enum": [
        "main",
        "skills",
        "agents",
        "commands",
        "hooks",
        "mcp",
        "plugins"
      ],
      "description": "Resource kinds a compile run can select.\n\n`main` is the target's instruction surface (root instruction files, rules, workflows, local memory); everything else matches the catalog resource categories."
    },
    "BuildProfileConfig": {
      "type": "object",
      "properties": {
        "entry": {
          "type": "string",
          "description": "Entry file path for this build profile"
        },
        "output": {
          "type": "string",
          "description": "Output directory for this build profile"
        },
        "targets": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TargetEntry"
          },
          "description": "Target list for this build profile"
        }
      },
      "additionalProperties": false,
      "description": "Named build profile for compiling a specific entry point to specific outputs."
    },
    "TargetEntry": {
      "anyOf": [
        {
          "$ref": "#/definitions/TargetName"
        },
        {
          "type": "object",
          "properties": {
            "github": {
              "$ref": "#/definitions/TargetConfig"
            },
            "claude": {
              "$ref": "#/definitions/TargetConfig"
            },
            "cursor": {
              "$ref": "#/definitions/TargetConfig"
            },
            "antigravity": {
              "$ref": "#/definitions/TargetConfig"
            },
            "factory": {
              "$ref": "#/definitions/TargetConfig"
            },
            "opencode": {
              "$ref": "#/definitions/TargetConfig"
            },
            "gemini": {
              "$ref": "#/definitions/TargetConfig"
            },
            "windsurf": {
              "$ref": "#/definitions/TargetConfig"
            },
            "cline": {
              "$ref": "#/definitions/TargetConfig"
            },
            "roo": {
              "$ref": "#/definitions/TargetConfig"
            },
            "codex": {
              "$ref": "#/definitions/TargetConfig"
            },
            "continue": {
              "$ref": "#/definitions/TargetConfig"
            },
            "augment": {
              "$ref": "#/definitions/TargetConfig"
            },
            "goose": {
              "$ref": "#/definitions/TargetConfig"
            },
            "kilo": {
              "$ref": "#/definitions/TargetConfig"
            },
            "amp": {
              "$ref": "#/definitions/TargetConfig"
            },
            "trae": {
              "$ref": "#/definitions/TargetConfig"
            },
            "junie": {
              "$ref": "#/definitions/TargetConfig"
            },
            "kiro": {
              "$ref": "#/definitions/TargetConfig"
            },
            "cortex": {
              "$ref": "#/definitions/TargetConfig"
            },
            "crush": {
              "$ref": "#/definitions/TargetConfig"
            },
            "command-code": {
              "$ref": "#/definitions/TargetConfig"
            },
            "kode": {
              "$ref": "#/definitions/TargetConfig"
            },
            "mcpjam": {
              "$ref": "#/definitions/TargetConfig"
            },
            "mistral-vibe": {
              "$ref": "#/definitions/TargetConfig"
            },
            "mux": {
              "$ref": "#/definitions/TargetConfig"
            },
            "openhands": {
              "$ref": "#/definitions/TargetConfig"
            },
            "pi": {
              "$ref": "#/definitions/TargetConfig"
            },
            "qoder": {
              "$ref": "#/definitions/TargetConfig"
            },
            "qwen-code": {
              "$ref": "#/definitions/TargetConfig"
            },
            "zencoder": {
              "$ref": "#/definitions/TargetConfig"
            },
            "neovate": {
              "$ref": "#/definitions/TargetConfig"
            },
            "pochi": {
              "$ref": "#/definitions/TargetConfig"
            },
            "adal": {
              "$ref": "#/definitions/TargetConfig"
            },
            "iflow": {
              "$ref": "#/definitions/TargetConfig"
            },
            "openclaw": {
              "$ref": "#/definitions/TargetConfig"
            },
            "codebuddy": {
              "$ref": "#/definitions/TargetConfig"
            },
            "aider": {
              "$ref": "#/definitions/TargetConfig"
            },
            "amazon-q": {
              "$ref": "#/definitions/TargetConfig"
            },
            "warp": {
              "$ref": "#/definitions/TargetConfig"
            },
            "zed": {
              "$ref": "#/definitions/TargetConfig"
            },
            "jules": {
              "$ref": "#/definitions/TargetConfig"
            },
            "devin": {
              "$ref": "#/definitions/TargetConfig"
            },
            "grok": {
              "$ref": "#/definitions/TargetConfig"
            },
            "kimi": {
              "$ref": "#/definitions/TargetConfig"
            },
            "mimo": {
              "$ref": "#/definitions/TargetConfig"
            },
            "deep-agents": {
              "$ref": "#/definitions/TargetConfig"
            },
            "forgecode": {
              "$ref": "#/definitions/TargetConfig"
            },
            "hermes": {
              "$ref": "#/definitions/TargetConfig"
            },
            "gitlab-duo": {
              "$ref": "#/definitions/TargetConfig"
            },
            "bundle": {
              "$ref": "#/definitions/TargetConfig"
            },
            "prompt-text": {
              "$ref": "#/definitions/TargetConfig"
            }
          },
          "additionalProperties": {
            "$ref": "#/definitions/TargetConfig"
          }
        }
      ],
      "description": "Target can be a simple string name or a full configuration object."
    },
    "TargetName": {
      "anyOf": [
        {
          "$ref": "#/definitions/KnownTarget"
        },
        {
          "$ref": "#/definitions/CustomTarget"
        }
      ],
      "description": "Supported output targets. Includes all known built-in targets plus custom targets registered via `registerFormatter`.\n\n- Use `KnownTarget` when you need exhaustiveness checks in switch/if-else.\n- Use `TargetName` when you need to accept both known and custom targets.\n- Use `isKnownTarget()` to narrow a `TargetName` to `KnownTarget` at runtime.\n- Use `customTarget()` to create a `CustomTarget` from a plain string."
    },
    "KnownTarget": {
      "type": "string",
      "enum": [
        "github",
        "claude",
        "cursor",
        "antigravity",
        "factory",
        "opencode",
        "gemini",
        "windsurf",
        "cline",
        "roo",
        "codex",
        "continue",
        "augment",
        "goose",
        "kilo",
        "amp",
        "trae",
        "junie",
        "kiro",
        "cortex",
        "crush",
        "command-code",
        "kode",
        "mcpjam",
        "mistral-vibe",
        "mux",
        "openhands",
        "pi",
        "qoder",
        "qwen-code",
        "zencoder",
        "neovate",
        "pochi",
        "adal",
        "iflow",
        "openclaw",
        "codebuddy",
        "aider",
        "amazon-q",
        "warp",
        "zed",
        "jules",
        "devin",
        "grok",
        "kimi",
        "mimo",
        "deep-agents",
        "forgecode",
        "hermes",
        "gitlab-duo",
        "bundle",
        "prompt-text"
      ],
      "description": "Known built-in output targets. These are the targets with first-class formatter support."
    },
    "CustomTarget": {
      "type": "string",
      "description": "Branded type for custom (user-registered) target names. Use `customTarget()` to create values of this type."
    },
    "TargetConfig": {
      "type": "object",
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Whether this target is enabled.",
          "default": true
        },
        "output": {
          "type": "string",
          "description": "Custom output path for this target."
        },
        "convention": {
          "$ref": "#/definitions/ConventionName",
          "description": "Output convention ('xml', 'markdown', or custom name)."
        },
        "version": {
          "type": "string",
          "description": "Target version or format variant. Use 'legacy' for deprecated formats (e.g., Cursor's .cursorrules).",
          "example": "'legacy' | '1.0' | '2.0'"
        },
        "rulesMode": {
          "$ref": "#/definitions/FactoryRulesMode",
          "description": "Factory always-on rules output mode. Split mode requires Factory's `multifile` or `full` version.",
          "default": "monolith"
        },
        "guardsAsSkills": {
          "type": "boolean",
          "description": "Generate skills from",
          "default": true
        },
        "guardsSkillsListing": {
          "type": "boolean",
          "description": "List generated guard skills in main output file (Factory).",
          "default": true
        },
        "skillBaseDir": {
          "type": "string",
          "description": "Custom base directory for generated skill files. When set, skill files are emitted under this directory instead of the target's native skill directory (for example `.factory/skills`)."
        },
        "includeSkills": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          ],
          "description": "Controls which skills are emitted for this target.\n- `true` or omitted: emit all skills\n- `false`: emit no skills\n- string array: emit only the listed skill names"
        },
        "skillPath": {
          "type": "string",
          "enum": [
            "agents",
            "gemini",
            "both"
          ],
          "description": "Gemini skill path selection.\n- `agents`: use `.agents/skills/` (interoperable, fixture-confirmed default)\n- `gemini`: use `.gemini/skills/` (legacy)\n- `both`: emit to both paths (requires explicit opt-in, deduplicates content)",
          "default": "agents"
        },
        "maxThreads": {
          "type": "number",
          "description": "Codex: maximum number of parallel agent threads. Positive integer. Maps to Codex config, never to AGENTS.md."
        },
        "maxDepth": {
          "type": "number",
          "description": "Codex: maximum nesting depth for agent delegation. Positive integer. Maps to Codex config, never to AGENTS.md."
        },
        "agentsFile": {
          "type": "string",
          "description": "Codex: override the agents file name for scoped build profiles. Defaults to `AGENTS.md`. Use `AGENTS.override.md` only for scoped builds."
        },
        "agentsFrontmatter": {
          "type": "string",
          "const": "experimental",
          "description": "AGENTS.md v1.1 frontmatter mode.\n- `experimental`: emit YAML frontmatter with `description` and `tags` from",
          "default": "undefined (no frontmatter)"
        },
        "autoMode": {
          "type": "string",
          "enum": [
            "acceptEdits",
            "plan",
            "bypassPermissions"
          ],
          "description": "Claude: auto mode setting for project settings.json. Maps to `.claude/settings.json` `autoMode` field. Only valid for Claude target with fixture-confirmed project-local schema."
        }
      },
      "additionalProperties": false,
      "description": "Configuration for a single target."
    },
    "ConventionName": {
      "anyOf": [
        {
          "$ref": "#/definitions/BuiltInConventionName"
        },
        {
          "type": "string"
        }
      ],
      "description": "Convention name (built-in or custom)."
    },
    "BuiltInConventionName": {
      "type": "string",
      "enum": [
        "xml",
        "markdown"
      ],
      "description": "Built-in convention names."
    },
    "FactoryRulesMode": {
      "type": "string",
      "enum": [
        "monolith",
        "split"
      ],
      "description": "Factory always-on rules output mode.\n- `monolith`: Keep all rule content in AGENTS.md\n- `split`: Emit rule content under .factory/rules"
    },
    "FormattingConfig": {
      "type": "object",
      "properties": {
        "prettier": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "type": "string"
            },
            {
              "$ref": "#/definitions/PrettierMarkdownOptions"
            }
          ],
          "description": "Enable Prettier formatting.\n- `true`: Auto-detect .prettierrc in project\n- `string`: Path to Prettier config file\n- `PrettierMarkdownOptions`: Explicit options",
          "example": "formatting:\n  prettier: true  # Auto-detect\n\nformatting:\n  prettier: \"./config/.prettierrc\"  # Explicit path\n\nformatting:\n  proseWrap: always\n  tabWidth: 4"
        },
        "proseWrap": {
          "type": "string",
          "enum": [
            "always",
            "never",
            "preserve"
          ],
          "description": "Explicit proseWrap setting (shorthand for prettier.proseWrap)."
        },
        "tabWidth": {
          "type": "number",
          "description": "Explicit tabWidth setting (shorthand for prettier.tabWidth)."
        },
        "printWidth": {
          "type": "number",
          "description": "Explicit printWidth setting (shorthand for prettier.printWidth)."
        }
      },
      "additionalProperties": false,
      "description": "Formatting configuration for output files. Controls how generated markdown is formatted."
    },
    "PrettierMarkdownOptions": {
      "type": "object",
      "properties": {
        "proseWrap": {
          "type": "string",
          "enum": [
            "always",
            "never",
            "preserve"
          ],
          "description": "How to wrap prose.\n- 'always': Wrap prose at printWidth\n- 'never': Do not wrap prose\n- 'preserve': Preserve original wrapping",
          "default": "preserve"
        },
        "tabWidth": {
          "type": "number",
          "description": "Number of spaces per indentation level.",
          "default": 2
        },
        "printWidth": {
          "type": "number",
          "description": "Maximum line width for prose wrapping.",
          "default": 80
        }
      },
      "additionalProperties": false,
      "description": "Prettier markdown formatting options. These options control how markdown output is formatted."
    },
    "OutputConvention": {
      "type": "object",
      "properties": {
        "name": {
          "$ref": "#/definitions/ConventionName",
          "description": "Convention identifier."
        },
        "description": {
          "type": "string",
          "description": "Human-readable description."
        },
        "section": {
          "$ref": "#/definitions/SectionRenderer",
          "description": "Section rendering configuration."
        },
        "subsection": {
          "$ref": "#/definitions/SectionRenderer",
          "description": "Subsection rendering (defaults to section config if not specified)."
        },
        "listStyle": {
          "type": "string",
          "enum": [
            "dash",
            "asterisk",
            "bullet",
            "numbered"
          ],
          "description": "How to render lists.",
          "default": "'dash' (- item)"
        },
        "codeBlockDelimiter": {
          "type": "string",
          "description": "Code block delimiter.",
          "default": "```"
        },
        "rootWrapper": {
          "type": "object",
          "properties": {
            "start": {
              "type": "string"
            },
            "end": {
              "type": "string"
            }
          },
          "required": [
            "start",
            "end"
          ],
          "additionalProperties": false,
          "description": "Whether to wrap content in a root element."
        }
      },
      "required": [
        "name",
        "section"
      ],
      "additionalProperties": false,
      "description": "Output convention definition."
    },
    "SectionRenderer": {
      "type": "object",
      "properties": {
        "start": {
          "type": "string",
          "description": "Template for section start. Variables: {{name}}, {{level}}, {{content}}",
          "example": "Markdown: \"{{#repeat level}}#{{/repeat}} {{name}}\""
        },
        "end": {
          "type": "string",
          "description": "Template for section end (optional, for paired tags). Variables: {{name}}",
          "example": "Markdown: \"\" (empty - no closing tag)"
        },
        "nameTransform": {
          "type": "string",
          "enum": [
            "kebab-case",
            "camelCase",
            "PascalCase",
            "none"
          ],
          "description": "Whether to convert section names.",
          "default": "kebab-case"
        },
        "indent": {
          "type": "string",
          "description": "Indentation per level.",
          "default": "'  ' (2 spaces)"
        }
      },
      "required": [
        "start"
      ],
      "additionalProperties": false,
      "description": "Section rendering configuration."
    },
    "ModelsConfig": {
      "type": "object",
      "properties": {
        "supported": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Model set the instructions are written and tested for. Entries are profile ids, aliases, or provider model identifiers. Validation reports agents and skills pinned outside this set.",
          "examples": [
            [
              "claude-opus-5-5",
              "gpt-6-sol"
            ]
          ],
          "example": [
            "claude-opus-5-5",
            "gpt-6-sol"
          ]
        },
        "profiles": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/ModelProfileInput"
          },
          "description": "Custom model profiles, or overrides of built-in profiles, keyed by profile id."
        }
      },
      "additionalProperties": false,
      "description": "Model catalog configuration (`models` in promptscript.yaml)."
    },
    "ModelProfileInput": {
      "type": "object",
      "properties": {
        "provider": {
          "type": "string",
          "description": "Model provider (e.g. 'anthropic', 'openai', 'google', 'xai')"
        },
        "family": {
          "type": "string",
          "description": "Family grouping the versions of one model line (e.g. 'claude-opus'). The newest non-retired release of a family backs its floating alias."
        },
        "version": {
          "type": "string",
          "description": "Version inside the family (e.g. '4.5')"
        },
        "displayName": {
          "type": "string",
          "description": "Human-readable name (e.g. 'Claude Opus 4.5')"
        },
        "apiId": {
          "type": "string",
          "description": "Provider API model identifier"
        },
        "aliases": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Additional names that resolve to this profile"
        },
        "status": {
          "$ref": "#/definitions/ModelStatus",
          "description": "Lifecycle status"
        },
        "successor": {
          "type": "string",
          "description": "Profile id of the recommended replacement"
        },
        "releaseDate": {
          "type": "string",
          "description": "Release date in YYYY-MM-DD format"
        },
        "retirementDate": {
          "type": "string",
          "description": "Date the provider stops (or stopped) serving the model, in YYYY-MM-DD format"
        },
        "targets": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          },
          "description": "Native model names per target. A target listed here always receives this name, even when its naming scheme or provider list would not map the model. Keys are target names, matched case-insensitively. Only targets that write a native model field use them; validation reports other keys.",
          "example": "targets:\n  github: Claude Opus 9 (Preview)"
        },
        "pricing": {
          "$ref": "#/definitions/ModelPricing",
          "description": "List price per million tokens, for cost estimates"
        }
      },
      "additionalProperties": false,
      "description": "Model profile declared in promptscript.yaml.\n\nA key of `models.profiles` that matches a built-in profile id overrides only the fields it sets. Any other key adds a new profile, so a team can use a model before PromptScript ships it in the built-in catalog.",
      "example": "models:\n  profiles:\n    claude-opus-9:\n      provider: anthropic\n      family: claude-opus\n      version: '9'\n      displayName: Claude Opus 9\n      apiId: claude-opus-9\n      releaseDate: '2027-01-15'"
    },
    "ModelStatus": {
      "type": "string",
      "enum": [
        "current",
        "legacy",
        "deprecated",
        "retired"
      ],
      "description": "Lifecycle status of a model profile.\n- `current`: recommended for new instructions\n- `legacy`: still served, but superseded by a newer version\n- `deprecated`: the provider announced a retirement date\n- `retired`: the provider no longer serves the model"
    },
    "ModelPricing": {
      "type": "object",
      "properties": {
        "inputUsdPerMillion": {
          "type": "number",
          "description": "Price of one million input tokens in USD.",
          "minimum": 0
        },
        "outputUsdPerMillion": {
          "type": "number",
          "description": "Price of one million output tokens in USD.",
          "minimum": 0
        },
        "checkedOn": {
          "type": "string",
          "description": "Date the price was checked against the provider, YYYY-MM-DD.",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        }
      },
      "required": [
        "inputUsdPerMillion",
        "outputUsdPerMillion",
        "checkedOn"
      ],
      "additionalProperties": false,
      "description": "List price of a model, in USD per million tokens. Cache, batch and long-context tiers are ignored, so a cost computed from it is an estimate."
    },
    "ReleasesConfig": {
      "type": "object",
      "properties": {
        "environments": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ReleaseEnvironmentName"
          },
          "description": "Environment names in promotion order, first to last. Each name is lowercase letters, digits and hyphens, starting with a letter.",
          "examples": [
            [
              "staging",
              "prod"
            ]
          ],
          "example": [
            "staging",
            "prod"
          ],
          "minItems": 1,
          "maxItems": 16,
          "uniqueItems": true
        }
      },
      "required": [
        "environments"
      ],
      "additionalProperties": false,
      "description": "Release configuration (`releases` in promptscript.yaml)."
    },
    "ReleaseEnvironmentName": {
      "type": "string",
      "description": "Name of a release environment.",
      "pattern": "^[a-z][a-z0-9-]{0,31}$"
    },
    "EvalConfig": {
      "type": "object",
      "properties": {
        "providers": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/EvalProviderConfig"
          },
          "description": "Providers by name. The name matches the `provider` of the resolved model profile, such as `anthropic` or `openai`.",
          "example": "providers:\n  anthropic: { kind: anthropic, apiKeyEnvVar: ANTHROPIC_API_KEY }"
        },
        "timeoutMs": {
          "type": "integer",
          "description": "Time limit of one model call in milliseconds, response body included.",
          "minimum": 1,
          "default": 60000
        },
        "maxOutputTokens": {
          "type": "integer",
          "description": "Output token cap of a model call when the prompt sets no `maxTokens`.",
          "minimum": 1,
          "default": 1024
        },
        "maxCases": {
          "type": "integer",
          "description": "Largest number of dataset rows a suite runs.",
          "minimum": 1,
          "default": 1000
        },
        "reportDir": {
          "type": "string",
          "description": "Directory for eval reports, relative to the project root.",
          "examples": [
            ".promptscript/eval-reports"
          ],
          "example": ".promptscript/eval-reports"
        }
      },
      "additionalProperties": false,
      "description": "Eval settings (`eval` in promptscript.yaml): the providers eval suites call and the limits of a run."
    },
    "EvalProviderConfig": {
      "type": "object",
      "properties": {
        "kind": {
          "$ref": "#/definitions/EvalProviderKind",
          "description": "Request and response format: the Anthropic Messages API or an OpenAI-compatible chat API"
        },
        "baseUrl": {
          "type": "string",
          "description": "Base URL of the provider API.",
          "examples": [
            "https://llm.corp.example/v1"
          ],
          "example": "https://llm.corp.example/v1"
        },
        "apiKeyEnvVar": {
          "type": "string",
          "description": "Environment variable that holds the provider API key. The key itself is never read from the config.",
          "examples": [
            "ANTHROPIC_API_KEY"
          ],
          "pattern": "^[A-Z_][A-Z0-9_]*$",
          "example": "ANTHROPIC_API_KEY"
        }
      },
      "required": [
        "kind"
      ],
      "additionalProperties": false,
      "description": "One model provider that eval suites can call."
    },
    "EvalProviderKind": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai-compatible"
      ],
      "description": "Request and response format of an eval provider."
    },
    "GovernanceConfig": {
      "type": "object",
      "properties": {
        "admins": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GovernanceOwner"
          },
          "description": "Owners of the config, the approvals root and the releases. Under `extends` the lists are joined and cannot shrink.",
          "examples": [
            [
              "@acme/prompt-platform"
            ]
          ],
          "example": [
            "@acme/prompt-platform"
          ],
          "uniqueItems": true
        },
        "roles": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/GovernanceRoleOwners"
          },
          "description": "Approval roles by name, each with the owners that review it. At most 32 roles. Under `extends` each role's list is joined with the base list and cannot shrink.",
          "examples": [
            {
              "legal": [
                "@acme/legal"
              ],
              "security": [
                "@acme/appsec"
              ]
            }
          ],
          "example": {
            "legal": [
              "@acme/legal"
            ],
            "security": [
              "@acme/appsec"
            ]
          },
          "maxProperties": 32,
          "propertyNames": {
            "pattern": "^(?!(?:con|prn|aux|nul|com[0-9]|lpt[0-9])$)[a-z][a-z0-9-]{0,31}$"
          }
        },
        "dataClasses": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Data class names the project uses.",
          "examples": [
            [
              "pii",
              "phi",
              "financial",
              "credentials",
              "internal",
              "public"
            ]
          ],
          "example": [
            "pii",
            "phi",
            "financial",
            "credentials",
            "internal",
            "public"
          ],
          "uniqueItems": true
        },
        "codeowners": {
          "$ref": "#/definitions/GovernanceCodeownersConfig",
          "description": "Generated CODEOWNERS settings"
        }
      },
      "additionalProperties": false,
      "description": "Governance settings (`governance` in promptscript.yaml): who owns the setup and which teams review each approval role."
    },
    "GovernanceOwner": {
      "type": "string",
      "description": "Git host identity of an owner, in a form CODEOWNERS accepts: a user (`@jane`), a team (`@acme/legal`, GitLab subgroups allowed) or an email address. A user may hold `.`, `_` and `-` between letters or digits, like GitLab users (`@jane.doe`) and GitHub managed users (`@jane_acme`).",
      "pattern": "^(?:@[A-Za-z0-9]+(?:[_.-][A-Za-z0-9]+)*|@[A-Za-z0-9][A-Za-z0-9_.-]*(?:/[A-Za-z0-9][A-Za-z0-9_.-]*)+|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})$",
      "maxLength": 254
    },
    "GovernanceRoleOwners": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/GovernanceOwner"
      },
      "description": "Owners that review one approval role, at least one.",
      "minItems": 1,
      "uniqueItems": true
    },
    "GovernanceCodeownersConfig": {
      "type": "object",
      "properties": {
        "output": {
          "type": "string",
          "enum": [
            ".github/CODEOWNERS",
            "CODEOWNERS",
            "docs/CODEOWNERS",
            ".gitlab/CODEOWNERS"
          ],
          "description": "Path of the CODEOWNERS file, relative to the repository root.",
          "default": ".github/CODEOWNERS"
        },
        "protect": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "More paths owned by the admins, relative to the repository root. Under `extends` the lists are joined and cannot shrink.",
          "examples": [
            [
              ".github/workflows"
            ]
          ],
          "example": [
            ".github/workflows"
          ]
        },
        "prompts": {
          "type": "boolean",
          "description": "Write lines for the `.prs` files that define prompts.",
          "default": true
        }
      },
      "additionalProperties": false,
      "description": "Settings of the generated CODEOWNERS file."
    },
    "ValidationExclude": {
      "type": "object",
      "properties": {
        "import": {
          "type": "string",
          "description": "Import source as declared in `@use` or as pinned in promptscript.lock"
        },
        "commit": {
          "type": "string",
          "description": "Commit SHA the exclude was reviewed at; must match the lockfile pin"
        },
        "rules": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Rule names (e.g. `blocked-patterns`) or IDs (e.g. `PS005`) to skip for this import"
        }
      },
      "required": [
        "import",
        "rules"
      ],
      "additionalProperties": false,
      "description": "Per-import validation exclusion, bound to the commit pinned in promptscript.lock.\n\nSuppression is declared by the consumer, never by the scanned content: an imported file cannot mute its own scan. When the lockfile pins a different commit than the one recorded here, the exclude stops applying and validation fails, so the consumer consciously re-reviews the new content."
    },
    "PolicyDefinition": {
      "anyOf": [
        {
          "$ref": "#/definitions/LayerBoundaryPolicy"
        },
        {
          "$ref": "#/definitions/PropertyProtectionPolicy"
        },
        {
          "$ref": "#/definitions/RegistryAllowlistPolicy"
        },
        {
          "$ref": "#/definitions/RequiredMetadataPolicy"
        },
        {
          "$ref": "#/definitions/RiskFloorPolicy"
        },
        {
          "$ref": "#/definitions/ModelAllowlistPolicy"
        },
        {
          "$ref": "#/definitions/RequiredApprovalsPolicy"
        },
        {
          "$ref": "#/definitions/RequiredEvalsPolicy"
        },
        {
          "$ref": "#/definitions/ContentRulesPolicy"
        },
        {
          "$ref": "#/definitions/MaxExperimentWeightPolicy"
        }
      ],
      "description": "Union of all policy definition types."
    },
    "LayerBoundaryPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "layer-boundary",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "layers": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Ordered list of layers from base to leaf"
        },
        "maxDistance": {
          "type": "number",
          "description": "Maximum allowed distance between source and target layers (default: 1)"
        }
      },
      "required": [
        "kind",
        "layers",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Layer-boundary policy: controls which layers can extend which."
    },
    "PolicySeverity": {
      "type": "string",
      "enum": [
        "error",
        "warning"
      ],
      "description": "Severity for policy violations."
    },
    "PropertyProtectionPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "property-protection",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "properties": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Properties that cannot be overridden"
        },
        "targetPattern": {
          "type": "string",
          "description": "Glob pattern for target skills (e.g., '@core/*'). If omitted, applies to all."
        }
      },
      "required": [
        "kind",
        "name",
        "properties",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Property-protection policy: prevents overriding specific properties."
    },
    "RegistryAllowlistPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "registry-allowlist",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "allowed": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "List of allowed registry prefixes"
        }
      },
      "required": [
        "allowed",
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Registry-allowlist policy: restricts which registries can provide extensions."
    },
    "RequiredMetadataPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "required-metadata",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "fields": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "risk",
              "owner",
              "domain",
              "dataClasses",
              "reviewers"
            ]
          },
          "description": "Governance fields every matched prompt must have",
          "minItems": 1,
          "uniqueItems": true
        }
      },
      "required": [
        "fields",
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Required-metadata policy: matched prompts must set governance fields."
    },
    "PromptPolicyMatch": {
      "type": "object",
      "properties": {
        "minRisk": {
          "$ref": "#/definitions/RiskLevel",
          "description": "Policy risk is at or above this level"
        },
        "domains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Effective domain is one of these",
          "minItems": 1
        },
        "dataClasses": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Any effective data class is one of these",
          "minItems": 1
        }
      },
      "additionalProperties": false,
      "description": "Selects the prompts a policy applies to. Keys combine with AND; no `match` selects every prompt."
    },
    "RiskLevel": {
      "type": "string",
      "enum": [
        "low",
        "medium",
        "high",
        "critical"
      ],
      "description": "Risk classification of a prompt."
    },
    "RiskFloorPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "risk-floor",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "floors": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/RiskFloorRow"
          },
          "description": "Rows; the floor is the highest `minimum` of the matching rows",
          "minItems": 1
        }
      },
      "required": [
        "floors",
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Risk-floor policy: derives a minimum risk from data classes, domains and input types. Every other policy matches on the higher of authored risk and this floor."
    },
    "RiskFloorRow": {
      "type": "object",
      "properties": {
        "dataClasses": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Any effective data class is one of these",
          "minItems": 1
        },
        "domains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Effective domain is one of these",
          "minItems": 1
        },
        "inputTypes": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/PromptScalarType"
          },
          "description": "Any input has one of these types",
          "minItems": 1
        },
        "minimum": {
          "$ref": "#/definitions/RiskLevel",
          "description": "Lowest policy risk of a matching prompt"
        }
      },
      "required": [
        "minimum"
      ],
      "additionalProperties": false,
      "description": "One row of a risk floor. The row matches when any of its conditions matches.",
      "minProperties": 2
    },
    "PromptScalarType": {
      "type": "string",
      "enum": [
        "string",
        "number",
        "boolean",
        "enum"
      ],
      "description": "Scalar value types allowed for prompt inputs and output fields."
    },
    "ModelAllowlistPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "model-allowlist",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "models": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Model catalog ids or aliases",
          "minItems": 1
        }
      },
      "required": [
        "kind",
        "models",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Model-allowlist policy: matched prompts must use one of the listed models."
    },
    "RequiredApprovalsPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "required-approvals",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "environments": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ReleaseEnvironmentName"
          },
          "description": "Environments from `releases.environments` the policy applies to. Absent means all.",
          "minItems": 1
        },
        "approvals": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/RequiredApproval"
          },
          "description": "Approvals per role",
          "minItems": 1
        }
      },
      "required": [
        "approvals",
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Required-approvals policy: matched prompts need approvals before promotion."
    },
    "RequiredApproval": {
      "type": "object",
      "properties": {
        "role": {
          "$ref": "#/definitions/GovernanceRoleName",
          "description": "Role from `governance.roles`"
        },
        "count": {
          "type": "integer",
          "description": "Distinct approvers needed. Only 1 is accepted while approval records are unsigned, since unsigned records cannot tell two approvers apart under one review.",
          "minimum": 1,
          "maximum": 1,
          "default": 1
        }
      },
      "required": [
        "role"
      ],
      "additionalProperties": false,
      "description": "Approvals one role must give."
    },
    "GovernanceRoleName": {
      "type": "string",
      "description": "Name of an approval role: lowercase letters, digits and hyphens, starting with a letter. Windows device names are refused, since a role names a directory.",
      "pattern": "^(?!(?:con|prn|aux|nul|com[0-9]|lpt[0-9])$)[a-z][a-z0-9-]{0,31}$"
    },
    "RequiredEvalsPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "required-evals",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "environments": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ReleaseEnvironmentName"
          },
          "description": "Environments from `releases.environments` the policy applies to. Absent means all.",
          "minItems": 1
        }
      },
      "required": [
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Required-evals policy: matched prompts need passing evals before promotion."
    },
    "ContentRulesPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "content-rules",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "checks": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ContentRuleCheck"
          },
          "description": "Validation rules to run on the prompt text"
        },
        "deny": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Literal phrases the prompt text must not contain"
        },
        "allow": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Blocked-pattern sources exempt for matched prompts, as exact source text"
        }
      },
      "required": [
        "kind",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Content-rules policy: runs content checks and denied phrases on matched prompts."
    },
    "ContentRuleCheck": {
      "type": "string",
      "enum": [
        "blocked-patterns",
        "suspicious-urls",
        "authority-injection",
        "obfuscated-content",
        "unicode-security",
        "PS005",
        "PS010",
        "PS011",
        "PS012",
        "PS014"
      ],
      "description": "Content check of a content-rules policy, by rule name or id."
    },
    "MaxExperimentWeightPolicy": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique policy name within the config"
        },
        "description": {
          "type": "string",
          "description": "Human-readable description"
        },
        "kind": {
          "type": "string",
          "const": "max-experiment-weight",
          "description": "Policy kind discriminator"
        },
        "severity": {
          "$ref": "#/definitions/PolicySeverity",
          "description": "Violation severity"
        },
        "match": {
          "$ref": "#/definitions/PromptPolicyMatch",
          "description": "Prompts the policy applies to"
        },
        "environments": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ReleaseEnvironmentName"
          },
          "description": "Environments from `releases.environments` the policy applies to. Absent means all.",
          "minItems": 1
        },
        "maxWeight": {
          "type": "number",
          "description": "Percent of traffic an arm may get without extra approvals, at most two decimals",
          "minimum": 0,
          "maximum": 100
        },
        "approvals": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/RequiredApproval"
          },
          "description": "Approvals per role an arm above `maxWeight` needs. Absent means the cap is hard.",
          "minItems": 1
        },
        "ceiling": {
          "type": "number",
          "description": "Percent above which no arm passes, at most two decimals and at least `maxWeight`. Only with `approvals`.",
          "minimum": 0,
          "maximum": 100
        }
      },
      "required": [
        "kind",
        "maxWeight",
        "name",
        "severity"
      ],
      "additionalProperties": false,
      "description": "Max-experiment-weight policy: caps the traffic share of a non-control arm of a label experiment. Arms above `maxWeight` need `approvals`, and nothing passes above `ceiling`."
    }
  },
  "allOf": [
    {
      "anyOf": [
        {
          "required": [
            "targets"
          ],
          "properties": {
            "targets": {
              "minItems": 1
            }
          }
        },
        {
          "required": [
            "builds"
          ],
          "properties": {
            "builds": {
              "minProperties": 1,
              "additionalProperties": {
                "required": [
                  "targets"
                ],
                "properties": {
                  "targets": {
                    "minItems": 1
                  }
                }
              }
            }
          }
        }
      ]
    },
    {
      "properties": {
        "builds": {
          "additionalProperties": {
            "properties": {
              "targets": {
                "minItems": 1
              }
            }
          }
        }
      }
    }
  ]
}
