{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://skillcatalog.dev/schemas/catalog.schema.json",
  "title": "SkillCatalog Catalog Metadata",
  "description": "The catalog.yaml file at the root of a catalog repository. Defines the catalog id and name, its taxonomy (categories, owners, tags), the content checks it turns on or off, and an optional metrics address. The runtime ignores unknown keys in this file, so this schema allows them; a misspelled optional field is ignored rather than flagged.",
  "type": "object",
  "required": [
    "id",
    "name"
  ],
  "additionalProperties": true,
  "properties": {
    "id": {
      "type": "string",
      "minLength": 2,
      "maxLength": 64,
      "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
      "description": "The catalog id. Profiles, team manifests, and local manifests name the catalog by it, so it must be the same on every machine."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500,
      "description": "Human-readable catalog name."
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 500,
      "description": "One-sentence summary of the catalog."
    },
    "taxonomy": {
      "type": "object",
      "additionalProperties": true,
      "default": {},
      "description": "Classification system for skills in this catalog. The runtime ignores keys other than categories, owners, and tags, so this schema allows them.",
      "properties": {
        "categories": {
          "type": "array",
          "default": [],
          "description": "Hierarchical categories. Maximum nesting depth is 2 (root + one child level). Validation also rejects duplicate category slugs anywhere in the tree.",
          "items": {
            "$ref": "#/$defs/categoryNode"
          }
        },
        "owners": {
          "type": "array",
          "default": [],
          "description": "Hierarchical owners. Maximum nesting depth is 2 (root + one child level). Validation also rejects duplicate owner slugs anywhere in the tree.",
          "items": {
            "$ref": "#/$defs/ownerNode"
          }
        },
        "tags": {
          "type": "array",
          "default": [],
          "description": "Flat list of tag display names. Slugs are derived automatically, so a name must contain at least one ASCII letter or digit or slug derivation fails. Uniqueness is enforced on the DERIVED slug, not the display name: 'Rust' and 'rust' both derive 'rust' and are rejected as duplicates even though uniqueItems passes.",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "pattern": ".*[A-Za-z0-9].*"
          }
        }
      }
    },
    "validation": {
      "type": "object",
      "default": {},
      "description": "Turns advisory content checks on or off for every skill in the catalog. Keys are check ids and values are true or false. Only the four advisory checks can be set; any other id, or a value that is not true or false, makes catalog.yaml invalid. A skill overrides these values with metadata.skillcatalog/validation in its SKILL.md.",
      "propertyNames": {
        "enum": [
          "validation.body.too-long",
          "validation.reference.depth-too-deep",
          "validation.reference.missing-toc",
          "validation.time-sensitive.outside-old-patterns"
        ]
      },
      "additionalProperties": {
        "type": "boolean"
      }
    },
    "metrics_url": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2048,
          "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://[^\\s/?#@]+(?:/[^?#]*)?/v1/metrics$"
        },
        {
          "type": "string",
          "pattern": "^\\s*$"
        },
        {
          "type": "null"
        }
      ],
      "examples": [
        "http://metrics.acme.internal:9090/api/v1/otlp/v1/metrics"
      ],
      "description": "The metrics address that receives anonymous usage counts from everyone who has the catalog, while their usage metrics are on. An http:// or https:// address that ends in /v1/metrics, with no user name, password, query, or fragment, at most 2,048 bytes. A blank value, or no value, means no address. The runtime does not reject other values: it ignores them, sends no counts for the catalog, and `skc validate --path` warns. This schema flags them so an editor shows the mistake."
    }
  },
  "$defs": {
    "categoryNode": {
      "type": "object",
      "required": [
        "slug",
        "name"
      ],
      "additionalProperties": true,
      "properties": {
        "slug": {
          "type": "string",
          "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
          "description": "Unique category slug."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "description": "Human-readable category name."
        },
        "children": {
          "type": "array",
          "default": [],
          "description": "Child categories (max one level of nesting).",
          "items": {
            "$ref": "#/$defs/categoryLeafNode"
          }
        }
      }
    },
    "categoryLeafNode": {
      "type": "object",
      "required": [
        "slug",
        "name"
      ],
      "additionalProperties": true,
      "properties": {
        "slug": {
          "type": "string",
          "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
          "description": "Unique category slug."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "description": "Human-readable category name."
        },
        "children": {
          "type": "array",
          "default": [],
          "description": "Leaf categories cannot define grandchildren.",
          "maxItems": 0
        }
      }
    },
    "ownerNode": {
      "type": "object",
      "required": [
        "slug",
        "name"
      ],
      "additionalProperties": true,
      "properties": {
        "slug": {
          "type": "string",
          "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
          "description": "Unique owner slug."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "description": "Human-readable owner name."
        },
        "children": {
          "type": "array",
          "default": [],
          "description": "Child owners (max one level of nesting).",
          "items": {
            "$ref": "#/$defs/ownerLeafNode"
          }
        }
      }
    },
    "ownerLeafNode": {
      "type": "object",
      "required": [
        "slug",
        "name"
      ],
      "additionalProperties": true,
      "properties": {
        "slug": {
          "type": "string",
          "pattern": "^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,127}$",
          "description": "Unique owner slug."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500,
          "description": "Human-readable owner name."
        },
        "children": {
          "type": "array",
          "default": [],
          "description": "Leaf owners cannot define grandchildren.",
          "maxItems": 0
        }
      }
    }
  }
}
