openapi: 3.0.3
info:
  title: Platform Registry API
  version: 0.1.0
  description: |
    MSS Platform Registry — structural metadata, launch targets, descriptors.

    **Auth:** `Authorization: Bearer <JWT>`. Required scopes: `registry:read` (read routes), `registry:admin` (admin routes).
    JWT claims: validate `iss`, `aud`, `exp`; scopes from `scope` (space-separated), `scp` array, or `permissions` array.

    **Transitional (non-production only):** when `REGISTRY_ALLOW_LEGACY_BEARER=1`, static
    `PLATFORM_REGISTRY_ADMIN_TOKEN` may be accepted for **admin** routes after JWT verification fails. Read routes require JWT.

    **Probes (`@mss/health` ^0.1.2):** **`GET /health`** / **`GET /ready`** at the service root (package default). Prefer **`GET /api/v1/platform-registry/health`** (liveness, no Mongo) and **`GET /api/v1/platform-registry/ready`** (readiness, Mongo `ping`) for callers that use the API base path. Internal (no auth): **`GET /internal/v1/platform-registry/{health,ready}`** — same JSON contract as public probes.

    OpenAPI served at `GET /api/v1/platform-registry/openapi.yaml`.
    No in-process Swagger UI (`/apidocs`); import this file into your OpenAPI client or IDE.
servers:
  - url: /
    description: Absolute paths from service root (port 4020 in integrated dev)
components:
  securitySchemes:
    registryBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Service JWT with `registry:read` and/or `registry:admin` in scope claims.
  schemas:
    RegistryErrorEnvelope:
      type: object
      required: [error, requestId]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: Machine-readable code (see docs/ERROR-TAXONOMY.md)
            message:
              type: string
        requestId:
          type: string
    MssHealthLive:
      type: object
      description: Standard `@mss/health` liveness body
      required: [message, data]
      properties:
        message: { type: string }
        data:
          type: object
          required: [service, status]
          properties:
            service: { type: string }
            status: { type: string, enum: [ok, unhealthy] }
    MssHealthReadyOk:
      type: object
      description: Standard `@mss/health` readiness body when dependencies pass
      required: [message, data]
      properties:
        message: { type: string }
        data:
          type: object
          required: [service, status]
          properties:
            service: { type: string }
            status: { type: string, enum: [ok, degraded, unhealthy] }
            checks:
              type: object
              additionalProperties: { type: boolean }
    DescriptorPutRequest:
      type: object
      required: [schemaVersion, body]
      additionalProperties: false
      properties:
        schemaVersion:
          type: integer
          minimum: 0
        body:
          type: object
          additionalProperties: true
    LaunchTarget:
      type: object
      description: Stored launch target document
      additionalProperties: true
    LaunchTargetCreate:
      type: object
      required: [key, name, status, appId, baseUrl, handoffMode, priority]
      additionalProperties: false
      properties:
        key: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, enum: [active, inactive] }
        appId: { type: string, minLength: 1 }
        orgId: { type: string, nullable: true }
        workspaceId: { type: string, nullable: true }
        portalId: { type: string, nullable: true }
        environmentId: { type: string, nullable: true }
        domainId: { type: string, nullable: true }
        serviceId: { type: string, nullable: true }
        baseUrl: { type: string, format: uri }
        routeBase: { type: string }
        handoffMode: { type: string, enum: [redirect_only, exchange_then_redirect] }
        priority: { type: number }
        metadata:
          type: object
          additionalProperties: true
    DomainHostCreate:
      type: object
      required: [host, orgId]
      additionalProperties: false
      properties:
        host: { type: string, minLength: 1 }
        orgId: { type: string, minLength: 1 }
        workspaceId: { type: string }
        environmentId: { type: string }
        metadata:
          type: object
          additionalProperties: true
    OrgContextCreate:
      type: object
      required: [orgId, workspaceIds]
      additionalProperties: false
      properties:
        orgId: { type: string, minLength: 1 }
        workspaceIds:
          type: array
          items: { type: string }
        defaultEnvironmentId: { type: string }
        metadata:
          type: object
          additionalProperties: true
    WorkspaceCreate:
      type: object
      required: [workspaceId, orgId, name, status]
      additionalProperties: false
      properties:
        workspaceId: { type: string, minLength: 1 }
        orgId: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, minLength: 1 }
        metadata:
          type: object
          additionalProperties: true
    AppCreate:
      type: object
      required: [appId, name, status]
      additionalProperties: false
      properties:
        appId: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, minLength: 1 }
        defaultLaunchTargetId: { type: string }
        metadata:
          type: object
          additionalProperties: true
    PortalCreate:
      type: object
      required: [portalId, name, status]
      additionalProperties: false
      properties:
        portalId: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, minLength: 1 }
        metadata:
          type: object
          additionalProperties: true

    PlatformService:
      type: object
      required: [serviceKey, displayName, status, plane, tags, updatedAt]
      properties:
        serviceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        description: { type: string }
        status: { type: string, enum: [active, inactive, archived] }
        plane: { type: string, enum: [platform, business, internal, client, ops] }
        ownerTeam: { type: string }
        runtimeComponentKey:
          type: string
          description: Reference to the runtime contract component key. Runtime contracts remain authoritative for orchestration facts.
        healthEndpoint:
          type: string
          description: Catalog/display hint copied from runtime contracts when available.
        publicBasePath:
          type: string
          description: Catalog/display hint copied from runtime contracts when available.
        internalBasePath:
          type: string
          description: Catalog/display hint copied from runtime contracts when available.
        tags:
          type: array
          items: { type: string }
        metadata:
          type: object
          additionalProperties: true
        updatedAt: { type: string, format: date-time }

    PlatformServiceInput:
      type: object
      required: [serviceKey, displayName, status, plane]
      properties:
        serviceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        description: { type: string }
        status: { type: string, enum: [active, inactive, archived] }
        plane: { type: string, enum: [platform, business, internal, client, ops] }
        ownerTeam: { type: string }
        runtimeComponentKey: { type: string }
        healthEndpoint: { type: string }
        publicBasePath: { type: string }
        internalBasePath: { type: string }
        tags:
          type: array
          items: { type: string }
          default: []
        metadata:
          type: object
          additionalProperties: true

    ProductSurface:
      type: object
      required: [surfaceKey, displayName, status, serviceKeys, appIds, portalIds, requiredCapabilities, requiredSubscriptions, updatedAt]
      properties:
        surfaceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        status: { type: string, enum: [active, inactive, archived] }
        serviceKeys:
          type: array
          items: { type: string }
        appIds:
          type: array
          items: { type: string }
        portalIds:
          type: array
          items: { type: string }
        defaultRoute: { type: string }
        requiredCapabilities:
          type: array
          items: { type: string }
        requiredSubscriptions:
          type: array
          items: { type: string }
        metadata:
          type: object
          additionalProperties: true
        updatedAt: { type: string, format: date-time }

    ProductSurfaceInput:
      type: object
      required: [surfaceKey, displayName, status]
      properties:
        surfaceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        status: { type: string, enum: [active, inactive, archived] }
        serviceKeys:
          type: array
          items: { type: string }
          default: []
        appIds:
          type: array
          items: { type: string }
          default: []
        portalIds:
          type: array
          items: { type: string }
          default: []
        defaultRoute: { type: string }
        requiredCapabilities:
          type: array
          items: { type: string }
          default: []
        requiredSubscriptions:
          type: array
          items: { type: string }
          default: []
        metadata:
          type: object
          additionalProperties: true

    ServiceContract:
      type: object
      required: [contractKey, serviceKey, contractType, status, updatedAt]
      properties:
        contractKey: { type: string, minLength: 1 }
        serviceKey: { type: string, minLength: 1 }
        contractType: { type: string, enum: [openapi, asyncapi, event, runtime, descriptor] }
        version: { type: string }
        status: { type: string, enum: [active, deprecated, archived] }
        descriptorRef:
          type: string
          description: Descriptor ref when the contract is stored or represented through Registry descriptors.
        openapiRef:
          type: string
          description: OpenAPI artifact reference when applicable.
        sourceUrl:
          type: string
          description: External or repository URL for the contract artifact when applicable.
        metadata:
          type: object
          additionalProperties: true
        updatedAt: { type: string, format: date-time }

    ServiceContractInput:
      type: object
      required: [contractKey, serviceKey, contractType, status]
      properties:
        contractKey: { type: string, minLength: 1 }
        serviceKey: { type: string, minLength: 1 }
        contractType: { type: string, enum: [openapi, asyncapi, event, runtime, descriptor] }
        version: { type: string }
        status: { type: string, enum: [active, deprecated, archived] }
        descriptorRef: { type: string }
        openapiRef: { type: string }
        sourceUrl: { type: string }
        metadata:
          type: object
          additionalProperties: true

    PortalModule:
      type: object
      required: [moduleKey, serviceKey, displayName, route, status, requiredCapabilities, requiredSubscriptions, updatedAt]
      properties:
        moduleKey: { type: string, minLength: 1 }
        serviceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        route: { type: string, minLength: 1 }
        status: { type: string, enum: [active, inactive, archived] }
        requiredCapabilities:
          type: array
          items: { type: string, minLength: 1 }
        requiredSubscriptions:
          type: array
          items: { type: string, minLength: 1 }
        metadata:
          type: object
          additionalProperties: true
        updatedAt:
          type: string
          format: date-time
    PortalModuleCreate:
      type: object
      required: [moduleKey, serviceKey, displayName, route, status]
      additionalProperties: false
      properties:
        moduleKey: { type: string, minLength: 1 }
        serviceKey: { type: string, minLength: 1 }
        displayName: { type: string, minLength: 1 }
        route: { type: string, minLength: 1 }
        status: { type: string, enum: [active, inactive, archived] }
        requiredCapabilities:
          type: array
          items: { type: string, minLength: 1 }
          default: []
        requiredSubscriptions:
          type: array
          items: { type: string, minLength: 1 }
          default: []
        metadata:
          type: object
          additionalProperties: true
    PortalModulesList:
      type: object
      required: [portalModules]
      properties:
        portalModules:
          type: array
          items:
            $ref: '#/components/schemas/PortalModule'
    EnvironmentCreate:
      type: object
      required: [environmentId, name, status]
      additionalProperties: false
      properties:
        environmentId: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, minLength: 1 }
        metadata:
          type: object
          additionalProperties: true
    PersonaCreate:
      type: object
      required: [personaId, name, status]
      additionalProperties: false
      properties:
        personaId: { type: string, minLength: 1 }
        name: { type: string, minLength: 1 }
        status: { type: string, minLength: 1 }
        metadata:
          type: object
          additionalProperties: true
  responses:
    Unauthorized:
      description: Missing or invalid bearer
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
          examples:
            default:
              value:
                error: { code: REGISTRY_UNAUTHORIZED, message: Invalid or expired token }
                requestId: 00000000-0000-4000-8000-000000000001
    Forbidden:
      description: Insufficient scope
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
    Conflict:
      description: Duplicate or conflict
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
paths:
  /api/v1/platform-registry/openapi.yaml:
    get:
      summary: OpenAPI document (this spec)
      responses:
        '200':
          description: YAML document
          content:
            text/yaml:
              schema: { type: string }
  /api/v1/platform-registry/health:
    get:
      summary: Liveness (no auth, no Mongo check)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthLive' }
  /api/v1/platform-registry/ready:
    get:
      summary: Readiness — MongoDB ping
      responses:
        '200':
          description: Mongo reachable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthReadyOk' }
        '503':
          description: Mongo unavailable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthReadyOk' }
  /internal/v1/platform-registry/health:
    get:
      summary: Liveness (same contract as public health; no auth)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthLive' }
  /internal/v1/platform-registry/ready:
    get:
      summary: Readiness — MongoDB ping (no auth)
      responses:
        '200':
          description: Mongo reachable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthReadyOk' }
        '503':
          description: Mongo unavailable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MssHealthReadyOk' }
  /api/v1/platform-registry/orgs/{orgId}/context:
    get:
      summary: Org structural context
      security: [{ registryBearer: [] }]
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
            maxLength: 128
            pattern: '^[a-zA-Z0-9._-]+$'
      responses:
        '200':
          description: Org context document (without Mongo _id)
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/resolve/host/{host}:
    get:
      summary: Resolve host to org/workspace/environment mapping
      security: [{ registryBearer: [] }]
      parameters:
        - name: host
          in: path
          required: true
          schema:
            type: string
            maxLength: 253
            pattern: '^[a-zA-Z0-9.-]+$'
      responses:
        '200':
          description: Host mapping
          content:
            application/json:
              schema:
                type: object
                properties:
                  host: { type: string }
                  orgId: { type: string }
                  workspaceId: { type: string }
                  environmentId: { type: string }
                  metadata: { type: object }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/workspaces/{workspaceId}:
    get:
      summary: Get workspace by id
      security: [{ registryBearer: [] }]
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema:
            type: string
            maxLength: 128
            pattern: '^[a-zA-Z0-9._-]+$'
      responses:
        '200':
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/apps/{appId}:
    get:
      summary: Get app by id
      security: [{ registryBearer: [] }]
      parameters:
        - name: appId
          in: path
          required: true
          schema:
            type: string
            maxLength: 128
            pattern: '^[a-zA-Z0-9._-]+$'
      responses:
        '200':
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/launch-targets/resolve:
    get:
      summary: Resolve launch target for org/app/dimensions
      security: [{ registryBearer: [] }]
      parameters:
        - name: orgId
          in: query
          required: true
          schema: { type: string }
        - name: appId
          in: query
          required: true
          schema: { type: string }
        - name: workspaceId
          in: query
          schema: { type: string }
        - name: portalId
          in: query
          schema: { type: string }
        - name: environmentId
          in: query
          schema: { type: string }
        - name: host
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Single resolved launch target
          content:
            application/json:
              schema:
                type: object
                required: [launchTarget]
                properties:
                  launchTarget: { $ref: '#/components/schemas/LaunchTarget' }
        '400':
          description: INVALID_LAUNCH_TARGET_QUERY or validation
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404':
          description: LAUNCH_TARGET_NOT_FOUND
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
        '409':
          description: AMBIGUOUS_LAUNCH_TARGET
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
        '410':
          description: LAUNCH_TARGET_INACTIVE
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
        '503':
          description: REGISTRY_DEPENDENCY_UNAVAILABLE
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RegistryErrorEnvelope' }
  /api/v1/platform-registry/portal-modules:
    get:
      tags: [Portal Modules]
      summary: List portal modules for Composition and registry readers
      security:
        - registryBearer: []
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [active, inactive, archived] }
        - in: query
          name: serviceKey
          schema: { type: string }
      responses:
        '200':
          description: Portal modules
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalModulesList'
    post:
      tags: [Portal Modules]
      summary: Register a portal module
      security:
        - registryBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PortalModuleCreate'
      responses:
        '201':
          description: Created portal module
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalModule'
  /api/v1/platform-registry/portal-modules/{moduleKey}:
    get:
      tags: [Portal Modules]
      summary: Read a portal module by moduleKey
      security:
        - registryBearer: []
      parameters:
        - in: path
          name: moduleKey
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Portal module
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalModule'
    patch:
      tags: [Portal Modules]
      summary: Update a portal module
      security:
        - registryBearer: []
      parameters:
        - in: path
          name: moduleKey
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PortalModuleCreate'
      responses:
        '200':
          description: Updated portal module
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalModule'
    delete:
      tags: [Portal Modules]
      summary: Delete a portal module
      security:
        - registryBearer: []
      parameters:
        - in: path
          name: moduleKey
          required: true
          schema: { type: string }
      responses:
        '204':
          description: Deleted


  /api/v1/platform-registry/platform-services:
    get:
      summary: List platform service catalog records
      security: [{ bearerAuth: [] }]
      responses:
        '200':
          description: Platform service catalog records
          content:
            application/json:
              schema:
                type: object
                required: [platformServices]
                properties:
                  platformServices:
                    type: array
                    items: { $ref: '#/components/schemas/PlatformService' }
    post:
      summary: Create platform service catalog record
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PlatformServiceInput' }
      responses:
        '201':
          description: Created platform service
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PlatformService' }

  /api/v1/platform-registry/platform-services/{serviceKey}:
    get:
      summary: Read platform service catalog record
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: serviceKey
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Platform service
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PlatformService' }
    patch:
      summary: Update platform service catalog record
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: serviceKey
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PlatformServiceInput' }
      responses:
        '200':
          description: Updated platform service
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PlatformService' }
    delete:
      summary: Delete platform service catalog record
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: serviceKey
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }

  /api/v1/platform-registry/product-surfaces:
    get:
      summary: List product surfaces
      security: [{ bearerAuth: [] }]
      responses:
        '200':
          description: Product surfaces
          content:
            application/json:
              schema:
                type: object
                required: [productSurfaces]
                properties:
                  productSurfaces:
                    type: array
                    items: { $ref: '#/components/schemas/ProductSurface' }
    post:
      summary: Create product surface
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProductSurfaceInput' }
      responses:
        '201':
          description: Created product surface
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductSurface' }

  /api/v1/platform-registry/product-surfaces/{surfaceKey}:
    get:
      summary: Read product surface
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: surfaceKey
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Product surface
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductSurface' }
    patch:
      summary: Update product surface
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: surfaceKey
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProductSurfaceInput' }
      responses:
        '200':
          description: Updated product surface
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductSurface' }
    delete:
      summary: Delete product surface
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: surfaceKey
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }

  /api/v1/platform-registry/service-contracts:
    get:
      summary: List service contracts
      security: [{ bearerAuth: [] }]
      parameters:
        - in: query
          name: serviceKey
          schema: { type: string }
        - in: query
          name: contractType
          schema: { type: string, enum: [openapi, asyncapi, event, runtime, descriptor] }
        - in: query
          name: status
          schema: { type: string, enum: [active, deprecated, archived] }
      responses:
        '200':
          description: Service contracts
          content:
            application/json:
              schema:
                type: object
                required: [serviceContracts]
                properties:
                  serviceContracts:
                    type: array
                    items: { $ref: '#/components/schemas/ServiceContract' }
    post:
      summary: Create service contract catalog record
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ServiceContractInput' }
      responses:
        '201':
          description: Created service contract
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ServiceContract' }

  /api/v1/platform-registry/service-contracts/{contractKey}:
    get:
      summary: Read service contract
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: contractKey
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Service contract
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ServiceContract' }
    patch:
      summary: Update service contract
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: contractKey
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ServiceContractInput' }
      responses:
        '200':
          description: Updated service contract
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ServiceContract' }
    delete:
      summary: Delete service contract
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: contractKey
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }

  /api/v1/platform-registry/descriptors/{ref}:
    get:
      summary: Get descriptor JSON body by ref
      security: [{ registryBearer: [] }]
      parameters:
        - name: ref
          in: path
          required: true
          schema:
            type: string
            maxLength: 512
      responses:
        '200':
          description: Raw descriptor body (JSON). Header `x-mss-descriptor-content-digest` when known.
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      summary: Upsert descriptor (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: ref
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/DescriptorPutRequest' }
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  ref: { type: string }
                  schemaVersion: { type: integer }
                  contentDigest: { type: string }
                  updatedAt: { type: string, format: date-time }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/platform-registry/admin/orgs:
    get:
      summary: List distinct org ids (admin)
      security: [{ registryBearer: [] }]
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  orgIds:
                    type: array
                    items: { type: string }
              example: { orgIds: [org-1] }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/platform-registry/launch-targets:
    post:
      summary: Create launch target (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LaunchTargetCreate' }
      responses:
        '201':
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LaunchTarget' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/platform-registry/launch-targets/{id}:
    patch:
      summary: Partial update launch target — unknown fields rejected; empty patch rejected (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/LaunchTargetCreate' }
      responses:
        '200':
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LaunchTarget' }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete launch target (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/workspaces:
    post:
      summary: Create workspace (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WorkspaceCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
  /api/v1/platform-registry/workspaces/{workspaceId}:
    patch:
      summary: Patch workspace (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WorkspaceCreate' }
      responses:
        '200': { description: Updated document }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete workspace (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: workspaceId
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/apps:
    post:
      summary: Create app (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AppCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
  /api/v1/platform-registry/apps/{appId}:
    patch:
      summary: Patch app (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: appId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AppCreate' }
      responses:
        '200': { description: Updated }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete app (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: appId
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/portals:
    post:
      summary: Create portal (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PortalCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
  /api/v1/platform-registry/portals/{portalId}:
    patch:
      summary: Patch portal (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: portalId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PortalCreate' }
      responses:
        '200': { description: Updated }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete portal (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: portalId
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/environments:
    post:
      summary: Create environment (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EnvironmentCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
  /api/v1/platform-registry/environments/{environmentId}:
    patch:
      summary: Patch environment (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: environmentId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/EnvironmentCreate' }
      responses:
        '200': { description: Updated }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete environment (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: environmentId
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/personas:
    post:
      summary: Create persona (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PersonaCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '409': { $ref: '#/components/responses/Conflict' }
  /api/v1/platform-registry/personas/{personaId}:
    patch:
      summary: Patch persona (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: personaId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PersonaCreate' }
      responses:
        '200': { description: Updated }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      summary: Delete persona (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: personaId
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/domain-hosts:
    post:
      summary: Upsert domain host mapping (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/DomainHostCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /api/v1/platform-registry/domain-hosts/{host}:
    delete:
      summary: Delete host mapping (admin)
      security: [{ registryBearer: [] }]
      parameters:
        - name: host
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Deleted }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /api/v1/platform-registry/orgs-context:
    post:
      summary: Upsert org context (admin)
      security: [{ registryBearer: [] }]
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/OrgContextCreate' }
      responses:
        '201': { description: Created }
        '400': { $ref: '#/components/responses/ValidationError' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
