{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://pipelinexlab.com/contracts/component-rich-surface-resolution.v1.schema.json",
  "title": "PipelineXLab Component rich surface resolution wire v1",
  "description": "The closed `component-rich-surface-resolution@1` request and response. It is a third port beside the Component catalog read and the presentation resolution read, and it is separate for the same reason those two are separate from each other: the catalog answers what a Project may browse, presentation answers what the host renders for one exact reference, and this answers whether that same reference may open a rich surface now, which of its tabs there are, and from where each opens. One operation answering two of them would be one read that can disagree with itself.",
  "oneOf": [
    {
      "$ref": "#/$defs/request"
    },
    {
      "$ref": "#/$defs/response"
    }
  ],
  "$defs": {
    "projectScope": {
      "description": "The exact Project the question is asked in. Every read and every answer carries it, so one Project's answer can never be read as another's.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind",
        "workspaceKey",
        "projectKey"
      ],
      "properties": {
        "kind": {
          "const": "project"
        },
        "workspaceKey": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
        },
        "projectKey": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
        }
      }
    },
    "componentRef": {
      "description": "The exact seven-field Component identity, whole. There is no partial key to spell, so there is no partial key to resolve.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "publisher",
        "packageKey",
        "packageVersion",
        "packageDigest",
        "releaseByteDigest",
        "componentKey",
        "componentContractVersion"
      ],
      "properties": {
        "publisher": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
        },
        "packageKey": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
        },
        "packageVersion": {
          "type": "string"
        },
        "packageDigest": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$"
        },
        "releaseByteDigest": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$"
        },
        "componentKey": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
        },
        "componentContractVersion": {
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        }
      }
    },
    "request": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "componentRef",
        "surfaceRole"
      ],
      "properties": {
        "messageKind": {
          "const": "component-rich-surface-resolution-request@1"
        },
        "contractVersion": {
          "const": "component-rich-surface-resolution@1"
        },
        "projectScope": {
          "$ref": "#/$defs/projectScope"
        },
        "componentRef": {
          "$ref": "#/$defs/componentRef"
        },
        "surfaceRole": {
          "description": "Which tab of the node modal is being opened. `editor` is reserved -- the role every surface published before package tabs existed carries, filed under the host's own Edit tab. Any other value is a publisher key a package chose for a tab of its own. The five names the host draws itself are refused as malformed rather than answered `surface-not-declared`: the caller asked for something this contract cannot mean.",
          "type": "string",
          "maxLength": 64,
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
          "not": {
            "enum": [
              "settings",
              "inputs",
              "output",
              "code",
              "ai"
            ]
          }
        }
      }
    },
    "response": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "messageKind",
        "contractVersion",
        "projectScope",
        "componentRef",
        "outcome",
        "availableSurfaces"
      ],
      "properties": {
        "messageKind": {
          "const": "component-rich-surface-resolution-response@1"
        },
        "contractVersion": {
          "const": "component-rich-surface-resolution@1"
        },
        "projectScope": {
          "$ref": "#/$defs/projectScope"
        },
        "componentRef": {
          "$ref": "#/$defs/componentRef"
        },
        "outcome": {
          "enum": [
            "available",
            "unavailable"
          ]
        },
        "surface": {
          "$ref": "#/$defs/surface"
        },
        "reasonCode": {
          "$ref": "#/$defs/unavailableReason"
        },
        "availableSurfaces": {
          "description": "Every tab this reference offers right now, in declaration order -- the whole rail the node modal draws. It is always present and often empty: an empty rail is a fact, and a member that appeared only sometimes would be a member every reader has to guess about. It is empty for every refusal that hides existence -- a Component this Project never installed, one whose trust was revoked, one whose release was withdrawn -- so the rail is never a way to learn what a neighbouring Project has. One Component ships at most four.",
          "type": "array",
          "maxItems": 4,
          "uniqueItems": true,
          "items": {
            "$ref": "#/$defs/surfaceListing"
          }
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "outcome": {
                "const": "available"
              }
            },
            "required": [
              "outcome"
            ]
          },
          "then": {
            "required": [
              "surface"
            ],
            "properties": {
              "surface": {
                "$ref": "#/$defs/surface"
              },
              "reasonCode": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "outcome": {
                "const": "unavailable"
              }
            },
            "required": [
              "outcome"
            ]
          },
          "then": {
            "required": [
              "reasonCode"
            ],
            "properties": {
              "reasonCode": {
                "$ref": "#/$defs/unavailableReason"
              },
              "surface": false
            }
          }
        }
      ]
    },
    "surface": {
      "description": "What an available answer carries, and nothing else. There is no member a package-selected renderer, a host stylesheet, a cross-origin URL, a helper, a dependency or a capability grant could arrive in. `role` echoes the role the request named, so a rail holding several open surfaces can tell whose answer it is holding.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "role",
        "documentPath",
        "contentDigest",
        "bridgeContract"
      ],
      "properties": {
        "role": {
          "description": "Which tab of the node modal is being opened. `editor` is reserved -- the role every surface published before package tabs existed carries, filed under the host's own Edit tab. Any other value is a publisher key a package chose for a tab of its own. The five names the host draws itself are refused as malformed rather than answered `surface-not-declared`: the caller asked for something this contract cannot mean.",
          "type": "string",
          "maxLength": 64,
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
          "not": {
            "enum": [
              "settings",
              "inputs",
              "output",
              "code",
              "ai"
            ]
          }
        },
        "documentPath": {
          "description": "Root-relative, same-origin and content-addressed, carrying no credential, no query and no fragment. The address is the whole-asset closure, not the document alone: changing one asset byte changes that asset's digest, which changes the canonical closure record, which changes this URL -- so the bytes it serves are safe to cache immutably and no viewer can assemble one surface's document with another's assets.",
          "type": "string",
          "pattern": "^/component-surfaces/[0-9a-f]{64}/index\\.html$"
        },
        "contentDigest": {
          "description": "The same closure address the path carries, stated once so the frame and the host agree about which surface is open.",
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$"
        },
        "bridgeContract": {
          "description": "The version the surface's own declaration names, echoed rather than replaced by the host's preference: a document written against version 1 checks the contract version on its own bootstrap, so telling it version 2 would make it ignore its boot and time out.",
          "enum": [
            "component-rich-surface-bridge@1",
            "component-rich-surface-bridge@2"
          ]
        }
      }
    },
    "unavailableReason": {
      "description": "The whole closed vocabulary. `surface-not-declared` is the single answer to a Component nobody installed, a Component only a neighbour installed, a reference that drifted in any of its seven fields, and a Component that simply ships no surface -- four answers would be four things a caller could tell apart, and telling them apart is how one Project reads what another installed.",
      "enum": [
        "surface-not-declared",
        "surface-not-admitted",
        "surface-bytes-unavailable",
        "release-not-selected",
        "release-removed",
        "release-revoked",
        "trust-revoked"
      ]
    },
    "surfaceListing": {
      "description": "One tab this reference offers, as the rail needs to draw it. It is presentation and never bytes: a caller learns that a tab exists, what to call it and what glyph to draw, and opens it by resolving that role. Nothing here loads a document, so listing the rail is not a way to reach a closure.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "role"
      ],
      "properties": {
        "role": {
          "description": "Which tab of the node modal is being opened. `editor` is reserved -- the role every surface published before package tabs existed carries, filed under the host's own Edit tab. Any other value is a publisher key a package chose for a tab of its own. The five names the host draws itself are refused as malformed rather than answered `surface-not-declared`: the caller asked for something this contract cannot mean.",
          "type": "string",
          "maxLength": 64,
          "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
          "not": {
            "enum": [
              "settings",
              "inputs",
              "output",
              "code",
              "ai"
            ]
          }
        },
        "label": {
          "description": "The tab name the rail draws. Present for a package role and absent for `editor`, whose tab the host names itself.",
          "type": "string",
          "minLength": 1,
          "maxLength": 40
        },
        "icon": {
          "description": "The rail glyph, from the host's governed publisher icon inventory.",
          "type": "string",
          "enum": [
            "bar-chart-3",
            "boxes",
            "brackets",
            "brain",
            "code-2",
            "database",
            "eye",
            "file-box",
            "file-input",
            "file-output",
            "file-spreadsheet",
            "file-text",
            "flask-conical",
            "gauge",
            "globe",
            "image",
            "list",
            "package",
            "route",
            "search",
            "settings",
            "sigma",
            "square-function",
            "table-2",
            "upload",
            "workflow",
            "file-pdf"
          ]
        },
        "receives": {
          "description": "What this surface is handed at boot. An omitted or empty list means `node-context` alone, which is what every surface has always received.",
          "type": "array",
          "maxItems": 2,
          "uniqueItems": true,
          "items": {
            "enum": [
              "node-context",
              "run-result"
            ]
          }
        }
      }
    }
  }
}
