openapi: 3.1.0
info:
  title: Mesh Global Software CBOM Export
  version: "1.1.0"
  description: |
    Returns a CycloneDX 1.6 Crypto Bill of Materials (CBOM) for a piece of
    software, drawn from Axiad Mesh's global software catalog. Intended for
    vendor and procurement CBOM consumption — for example, evaluating the
    post-quantum readiness of software before you adopt it.

    This endpoint is **not** tenant-scoped: it serves only public catalog data
    and returns the same result for every authenticated caller. Authentication
    is still required (see Security).
servers:
  - url: https://mesh.axiad.io/api/analytics/v1
    description: Axiad Mesh API host

tags:
  - name: Software CBOM
    description: Export a Crypto Bill of Materials for a piece of software.

paths:
  /export/cyclonedx:
    get:
      tags: [Software CBOM]
      operationId: exportGlobalCycloneDX
      summary: Export a CycloneDX 1.6 CBOM for global-catalog software
      description: |
        Resolves the `(name, version, publisher)` query to global-catalog rows
        (latest **complete** snapshot only — the read is freshness-gated) and
        renders ONE CycloneDX 1.6 document.

        - **With `publisher`:** exact fingerprint lookup → at most one component.
        - **Without `publisher`:** `name`+`version` may match several publisher
          variants; all are emitted as components in the single document, each
          with its own `mesh:pqc_compliant` and `mesh:pqc_state` properties.

        On a **catalog miss** the request is recorded for fulfillment and the
        endpoint returns `202` (not `404`): eligibility is decided at nightly
        build time by the fail-closed public gate, never at request time.
      parameters:
        - name: name
          in: query
          required: true
          description: |
            Software name (required). Matched case-insensitively against the
            normalized catalog name. A blank / whitespace-only / literal `null`
            value collapses the lookup key and is rejected with `400`.
          schema:
            type: string
            maxLength: 512
          example: OpenSSL
        - name: version
          in: query
          required: false
          description: Software version. Omit to match any version of the name.
          schema:
            type: string
            maxLength: 512
          example: "3.0"
        - name: publisher
          in: query
          required: false
          description: |
            Software publisher / vendor. When present, the lookup is an exact
            fingerprint (≤ 1 component). When absent, all publisher variants of
            the name+version are returned.
          schema:
            type: string
            maxLength: 512
          example: OpenSSL Project
      responses:
        "200":
          description: Catalog hit — a CycloneDX 1.6 CBOM document.
          content:
            application/vnd.cyclonedx+json; version=1.6:
              schema:
                $ref: "#/components/schemas/CycloneDXDocument"
              example:
                bomFormat: CycloneDX
                specVersion: "1.6"
                version: 1
                components:
                  - type: library
                    name: OpenSSL
                    version: "3.0"
                    purl: "pkg:generic/openssl@3.0"
                    supplier:
                      name: OpenSSL Project
                    cryptoProperties:
                      assetType: algorithm
                      algorithmProperties:
                        families: ["ML-KEM", "RSA"]
                    properties:
                      - name: "mesh:pqc_compliant"
                        value: "true"
                      - name: "mesh:pqc_state"
                        value: "quantum-ready"
                      - name: "mesh:pqc_ruleset_version"
                        value: "v1"
        "202":
          description: |
            Catalog miss — the software is not in the latest complete snapshot.
            A fulfillment request has been recorded (deduped per fingerprint).
            `status` is `scheduled` on first request, `in_progress` on a repeat
            of one already pending.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FulfillmentAccepted"
              example:
                status: scheduled
                message: "software not yet available; catalog fulfillment scheduled for the next build cycle"
                query: { name: Ghostware, version: "9.9", publisher: "" }
        "400":
          description: Bad request — missing/blank `name`, or a parameter over the 512-char cap.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: BadRequest, message: "name query parameter is required" } }
        "401":
          description: |
            Unauthenticated — the request carried no valid bearer token.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: Unauthorized, message: "authentication required" } }
        "422":
          description: >-
            The software could not be identified or did not qualify for the
            catalog on a recent fulfillment attempt, and the fingerprint is in
            a re-request cooldown window. The error body carries the failure
            reason; the Retry-After header says when a retry becomes
            meaningful. Clients should stop polling.
        "429":
          description: |
            Too Many Requests — the per-caller rate limit was exceeded. The
            `Retry-After` response header gives the wait, in seconds, before a
            retry will be accepted.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: TooManyRequests, message: "rate limit exceeded" } }
        "500":
          description: Internal error querying the catalog or rendering the document.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "503":
          description: |
            The export is temporarily unavailable. Retry later, or contact your
            Axiad representative if it persists.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: ServiceUnavailable, message: "the global CycloneDX export is not available" } }
      security:
        - azureAdBearer: []

  /export/cyclonedx/openapi.yaml:
    get:
      tags: [Software CBOM]
      operationId: getGlobalCycloneDXOpenAPISpec
      summary: Fetch this OpenAPI 3.1 spec for the global CBOM export
      description: |
        Returns this OpenAPI 3.1 contract as YAML, so procurement and vendor
        tooling can retrieve the machine-readable API definition at runtime. It
        uses the same authentication as the export itself.
      responses:
        "200":
          description: The OpenAPI 3.1 document (YAML).
          content:
            application/yaml:
              schema:
                type: string
        "401":
          description: Unauthenticated — the request carried no valid bearer token.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: Unauthorized, message: "authentication required" } }
        "503":
          description: The export, and hence its spec, is temporarily unavailable.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
              example: { error: { code: ServiceUnavailable, message: "the global CycloneDX export is not available" } }
      security:
        - azureAdBearer: []

components:
  securitySchemes:
    azureAdBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        A bearer access token obtained through the standard OAuth 2.0 flow (the
        same authentication used by the rest of the Axiad Mesh API). Present it
        in the `Authorization: Bearer <token>` header. See
        [API Authentication](/docs/api-reference/authentication) for how to
        obtain a token.
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string, example: BadRequest }
            message: { type: string }
          required: [code, message]
      required: [error]
    FulfillmentAccepted:
      type: object
      properties:
        status: { type: string, enum: [scheduled, in_progress] }
        message: { type: string }
        query:
          type: object
          properties:
            name: { type: string }
            version: { type: string }
            publisher: { type: string }
      required: [status, message, query]
    CycloneDXDocument:
      type: object
      description: A CycloneDX 1.6 CBOM. Only the fields Mesh emits are described here.
      properties:
        bomFormat: { type: string, const: CycloneDX }
        specVersion: { type: string, const: "1.6" }
        version: { type: integer, example: 1 }
        components:
          type: array
          items: { $ref: "#/components/schemas/Component" }
        dependencies:
          type: array
          description: |
            Dependency graph edges. Each edge represents a software application
            and its cryptographic dependencies. Only present when the top-level
            component has nested crypto dependencies (via on-demand analysis).
            Each edge includes `ref` (the application's bom-ref) and `dependsOn`
            (an array of nested component bom-refs).
          items:
            type: object
            properties:
              ref: { type: string, description: "The application component's bom-ref" }
              dependsOn:
                type: array
                items: { type: string }
                description: "Array of crypto-dependency component bom-refs"
      required: [bomFormat, specVersion, version, components]
    Component:
      type: object
      properties:
        type: { type: string, example: library }
        name: { type: string }
        version:
          type: string
          description: Omitted when the catalog version is unknown / the `null` sentinel.
        publisher: { type: string }
        purl: { type: string, example: "pkg:generic/openssl@3.0" }
        cpe: { type: string }
        supplier:
          type: object
          properties:
            name: { type: string }
        cryptoProperties:
          type: object
          description: Present when the component has crypto evidence.
          properties:
            assetType: { type: string, example: algorithm }
            algorithmProperties:
              type: object
              properties:
                families:
                  type: array
                  items: { type: string }
                  example: ["RSA"]
        components:
          type: array
          description: |
            Nested components representing cryptographic dependencies. Each
            dependency discovered by on-demand analysis is emitted as a nested
            component with its own `type`, `name`, `version`, `cryptoProperties`
            (families only — transport/security protocols such as TLS/DTLS/SSL
            are stripped, they are not crypto primitives), and its own
            `mesh:pqc_compliant` + `mesh:pqc_state` properties computed
            deterministically from that dependency's families. Omitted (field
            absent) when no crypto dependencies are present.
          items: { $ref: "#/components/schemas/Component" }
        properties:
          type: array
          description: |
            Mesh component-level properties. The PQC verdict is
            derived DETERMINISTICALLY from the component's recognized algorithm
            families.
            Documented properties include:
            - `mesh:pqc_state` ("quantum-active" | "quantum-ready" |
              "not-quantum-ready" | "not-applicable"): the granular ternary PQC
              state.
                - `quantum-active`: a post-quantum family is present and there is
                  NO quantum-vulnerable asymmetric crypto (PQC-only).
                - `quantum-ready`: a post-quantum family is present ALONGSIDE
                  quantum-vulnerable legacy crypto (transitional / hybrid — e.g.
                  OpenSSL 3.x with ML-KEM + RSA).
                - `not-quantum-ready`: quantum-vulnerable asymmetric crypto is
                  present but NO post-quantum family.
                - `not-applicable`: no asymmetric crypto families at all
                  (symmetric/hash-only, or protocol-only). Roll-up across a
                  software's dependencies is BEST-wins: the software is
                  quantum-ready/active if ANY component contributes PQC, and is
                  downgraded active→ready when a quantum-vulnerable asymmetric
                  primitive exists anywhere across its dependencies. Post-quantum
                  recognition covers ML-KEM/ML-DSA/SLH-DSA and hybrid tokens
                  containing a PQC primitive (e.g. `X25519MLKEM768`, Kyber,
                  Dilithium); bare X25519/X448/Ed25519/RSA are classical and never
                  count as PQC.
            - `mesh:pqc_compliant` ("true" | "false" | "not-applicable"): the
              backward-compatible binary procurement signal, derived from
              `mesh:pqc_state` — "true" when the state is `quantum-active` or
              `quantum-ready`, "not-applicable" when the state is
              `not-applicable`, otherwise "false".
            - `mesh:pqc_ruleset_version`: The ruleset version used for PQC evaluation.
            - `mesh:analysis_source` ("gold" | "on_demand"): Indicates whether the
              component was sourced from the gold layer catalog (nightly build) or
              the on-demand enrichment service. Present when the analysis source is
              recorded; absent on legacy gold rows where the column is not yet
              populated.
            - `mesh:confidence` (number, 0–1): Confidence score for the on-demand
              analysis. Present only when `mesh:analysis_source` is "on_demand".
          items:
            type: object
            properties:
              name: { type: string, example: "mesh:pqc_state" }
              value: { type: string, example: "quantum-ready" }
      required: [type, name]
