{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://pipelinexlab.com/contracts/component-catalog-wire.v1.schema.json",
  "title": "PipelineXLab Component catalog wire v1",
  "$comment": "RFC-014. The actual bytes the frontend reads, not a metadata profile: every payload below is a closed message this schema validates. A row is identity plus host-owned declarative presentation only -- no renderer, no HTML, no CSS, no React, no iframe, no URL asset, no runtime ABI, no helper, no dependency and no capability grant may appear on it. Every response states the exact projectScope it was resolved under. The cursor is opaque and its binding to scope, query and snapshot revision is server-owned: a request never carries the binding, this closed schema refuses one that tries, and replay across any of the three fails closed. This contract spells the stale-cursor outcome exactly one way: stale-cursor.",
  "type": "object",
  "required": ["messageKind"],
  "oneOf": [
    { "$ref": "#/$defs/queryRequest" },
    { "$ref": "#/$defs/pageResponse" },
    { "$ref": "#/$defs/stale-cursor-outcome" },
    { "$ref": "#/$defs/batchLookupRequest" },
    { "$ref": "#/$defs/batchLookupResponse" }
  ],
  "$defs": {
    "semanticKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
      "$comment": "The canonical RFC-003 semantic key. Lowercase, digits and single underscores that separate non-empty segments: a doubled underscore, a trailing underscore and a leading underscore are all refused, so one key has exactly one spelling."
    },
    "semanticVersion": {
      "type": "string",
      "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
    },
    "contentDigest": {
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$"
    },
    "displayText": {
      "type": "string",
      "minLength": 1,
      "maxLength": 512
    },
    "iconKey": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "pattern": "^[a-z][a-z0-9-]*$",
      "$comment": "A key into the host icon registry. Never a URL, never bytes, never an asset reference."
    },
    "opaqueCursor": {
      "type": "string",
      "minLength": 16,
      "maxLength": 512,
      "pattern": "^[A-Za-z0-9_-]+$",
      "$comment": "Opaque to every reader. The reader never parses it and never constructs one."
    },
    "projectScope": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "workspaceKey", "projectKey"],
      "properties": {
        "kind": { "const": "project" },
        "workspaceKey": { "$ref": "#/$defs/semanticKey" },
        "projectKey": { "$ref": "#/$defs/semanticKey" }
      }
    },
    "componentRef": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "publisher",
        "packageKey",
        "packageVersion",
        "packageDigest",
        "releaseByteDigest",
        "componentKey",
        "componentContractVersion"
      ],
      "properties": {
        "publisher": { "$ref": "#/$defs/semanticKey" },
        "packageKey": { "$ref": "#/$defs/semanticKey" },
        "packageVersion": { "$ref": "#/$defs/semanticVersion" },
        "packageDigest": { "$ref": "#/$defs/contentDigest" },
        "releaseByteDigest": { "$ref": "#/$defs/contentDigest" },
        "componentKey": { "$ref": "#/$defs/semanticKey" },
        "componentContractVersion": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        }
      }
    },
    "filter": {
      "type": "object",
      "additionalProperties": false,
      "required": ["composition"],
      "properties": {
        "composition": { "const": "logical-and" },
        "category": { "$ref": "#/$defs/componentCategory" },
        "subcategory": { "$ref": "#/$defs/semanticKey" },
        "packageFacet": { "$ref": "#/$defs/packageFacet" },
        "trustStatus": { "$ref": "#/$defs/trustStatus" }
      }
    },
    "order": {
      "type": "object",
      "additionalProperties": false,
      "required": ["field", "direction"],
      "properties": {
        "field": { "enum": ["label", "category", "subcategory", "trustStatus"] },
        "direction": { "enum": ["ascending", "descending"] }
      }
    },
    "page": {
      "type": "object",
      "additionalProperties": false,
      "required": ["cursor"],
      "properties": {
        "limit": {
          "type": "integer",
          "minimum": 1,
          "maximum": 80,
          "default": 40,
          "$comment": "Omitted means the contract default 40. 80 is the hard ceiling; 81 is refused."
        },
        "cursor": {
          "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/opaqueCursor" }]
        }
      }
    },
    "componentCategory": {
      "enum": ["input", "transform", "calculate", "review", "deliver", "control"],
      "$comment": "The closed primary task registry the node-presentation profile carries as classification.primaryTaskKeys. Exactly one per row and one per filter; a publisher never invents a category key."
    },
    "packageFacet": {
      "enum": ["built-in", "installed", "available"],
      "$comment": "Host-owned, exactly one per row, derived from the request Project effective closure. A publisher never writes this and no other key is admitted."
    },
    "placementReasonCode": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64,
      "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
      "$comment": "A stable host diagnostic code. Never a sentence, never a URL, never publisher text."
    },
    "placementCapability": {
      "type": "object",
      "additionalProperties": false,
      "required": ["allowed"],
      "properties": {
        "allowed": { "type": "boolean" },
        "reasonCode": { "$ref": "#/$defs/placementReasonCode" }
      },
      "allOf": [
        {
          "if": {
            "properties": { "allowed": { "const": true } },
            "required": ["allowed"]
          },
          "then": { "properties": { "reasonCode": false } },
          "else": {
            "properties": { "reasonCode": { "$ref": "#/$defs/placementReasonCode" } },
            "required": ["reasonCode"]
          }
        }
      ],
      "$comment": "Host-owned. A publisher never writes this. allowed true carries no reason; allowed false carries one stable kebab-case reasonCode. Trust status stays a separate member and is never folded in here."
    },
    "trustStatus": {
      "enum": ["trusted", "untrusted", "revoked", "unknown"],
      "$comment": "Host-owned. A publisher never writes this."
    },
    "row": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "componentRef",
        "label",
        "description",
        "category",
        "icon",
        "packageFacet",
        "placementCapability",
        "trustStatus"
      ],
      "properties": {
        "componentRef": { "$ref": "#/$defs/componentRef" },
        "label": { "$ref": "#/$defs/displayText" },
        "description": { "type": "string", "maxLength": 2048 },
        "category": { "$ref": "#/$defs/componentCategory" },
        "subcategory": { "$ref": "#/$defs/semanticKey" },
        "icon": { "$ref": "#/$defs/iconKey" },
        "packageFacet": { "$ref": "#/$defs/packageFacet" },
        "placementCapability": { "$ref": "#/$defs/placementCapability" },
        "trustStatus": { "$ref": "#/$defs/trustStatus" }
      },
      "allOf": [
        {
          "if": {
            "properties": { "packageFacet": { "const": "available" } },
            "required": ["packageFacet"]
          },
          "then": {
            "properties": {
              "placementCapability": {
                "type": "object",
                "required": ["allowed", "reasonCode"],
                "properties": {
                  "allowed": { "const": false },
                  "reasonCode": { "const": "install-required" }
                }
              }
            }
          }
        },
        {
          "if": {
            "properties": { "trustStatus": { "const": "revoked" } },
            "required": ["trustStatus"]
          },
          "then": {
            "properties": {
              "packageFacet": { "enum": ["built-in", "installed"] },
              "placementCapability": {
                "type": "object",
                "required": ["allowed", "reasonCode"],
                "properties": {
                  "allowed": { "const": false },
                  "reasonCode": { "$ref": "#/$defs/placementReasonCode" }
                }
              }
            }
          }
        }
      ],
      "$comment": "subcategory is optional: it is the publisher's own key under the closed category, and a Component that has none carries none rather than an empty string. An `available` row is never placeable -- it is denied with the single host reason install-required. A `revoked` row is never placeable either, and it is never the `available` facet: `available` means a verified compatible release the Project could install, which a revoked release is not. The two branches below are what refuse a row that says otherwise."
    },
    "queryRequest": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "query",
        "filter",
        "order",
        "page"
      ],
      "properties": {
        "messageKind": { "const": "component-catalog-query-request@1" },
        "contractVersion": { "const": "component-catalog-wire@1" },
        "projectScope": { "$ref": "#/$defs/projectScope" },
        "query": { "type": "string", "maxLength": 256 },
        "filter": { "$ref": "#/$defs/filter" },
        "order": { "$ref": "#/$defs/order" },
        "page": { "$ref": "#/$defs/page" }
      }
    },
    "pageResponse": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "items",
        "snapshotRevision",
        "totalCount",
        "nextCursor"
      ],
      "properties": {
        "messageKind": { "const": "component-catalog-page-response@1" },
        "contractVersion": { "const": "component-catalog-wire@1" },
        "projectScope": { "$ref": "#/$defs/projectScope" },
        "items": {
          "type": "array",
          "maxItems": 80,
          "items": { "$ref": "#/$defs/row" }
        },
        "snapshotRevision": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "totalCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "nextCursor": {
          "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/opaqueCursor" }]
        }
      }
    },
    "stale-cursor-outcome": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "outcome",
        "refusedBinding",
        "failsClosed",
        "nextAction"
      ],
      "properties": {
        "messageKind": { "const": "component-catalog-stale-cursor@1" },
        "contractVersion": { "const": "component-catalog-wire@1" },
        "outcome": { "const": "component-catalog-cursor-snapshot-stale" },
        "refusedBinding": { "enum": ["projectScope", "query", "snapshotRevision"] },
        "failsClosed": { "const": true },
        "nextAction": { "const": "restart-query-without-cursor" }
      }
    },
    "batchLookupRequest": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "targets",
        "currentPageIndependent"
      ],
      "properties": {
        "messageKind": { "const": "component-catalog-batch-lookup-request@1" },
        "contractVersion": { "const": "component-catalog-batch-lookup@1" },
        "projectScope": { "$ref": "#/$defs/projectScope" },
        "targets": {
          "type": "array",
          "minItems": 1,
          "maxItems": 80,
          "uniqueItems": true,
          "items": { "$ref": "#/$defs/componentRef" },
          "$comment": "Exact favorite targets in caller order. Resolution never depends on the current page."
        },
        "currentPageIndependent": { "const": true }
      }
    },
    "batchLookupResult": {
      "type": "object",
      "additionalProperties": false,
      "required": ["componentRef", "status", "row"],
      "properties": {
        "componentRef": { "$ref": "#/$defs/componentRef" },
        "status": {
          "enum": ["available", "missing", "removed", "revoked"],
          "$comment": "missing, removed and revoked are preserved as results. A target is never dropped."
        },
        "row": { "type": ["object", "null"] }
      },
      "allOf": [
        {
          "if": {
            "properties": { "status": { "const": "available" } },
            "required": ["status"]
          },
          "then": { "properties": { "row": { "$ref": "#/$defs/row" } } },
          "else": { "properties": { "row": { "type": "null" } } }
        }
      ]
    },
    "batchLookupResponse": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "results",
        "inputOrderPreserved"
      ],
      "properties": {
        "messageKind": { "const": "component-catalog-batch-lookup-response@1" },
        "contractVersion": { "const": "component-catalog-batch-lookup@1" },
        "projectScope": { "$ref": "#/$defs/projectScope" },
        "results": {
          "type": "array",
          "minItems": 1,
          "maxItems": 80,
          "items": { "$ref": "#/$defs/batchLookupResult" }
        },
        "inputOrderPreserved": { "const": true }
      }
    }
  }
}
