openapi: 3.1.0
info:
  title: not.catastrophic.io
  version: "1.0.0"
  summary: Well-formed counterparts to the catastrophic.io chaos endpoints.
  description: |
    A control site. Every endpoint here returns the shape a correctly-implemented
    version of the same path would produce. Use it as a reference: if your client
    works against `https://not.catastrophic.io/X` but fails against
    `https://catastrophic.io/X`, the chaos detection on the chaos site is real.

    Every response carries:

    - `X-Chaos-Origin: control`
    - `X-Chaos-Counterpart: https://catastrophic.io/<path>`

    The discovery documents on this site reference URLs that actually resolve
    here (not on the chaos site).
servers:
  - url: https://not.catastrophic.io
    description: Control site (well-formed counterparts).
  - url: https://chaos.catastrophic.io
    description: Chaos site (the same paths, but adversarial).

tags:
  - name: payload
    description: Mirrors of the chaos format-chaos and delivery endpoints.
  - name: headers
    description: Mirrors of the chaos header / caching endpoints.
  - name: agent
    description: Mirrors of the chaos agent-confusion endpoints.
  - name: discovery
    description: Self-consistent .well-known discovery documents.
  - name: inspection
    description: Mirrors of the request-inspection endpoints.

paths:
  # The control site exposes both a positive name (canonical) and the
  # path-equivalent chaos name (alias) for each non-discovery endpoint.
  # Aliases route to the same handler; documented separately so tooling
  # and the playground show both.
  /json:
    get:
      operationId: nonJson
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/json/
      tags: [payload]
      summary: Valid JSON, application/json Content-Type
      description: Canonical name for the json-chaos control. Path-equivalent alias `/json-chaos` also accepted.
      responses:
        '200':
          description: Valid JSON body.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /complete-stream:
    get:
      operationId: nonCompleteStream
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/delivery/partial-stream/
      tags: [payload]
      summary: Complete JSON body (canonical for partial-stream control)
      description: Path-equivalent alias `/partial-stream` also accepted.
      responses:
        '200':
          description: Complete JSON body.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /matched-content-type:
    get:
      operationId: nonMatchedContentType
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/headers/wrong-content-type/
      tags: [payload]
      summary: JSON body with matching Content-Type
      description: Canonical name for the wrong-content-type control. Path-equivalent alias `/wrong-content-type` also accepted.
      responses:
        '200':
          description: JSON body, correctly labelled.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /cache-coherent:
    get:
      operationId: nonCacheCoherent
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/headers/cache-confused/
      tags: [headers]
      summary: Single sensible Cache-Control directive
      description: "Canonical name for the cache-confused control. Returns `Cache-Control: public, max-age=300`. Path-equivalent alias `/cache-confused` also accepted."
      responses:
        '200':
          description: Response with a non-contradictory Cache-Control header.
          headers:
            Cache-Control:        { schema: { type: string } }
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /complete-body:
    parameters:
      - name: at
        in: query
        description: Number of bytes in the body. Content-Length matches.
        schema: { type: integer, minimum: 1, maximum: 1000000, default: 512 }
    get:
      operationId: nonCompleteBody
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/delivery/truncate/
      tags: [payload]
      summary: Full body sent; Content-Length honest
      description: Canonical name for the truncate control. Path-equivalent alias `/truncate` also accepted.
      responses:
        '200':
          description: Honest body.
          headers:
            Content-Length:           { schema: { type: integer } }
            X-Chaos-Origin:           { schema: { type: string, const: control } }
            X-Chaos-Counterpart:      { schema: { type: string } }
          content:
            text/plain:
              schema: { type: string }

  /image:
    get:
      operationId: nonImage
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/image/
      tags: [payload]
      summary: Image whose metadata matches its bytes
      description: |
        Canonical name for the image-chaos control. Returns a real 1×1
        PNG whose magic bytes, Content-Type, Content-Length, and IHDR
        dimensions all agree. Path-equivalent alias `/image-chaos` also
        accepted — build a client against `not.catastrophic.io/image-chaos`,
        then flip the hostname to `chaos.catastrophic.io` to exercise
        the chaos counterparts.
      responses:
        '200':
          description: Coherent image response.
          headers:
            Content-Length:                  { schema: { type: integer } }
            X-Chaos-Image-Note:              { schema: { type: string } }
            X-Chaos-Image-Actual-Dimensions: { schema: { type: string } }
            X-Chaos-Origin:                  { schema: { type: string, const: control } }
            X-Chaos-Counterpart:             { schema: { type: string } }
            Cache-Control:                   { schema: { type: string, const: no-transform } }
          content:
            image/png:
              schema: { type: string, format: binary }

  /infinite/json/users/{path}:
    get:
      operationId: infiniteJsonUsers
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/json-users/
      summary: Deterministic well-formed user JSON for any path tail.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: fields
          in: query
          required: false
          schema: { type: string }
          description: "Comma-separated field names. Open-ended; unknown names get a generic seeded string."
      responses:
        '200':
          description: A well-formed JSON object representing a user.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/json/products/{path}:
    get:
      operationId: infiniteJsonProducts
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/json-products/
      summary: Deterministic well-formed product JSON for any path tail.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: fields
          in: query
          required: false
          schema: { type: string }
          description: "Comma-separated field names. Open-ended; unknown names get a generic seeded string."
      responses:
        '200':
          description: A well-formed JSON object representing a product.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/json/posts/{path}:
    get:
      operationId: infiniteJsonPosts
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/json-posts/
      summary: Deterministic well-formed post JSON for any path tail.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: fields
          in: query
          required: false
          schema: { type: string }
          description: "Comma-separated field names. Open-ended; unknown names get a generic seeded string."
      responses:
        '200':
          description: A well-formed JSON object representing a post.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/json/comments/{path}:
    get:
      operationId: infiniteJsonComments
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/json-comments/
      summary: Deterministic well-formed comment JSON for any path tail.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: fields
          in: query
          required: false
          schema: { type: string }
          description: "Comma-separated field names. Open-ended; unknown names get a generic seeded string."
      responses:
        '200':
          description: A well-formed JSON object representing a comment.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            application/json:
              schema: { type: object, additionalProperties: true }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/html/users/{path}:
    get:
      operationId: infiniteHtmlUsers
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/html-users/
      summary: Deterministic well-formed user HTML; links to their posts, comments, and followed users.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: links
          in: query
          required: false
          schema: { type: integer }
          description: "Outbound link count (0-10, default 10). Out-of-range values clamped silently."
      responses:
        '200':
          description: A well-formed HTML user-profile page with cross-type outbound links.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            text/html:
              schema: { type: string }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/html/products/{path}:
    get:
      operationId: infiniteHtmlProducts
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/html-products/
      summary: Deterministic well-formed product HTML; links to related products and reviewing users.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: links
          in: query
          required: false
          schema: { type: integer }
          description: "Outbound link count (0-10, default 10). Out-of-range values clamped silently."
      responses:
        '200':
          description: A well-formed HTML product page with cross-type outbound links.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            text/html:
              schema: { type: string }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/html/posts/{path}:
    get:
      operationId: infiniteHtmlPosts
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/html-posts/
      summary: Deterministic well-formed post HTML; links to author, comments, and related posts.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: links
          in: query
          required: false
          schema: { type: integer }
          description: "Outbound link count (0-10, default 10). Out-of-range values clamped silently."
      responses:
        '200':
          description: A well-formed HTML article page with cross-type outbound links.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            text/html:
              schema: { type: string }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /infinite/html/comments/{path}:
    get:
      operationId: infiniteHtmlComments
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/infinite/html-comments/
      summary: Deterministic well-formed comment HTML; links to author, parent post, and sibling/related comments.
      tags: [Infinite]
      parameters:
        - name: path
          in: path
          required: true
          schema: { type: string }
          description: |
            Arbitrary path of any depth — slashes are NOT URL-encoded.
            The full path tail (e.g. `alice/profile/2024`) seeds the
            deterministic content generator. Same path tail under different
            `type` segments yields different content.
          style: simple
          explode: false
        - name: links
          in: query
          required: false
          schema: { type: integer }
          description: "Outbound link count (0-10, default 10). Out-of-range values clamped silently."
      responses:
        '200':
          description: A well-formed HTML comment page with cross-type outbound links.
          headers:
            X-Not-Infinite-Type:
              schema: { type: string }
            X-Not-Infinite-Format:
              schema: { type: string }
            X-Not-Infinite-Truncated:
              schema: { type: string }
              description: "Present only when the body was truncated to fit under the 20 KB cap."
          content:
            text/html:
              schema: { type: string }
        '429':
          description: Rate limit exceeded (10 req / 60s per IP for /infinite/*).
        '404':
          description: Format or type not recognised, or path tail empty.

  /jsonl:
    get:
      operationId: nonJsonl
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/jsonl/
      tags: [payload]
      summary: Conformant NDJSON stream
      description: |
        Canonical name for the jsonl-chaos control. Returns one valid
        JSON object per line, consistent schema across all records, LF
        line endings, trailing newline, no BOM. Path-equivalent alias
        `/jsonl-chaos` also accepted.
      responses:
        '200':
          description: Conformant NDJSON body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/x-ndjson:
              schema: { type: string }

  /svg:
    get:
      operationId: nonSvg
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/svg/
      tags: [payload]
      summary: Conformant SVG document
      description: |
        Canonical name for the svg-chaos control. Returns a small SVG
        with the declared SVG namespace, correctly nested tags, no
        external references, no <use> cycles. Renders identically in
        every browser and SVG processor. Path-equivalent alias
        `/svg-chaos` also accepted.
      responses:
        '200':
          description: Conformant SVG body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            image/svg+xml:
              schema: { type: string }

  /csv:
    get:
      operationId: nonCsv
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/csv/
      tags: [payload]
      summary: RFC 4180 compliant CSV
      description: |
        Canonical name for the csv-chaos control. Returns a CSV with
        header + rows, all consistent column counts, fields with commas
        quoted, no BOM, CRLF line endings. Path-equivalent alias
        `/csv-chaos` also accepted.
      responses:
        '200':
          description: Well-formed CSV body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            text/csv:
              schema: { type: string }

  /xml:
    get:
      operationId: nonXml
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/xml/
      tags: [payload]
      summary: Well-formed XML document
      description: |
        Canonical name for the xml-chaos control. Returns a small XML
        document with an honest declaration, correct tag nesting, no
        entity declarations, and no external references. Path-equivalent
        alias `/xml-chaos` also accepted — build against
        `not.catastrophic.io/xml-chaos`, then flip the hostname to
        exercise the chaos counterparts.
      responses:
        '200':
          description: Well-formed XML body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/xml:
              schema: { type: string }

  /zip:
    get:
      operationId: nonZip
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/zip/
      tags: [payload]
      summary: Well-formed STORED ZIP archive
      description: |
        Canonical name for the zip-chaos control. Returns a single-entry
        STORED ZIP archive — `hello.txt` with correct CRC32, declared
        sizes matching the body, central directory entries agreeing with
        the local file header, and a present EOCD. Path-equivalent alias
        `/zip-chaos` also accepted — build against
        `not.catastrophic.io/zip-chaos`, then flip the hostname to
        exercise the chaos counterparts.
      responses:
        '200':
          description: Well-formed ZIP body.
          headers:
            Content-Length:      { schema: { type: integer } }
            X-Chaos-Zip-Note:    { schema: { type: string } }
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/zip:
              schema: { type: string, format: binary }

  /ooxml:
    parameters:
      - name: format
        in: query
        description: Which OOXML format to return.
        schema:
          type: string
          enum: [docx, xlsx, pptx]
          default: docx
    get:
      operationId: nonOoxml
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/ooxml/
      tags: [payload]
      summary: Well-formed OOXML package (DOCX / XLSX / PPTX)
      description: |
        Canonical name for the ooxml-chaos control. Returns a minimal
        well-formed Office Open XML package for the requested format —
        `[Content_Types].xml` declares the correct content type for
        every part, all relationships resolve to parts that exist, no
        format confusion. Opens cleanly in python-docx / openpyxl /
        python-pptx (and by extension Microsoft Office, LibreOffice,
        and other conformant readers). Path-equivalent alias
        `/ooxml-chaos` also accepted.
      responses:
        '200':
          description: Well-formed OOXML package.
          headers:
            X-Chaos-Ooxml-Format: { schema: { type: string } }
            X-Chaos-Ooxml-Note:   { schema: { type: string } }
            Content-Disposition:  { schema: { type: string } }
            Content-Length:       { schema: { type: integer } }
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
            Cache-Control:        { schema: { type: string, const: no-transform } }
          content:
            application/vnd.openxmlformats-officedocument.wordprocessingml.document:
              schema: { type: string, format: binary }
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema: { type: string, format: binary }
            application/vnd.openxmlformats-officedocument.presentationml.presentation:
              schema: { type: string, format: binary }

  /pdf:
    get:
      operationId: nonPdf
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/pdf/
      tags: [payload]
      summary: Well-formed PDF document
      description: |
        Canonical name for the pdf-chaos control. Returns a five-object PDF
        (Catalog, Pages, Page, content stream, link annotation to catastrophic.io).
        xref offsets accurate, /Count honest, no /Encrypt reference, no JavaScript
        actions. Opens cleanly in any conformant PDF reader. Path-equivalent alias
        `/pdf-chaos` also accepted — build against `not.catastrophic.io/pdf-chaos`,
        then flip the hostname to exercise the chaos counterparts.
      responses:
        '200':
          description: Well-formed PDF body.
          headers:
            Content-Length:      { schema: { type: integer } }
            X-Chaos-Pdf-Note:    { schema: { type: string } }
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /html:
    get:
      operationId: nonHtml
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/html/
      tags: [payload]
      summary: Well-formed HTML document
      description: |
        Canonical name for the html-chaos control. Returns a small HTML
        document where tags nest correctly, the declared charset matches
        the body bytes, the doctype matches the markup version, and no
        tag is left open. Path-equivalent alias `/html-chaos` also
        accepted — build against `not.catastrophic.io/html-chaos`, then
        flip the hostname to exercise the chaos counterparts.
      responses:
        '200':
          description: Well-formed HTML body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            text/html:
              schema: { type: string }

  /resolvable-discovery:
    parameters:
      - name: schema
        in: query
        description: Which schema shape to return. URLs in the document resolve to real stubs on this same subdomain.
        schema:
          type: string
          enum: [openid-configuration, oauth-authorization-server, webfinger, jwks, host-meta, agent-card]
          default: openid-configuration
    get:
      operationId: nonResolvableDiscovery
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/agent/semantic-drift/
      tags: [agent]
      summary: Discovery doc with URLs that actually resolve
      description: Canonical name for the semantic-drift control. Path-equivalent alias `/semantic-drift` also accepted.
      responses:
        '200':
          description: Well-formed discovery document.
          headers:
            X-Chaos-Schema:       { schema: { type: string } }
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  # ---- Path-equivalent chaos-name aliases (kept for client base-URL swaps) ----

  /partial-stream:
    get:
      operationId: nonPartialStream
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/delivery/partial-stream/
      tags: [payload]
      summary: Complete JSON body
      description: Returns a complete, well-formed JSON body. Counterpart to the chaos `/partial-stream`.
      responses:
        '200':
          description: Complete JSON body.
          headers:
            X-Chaos-Origin: { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:     { type: string }
                  complete:     { type: boolean }
                  items:        { type: array, items: { type: string } }
                  finished_at:  { type: string, format: date-time }

  /wrong-content-type:
    get:
      operationId: nonWrongContentType
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/headers/wrong-content-type/
      tags: [payload]
      summary: JSON body with matching Content-Type
      description: Returns JSON labelled `application/json` (declared type matches body). Counterpart to the chaos `/wrong-content-type`.
      responses:
        '200':
          description: JSON body, correctly labelled.
          headers:
            X-Chaos-Origin: { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:           { type: string }
                  content_type_sent:  { type: string }
                  matches_body:       { type: boolean }

  /cache-confused:
    get:
      operationId: nonCacheConfused
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/headers/cache-confused/
      tags: [headers]
      summary: Sensible single Cache-Control directive
      description: "Returns a sensible `Cache-Control: public, max-age=300`. Counterpart to the chaos `/cache-confused`."
      responses:
        '200':
          description: Response with a non-contradictory Cache-Control header.
          headers:
            Cache-Control:        { schema: { type: string } }
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint:        { type: string }
                  directives_sent: { type: array, items: { type: string } }

  /truncate:
    parameters:
      - name: at
        in: query
        description: Number of bytes in the body. Content-Length matches.
        schema: { type: integer, minimum: 1, maximum: 1000000, default: 512 }
    get:
      operationId: nonTruncate
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/delivery/truncate/
      tags: [payload]
      summary: Full body sent; Content-Length honest
      description: Returns `at` bytes of `A` characters with a matching `Content-Length`. Counterpart to the chaos `/truncate`.
      responses:
        '200':
          description: Honest body.
          headers:
            Content-Length:           { schema: { type: integer } }
            X-Chaos-Promised-Bytes:   { schema: { type: integer } }
            X-Chaos-Origin:           { schema: { type: string, const: control } }
            X-Chaos-Counterpart:      { schema: { type: string } }
          content:
            text/plain:
              schema: { type: string }

  /semantic-drift:
    parameters:
      - name: schema
        in: query
        description: Which schema shape to return. URLs in the document resolve to real stubs on this same subdomain.
        schema:
          type: string
          enum: [openid-configuration, oauth-authorization-server, webfinger, jwks, host-meta, agent-card]
          default: openid-configuration
    get:
      operationId: nonSemanticDrift
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/agent/semantic-drift/
      tags: [agent]
      summary: Discovery doc with URLs that actually resolve
      description: Returns a structurally and semantically correct discovery document. Every URL inside points to a real stub endpoint on `not.catastrophic.io`.
      responses:
        '200':
          description: Well-formed discovery document.
          headers:
            X-Chaos-Schema:       { schema: { type: string } }
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /echo:
    get:
      operationId: nonEcho
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/echo/
      tags: [inspection]
      summary: Reflect the request as JSON
      description: Same behavior as the chaos `/echo`.
      responses:
        '200':
          description: Reflected request.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { type: string }
                  method:   { type: string }
                  url:      { type: string }
                  path:     { type: string }
                  query:    { type: object }
                  headers:  { type: object }
                  body:     { nullable: true }

  /.well-known/openid-configuration:
    get:
      operationId: nonOidcConfig
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/openid-configuration/
      tags: [discovery]
      summary: Self-consistent OIDC discovery document
      description: Issuer is `https://not.catastrophic.io`. All URLs resolve to real stubs on this subdomain.
      responses:
        '200':
          description: OIDC discovery document.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /.well-known/oauth-authorization-server:
    get:
      operationId: nonOauthAs
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/oauth-authorization-server/
      tags: [discovery]
      summary: OAuth Authorization Server metadata
      description: Same issuer as the OIDC document. Self-consistent across the discovery group.
      responses:
        '200':
          description: OAuth AS metadata.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /.well-known/oauth-protected-resource:
    get:
      operationId: nonOauthProtectedResource
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/oauth-protected-resource/
      tags: [discovery]
      summary: RFC 9728 Protected Resource Metadata
      description: |
        Self-consistent Protected Resource Metadata document.
        `resource` matches the URL the document is served from;
        `authorization_servers` points at
        `not.catastrophic.io/.well-known/oauth-authorization-server`
        which itself resolves on this host; `bearer_methods_supported`
        uses only IANA-registered values.
      responses:
        '200':
          description: Coherent Protected Resource Metadata document.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /.well-known/mcp/server-card.json:
    get:
      operationId: nonMcpServerCard
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/mcp-server-card/
      tags: [discovery]
      summary: SEP-1649 MCP Server Card
      description: |
        Self-consistent MCP Server Card. `protocolVersion` matches the
        declared `capabilities`; `transport.endpoint` resolves to a
        minimal MCP error stub on this same host (returns a valid
        JSON-RPC "Method not found" response).
      responses:
        '200':
          description: Coherent MCP Server Card.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /.well-known/agent-skills/index.json:
    get:
      operationId: nonAgentSkillsIndex
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/agent-skills-index/
      tags: [discovery]
      summary: Agent Skills Discovery v0.2.0 index
      description: |
        Self-consistent agent-skills index. Single skill entry whose
        `url` resolves to a valid skill document on this same host at
        `/.well-known/agent-skills/echo.json`. The `sha256` digest is
        all-zeros — obviously fake but format-correct, matching the
        AASA / assetlinks "fake-but-format-valid" identity pattern
        used elsewhere on this host.
      responses:
        '200':
          description: Coherent agent-skills index.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /.well-known/agent-card.json:
    get:
      operationId: nonAgentCard
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/discovery/agent-card/
      tags: [discovery]
      summary: Agent card consistent with the discovery group
      description: Agent URL is on `not.catastrophic.io`, matching the OIDC/OAuth issuer. Skills point to real endpoints.
      responses:
        '200':
          description: Agent card.
          headers:
            X-Chaos-Origin:       { schema: { type: string, const: control } }
            X-Chaos-Counterpart:  { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /problem-details:
    get:
      operationId: nonProblemDetails
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/problem-details/
      tags: [payload]
      summary: Well-formed RFC 9457 Problem Details object
      description: |
        Returns a spec-compliant RFC 9457 Problem Details envelope: `type` URI present, `status` as integer 404, `detail` string, `instance` as a URI reference. Served as `application/problem+json`. Path-equivalent alias `/problem-details-chaos` also accepted — build against `not.catastrophic.io/problem-details-chaos`, then flip the hostname to exercise the chaos counterpart.
      responses:
        '404':
          description: Well-formed Problem Details body.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/problem+json:
              schema: { type: object }

  /jsonrpc:
    get:
      operationId: nonJsonRpc
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/jsonrpc/
      tags: [payload]
      summary: Well-formed JSON-RPC 2.0 response
      description: |
        Returns a spec-compliant JSON-RPC 2.0 response: `jsonrpc` version present, `id` matching the implied request, `result` present without `error`. Path-equivalent alias `/jsonrpc-chaos` also accepted.
      responses:
        '200':
          description: Well-formed JSON-RPC 2.0 envelope.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /jsonapi:
    get:
      operationId: nonJsonApi
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/jsonapi/
      tags: [payload]
      summary: Well-formed JSON:API response
      description: |
        Returns a spec-compliant JSON:API response served as `application/vnd.api+json`: single resource object with `type`, `id`, `attributes`, and `relationships`; all `included` resources referenced by a relationship. Path-equivalent alias `/jsonapi-chaos` also accepted.
      responses:
        '200':
          description: Well-formed JSON:API envelope.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/vnd.api+json:
              schema: { type: object }

  /oauth-token:
    get:
      operationId: nonOauthToken
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/oauth-token/
      tags: [payload]
      summary: Well-formed RFC 6749 token response
      description: |
        Returns a spec-compliant RFC 6749 §5.1 token response: `expires_in` as integer, `scope` space-delimited, `token_type` as "Bearer", `refresh_token` present. `Cache-Control: no-store` matches real token endpoint behaviour. Path-equivalent alias `/oauth-token-chaos` also accepted.
      responses:
        '200':
          description: Well-formed OAuth token response.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-store } }
          content:
            application/json:
              schema: { type: object }

  /cloudevents:
    get:
      operationId: nonCloudEvents
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/cloudevents/
      tags: [payload]
      summary: Well-formed CloudEvents 1.0 envelope
      description: |
        Returns a spec-compliant CloudEvents 1.0 envelope served as `application/cloudevents+json`: `specversion` is "1.0", `type` is a non-empty string, `datacontenttype` matches the `data` shape, only `data` present. Path-equivalent alias `/cloudevents-chaos` also accepted.
      responses:
        '200':
          description: Well-formed CloudEvents 1.0 envelope.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/cloudevents+json:
              schema: { type: object }

  /geojson:
    get:
      operationId: nonGeoJson
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/geojson/
      tags: [payload]
      summary: Well-formed GeoJSON Feature
      description: |
        Returns a spec-compliant GeoJSON Feature served as `application/geo+json`: Polygon ring closed (first and last positions identical), coordinates in [longitude, latitude] order per RFC 7946, `type` consistent with `coordinates` shape, `properties` member present. Path-equivalent alias `/geojson-chaos` also accepted.
      responses:
        '200':
          description: Well-formed GeoJSON Feature.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/geo+json:
              schema: { type: object }

  /schema-org:
    get:
      operationId: nonSchemaOrg
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/schema-org/
      tags: [payload]
      summary: Well-formed Schema.org / JSON-LD document
      description: |
        Returns a spec-compliant Schema.org JSON-LD document served as `application/ld+json`: `@context` is https://schema.org, `@type` is from the vocabulary, all required properties for the type present, no context-array shadowing. Path-equivalent alias `/schema-org-chaos` also accepted.
      responses:
        '200':
          description: Well-formed Schema.org JSON-LD document.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/ld+json:
              schema: { type: object }

  /jwt-payload:
    get:
      operationId: nonJwtPayload
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/jwt-payload/
      tags: [payload]
      summary: Well-formed JWT payload (claim set)
      description: |
        Returns a spec-compliant RFC 7519 JWT claim set: `iss` is a URI, `sub` is a string principal identifier, `aud` is a homogeneous array of StringOrURI values, `exp` and `iat` are NumericDate integers. Path-equivalent alias `/jwt-payload-chaos` also accepted.
      responses:
        '200':
          description: Well-formed JWT claim set.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }

  /activitystreams:
    get:
      operationId: nonActivityStreams
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/activitystreams/
      tags: [payload]
      summary: Well-formed ActivityStreams 2.0 object
      description: |
        Returns a spec-compliant ActivityStreams 2.0 / ActivityPub object served as `application/activity+json`: `@context` present, `type` from the AS2 vocabulary, `actor` a dereferenceable URI, `object` consistent with the activity type. Path-equivalent alias `/activitystreams-chaos` also accepted.
      responses:
        '200':
          description: Well-formed ActivityStreams 2.0 object.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/activity+json:
              schema: { type: object }

  /web-annotation:
    get:
      operationId: nonWebAnnotation
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/web-annotation/
      tags: [payload]
      summary: Well-formed W3C Web Annotation
      description: |
        Returns a spec-compliant W3C Web Annotation served as `application/ld+json` with the `anno.jsonld` profile: `id` present, `body` is a TextualBody object, `motivation` from the standard vocabulary, `target` is a URI string. Path-equivalent alias `/web-annotation-chaos` also accepted.
      responses:
        '200':
          description: Well-formed Web Annotation.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/ld+json:
              schema: { type: object }

  /sse:
    get:
      operationId: nonSse
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/sse/
      tags: [payload]
      summary: Well-formed Server-Sent Events stream
      description: |
        Returns a spec-compliant Server-Sent Events stream served as `text/event-stream`: `data:`, `event:`, `id:`, and `retry:` fields all correctly framed, blank-line event separators, four events then close. Path-equivalent alias `/sse-chaos` also accepted.
      responses:
        '200':
          description: Well-formed SSE stream.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            text/event-stream:
              schema: { type: string }

  /multipart:
    get:
      operationId: nonMultipart
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/multipart/
      tags: [payload]
      summary: Well-formed multipart/form-data response
      description: |
        Returns a spec-compliant `multipart/form-data` response with two parts (a text field and a file field): declared boundary matches body delimiter, every part has a single Content-Disposition `name=` parameter, closing boundary has trailing CRLF. Path-equivalent alias `/multipart-chaos` also accepted.
      responses:
        '200':
          description: Well-formed multipart/form-data response.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            multipart/form-data:
              schema: { type: string }

  /toml:
    get:
      operationId: nonToml
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/toml/
      tags: [payload]
      summary: Well-formed TOML 1.0 document
      description: |
        Returns a spec-compliant TOML 1.0 document served as `application/toml`: top-level scalars, sub-table, arrays of tables, RFC 3339 datetime, homogeneous arrays. Path-equivalent alias `/toml-chaos` also accepted.
      responses:
        '200':
          description: Well-formed TOML 1.0 document.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/toml:
              schema: { type: string }

  /yaml:
    get:
      operationId: nonYaml
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/yaml/
      tags: [payload]
      summary: Well-formed YAML 1.2 document
      description: |
        Returns a spec-compliant YAML 1.2 document served as `application/yaml`: scalars, mapping, sequence, and a sane anchor with merge-key. No Norway-problem scalars, no cycles, no duplicate keys, no tag lies. Parses identically under YAML 1.1 and 1.2 parsers. Path-equivalent alias `/yaml-chaos` also accepted.
      responses:
        '200':
          description: Well-formed YAML 1.2 document.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
            Cache-Control:       { schema: { type: string, const: no-transform } }
          content:
            application/yaml:
              schema: { type: string }

  /json-feed:
    get:
      operationId: nonJsonFeed
      externalDocs:
        description: Endpoint reference on catastrophic.io
        url: https://catastrophic.io/not/endpoints/format/json-feed/
      tags: [payload]
      summary: Well-formed JSON Feed 1.1
      description: |
        Returns a spec-compliant JSON Feed 1.1 document served as `application/feed+json`: `version` is the full URL "https://jsonfeed.org/version/1.1", `feed_url` matches the served URL, item ids unique, item urls absolute. Path-equivalent alias `/json-feed-chaos` also accepted.
      responses:
        '200':
          description: Well-formed JSON Feed 1.1 document.
          headers:
            X-Chaos-Origin:      { schema: { type: string, const: control } }
            X-Chaos-Counterpart: { schema: { type: string } }
          content:
            application/feed+json:
              schema: { type: object }
