{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://unpkg.com/@7n/rules/schemas/plugin-manifest.json",
  "title": "n-rules plugin manifest (slots, plugin API v2)",
  "description": "Форма блоку package.json#n-rules для плагіна на universal typed slot bus (spec 2026-07-27-universal-plugin-slots-lang-php-extraction, Фаза 1). НЕ зареєстрована у v8r-catalog.json з fileMatch на package.json: package.json уже валідується як ціле проти vendor/package.json (permissive, additionalProperties не заборонені), і другий catalog-запис із тим самим fileMatch перебив би цю загальну валідацію замість доповнення. Ця схема — для точкової валідації самого блоку n-rules (тести, майбутній dedicated CLI check), референс через $ref/definitions. Валідує лише СТРУКТУРУ: filesystem-інваріанти (symlink escape, реальне існування resource, containment у packageRoot) статичною JSON Schema непередавані — їх перевіряє resolveSlotGraph (plugin-slots.mjs) під час discovery.",
  "type": "object",
  "additionalProperties": false,
  "required": ["requiresPluginApi"],
  "properties": {
    "requiresPluginApi": {
      "type": "integer",
      "minimum": 1,
      "description": "Версія контракту plugin-api, яку декларує плагін (поточна core-вимога — PLUGIN_API_VERSION у plugin-api.mjs, зараз 2). Плагін з іншим значенням (або без цього поля) не входить у slot graph — жодна first-party поверхня (rules/taze/coverage/doc-files/skills) його не бачить."
    },
    "capabilities": {
      "type": "array",
      "description": "Capabilities, які плагін вмикає для гейта requires.capability у concern.json ТА для requires.capabilities у slot contributions.",
      "items": { "type": "string", "minLength": 1 }
    },
    "slots": {
      "type": "object",
      "additionalProperties": false,
      "description": "Universal slot bus: що плагін постачає (provides) і що споживає (consumes).",
      "properties": {
        "provides": {
          "type": "array",
          "description": "Contributions цього плагіна — immutable дані/посилання на package-relative resource.",
          "items": { "$ref": "#/definitions/contribution" }
        },
        "consumes": {
          "type": "array",
          "description": "Consumers цього плагіна — які версії якого слоту він вміє матеріалізувати і через який handler.",
          "items": { "$ref": "#/definitions/consumer" }
        }
      }
    }
  },
  "definitions": {
    "contribution": {
      "type": "object",
      "additionalProperties": false,
      "required": ["slot", "version", "id"],
      "description": "Один запис slots.provides. Рівно одне з resource/value обов'язкове (oneOf нижче); priority/before/after не є валідними полями (жодного прихованого ordering у v1, spec рішення З) — additionalProperties:false вже їх забороняє.",
      "properties": {
        "slot": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z][a-z0-9]*)+$",
          "description": "Lowercase dot/dash-separated slot id, мінімум два сегменти (напр. rules.directory, ci.artifact)."
        },
        "version": {
          "type": "integer",
          "minimum": 1,
          "description": "Версія слоту — positive integer, незалежний від package SemVer і PLUGIN_API_VERSION."
        },
        "id": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9-]*$",
          "description": "Стабільний plugin-local identifier contribution-у. Унікальний у межах (plugin, slot, version) — дублікат є hard error."
        },
        "resource": {
          "type": "string",
          "pattern": "^\\./",
          "description": "Package-relative шлях до ресурсу (файл або каталог), обов'язково від package root. Абсолютні шляхи та \"..\"-сегменти заборонені; symlink escape за межі пакета блокується під час discovery."
        },
        "value": {
          "description": "Inline JSON-compatible payload contribution-у (альтернатива resource)."
        },
        "requires": {
          "type": "object",
          "additionalProperties": false,
          "description": "Опційні активаційні умови contribution-у, перевіряються ДО читання resource.",
          "properties": {
            "capabilities": {
              "type": "array",
              "description": "Усі перелічені capabilities мають бути активні (сукупність з capabilities усіх наявних плагінів, resolvePlugins), інакше contribution ігнорується без завантаження resource.",
              "items": { "type": "string", "minLength": 1 }
            }
          }
        }
      },
      "oneOf": [
        { "required": ["resource"], "not": { "required": ["value"] } },
        { "required": ["value"], "not": { "required": ["resource"] } }
      ]
    },
    "consumer": {
      "type": "object",
      "additionalProperties": false,
      "required": ["slot", "versions", "handler"],
      "description": "Один запис slots.consumes.",
      "properties": {
        "slot": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z][a-z0-9]*)+$",
          "description": "Slot id цього консюмера."
        },
        "versions": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "description": "Підтримувані версії слоту — непорожній масив унікальних positive integers.",
          "items": { "type": "integer", "minimum": 1 }
        },
        "handler": {
          "type": "string",
          "pattern": "^\\./",
          "description": "Package-relative шлях до handler-модуля (default export: { id, validate(contribution), ...surface-specific methods }). Той самий набір path-обмежень, що й у resource."
        }
      }
    }
  }
}
