# Mosaica's HTTP API: the viewer, session and control planes, as an OpenAPI 3.1 description.
#
# Hand-authored, and kept true by `crates/mosaica-server/tests/openapi.rs`, which starts the
# server, exercises every route below (a success and at least one refusal each) and validates
# every JSON request and response body against the schemas in `components`. A field added to a
# response and not to this file fails that test, because every closed DTO is declared
# `additionalProperties: false`. Where this file and the server disagree, the server is right and
# this file is wrong.
#
# The examples use the arXiv example dataset in `examples/data/arxiv`, and the same test sends
# every request example to a server built from it and compares every response example with the
# answer to the request example of its name. Where a value in an example has to come from an
# earlier answer, such as a cursor or a `mosaica_id`, `x-mosaica-fill` says where from: on a
# request body's example, for each `body` location (a JSON pointer), `path` or `query` parameter;
# on a parameter's example, for that parameter. Each names the earlier request (`operation` and
# `example`, or a request written out), what to `read` from its answer (a field of the trailer, a
# header, a column of the first records frame, or a JSON pointer), a schema the value is `like`,
# and with `same: true` that the value shown is the one the dataset gives, which the test checks.
# A response example's `x-mosaica-vary` lists the JSON pointers the comparison skips.
#
# Each server names its plane and the prefix of the routes it serves in `x-mosaica-plane` and
# `x-mosaica-route-prefix`, which `clients/ts/core/test/contract.test.ts` reads to send each
# operation to its listener.
#
# What this file cannot say: the framed-Arrow bodies of `POST /v1/viewport`,
# `POST /v1/artifacts/viewport`, `POST /v1/items`, `POST /v1/artifacts` and `POST /v1/aggregate`, and the Arrow IPC forms of `POST /control/ingest` and
# `PATCH /control/layers/{name}/artifacts`. They are not JSON, so each is declared as a binary
# media type and described in prose on its route. `docs/openapi/README.md` carries worked decodes
# of the framed bodies in Python and in JavaScript. The JSON frames of the paged reads have
# schemas below (`ItemsHead`, `ArtifactsHead`, `AggregateTableHead`, `PageEnd`, `RecordsTrailer`).
openapi: 3.1.0
info:
  title: Mosaica HTTP API
  version: "1"
  summary: The viewer, session and control planes of a Mosaica server.
  description: |
    Three planes, each served on the viewer plane's listener unless the deployment gives it an
    address of its own. The **viewer plane** answers about the corpus as one principal may see it and
    is authenticated by a *session token*; `POST /v1/login` mints one from a password, an API key
    or an OIDC access token. The **session plane** lets an integrator's backend mint a session
    for another principal, with an API key holding `authorise-as`; it is never browser-facing.
    The **control plane** declares, ingests, deletes, suppresses and publishes, reports the
    node's state, and manages principals, credentials, groups, grants, OIDC providers and
    sessions. It takes the operator credential, an API key or an OIDC access token. `/healthz`
    and `/readyz` sit on the viewer's listener and on a session listener of its own,
    unauthenticated, bare status codes with no body.

    `api_version` is published by `GET /v1/meta` and by nothing else. There is no version header,
    on any plane. A client does not send one to negotiate, and a server does not refuse a client
    whose assumed version is wrong.

    Every refusal a route's handler makes is one envelope (`Error` below) with a closed code list.
    The codes group into three classes that tell a client whether to retry:

    - **Failure**: `500 fail-closed` and `503 not-ready`. Never a partial result.
    - **Backpressure**: `429 backpressure`. Retry the request unchanged after `Retry-After`, which
      every 429 carries with an agreeing body `retry_after_s`. The number depends on what shed the
      request: `1` for compute admission, and derived from the write executor's service
      time on the control plane. Read the header.
    - **Shape**: `422 contract`. The request has to change before it is sent again.

    `401`, `403`, `404` and `409` are in none of the three: they concern the credential or the
    thing addressed. A `401 bad-credential` on a session token that worked before is a revoked
    session, and a client treats it as `403 expired-token`: the session has ended, so
    re-authorise.

    The HTTP framework answers three refusals itself, before a handler runs, with a plain-text
    body instead of the envelope: `400` for a JSON body that does not parse or a path segment that
    is not UTF-8, `413` for a JSON body over its route's byte cap, and `415` for a JSON route
    called without `content-type: application/json`. Each route declares the ones it can return.

    **An open server** (`serve.open`, or `mosaica serve --open`) serves every request on every
    plane as the operator, for one person on this machine. No credential is needed, and one that
    is sent changes nothing: a viewer route answers as a session minted with `read_all: true`
    does, and a session or control route acts with every permission. Every plane answers `403
    forbidden` to a request whose `Host`, or whose absolute-form request line, names anything but
    `localhost` or a loopback address, or that carries no host or two. Every plane answers `403
    forbidden` to a request by any method but `GET`, `HEAD`, `OPTIONS` and `TRACE` that has no
    `content-type`, or whose `content-type` is `text/plain`, `application/x-www-form-urlencoded`
    or `multipart/form-data`, whatever its case and parameters; a route that takes no body is sent
    `content-type: application/json` with an empty one. Every plane answers `403 forbidden` to a
    request whose `Origin` is not `http` or `https` on `localhost` or a loopback address, and to
    one with no `Origin` whose `Sec-Fetch-Site` is `cross-site`. The viewer plane admits a page from `localhost`,
    `127.0.0.1` or `[::1]` on any port, as `serve.cors_loopback` does, and the session and control
    planes admit no page. `POST /control/principals` and `PUT /control/providers/{name}` answer `403
    forbidden`, since an open server holds no principal and no provider.

    On the viewer plane a refusal depends on the request and the deployment's configuration, and
    never on the viewer's data. `404 unknown` there is one status, one code and one detail string
    for *no such identifier* and *exists but is not visible to this principal*.
servers:
  - url: http://{viewer-host}
    description: The viewer plane's listener, `serve.viewer`. Routes under `/v1/`, and the probes.
    x-mosaica-plane: viewer
    x-mosaica-route-prefix: /v1/
    variables:
      viewer-host:
        default: 127.0.0.1:8080
  - url: http://{session-host}
    description: The session plane's listener, `serve.session`, or the viewer's where that is not set. Routes under `/session/`, and the probes on a listener of its own.
    x-mosaica-plane: session
    x-mosaica-route-prefix: /session/
    variables:
      session-host:
        default: 127.0.0.1:8081
  - url: http://{control-host}
    description: "The control plane's listener, `serve.control`: a TCP address, or a Unix socket written `unix:<path>`, or the viewer's where that is not set. The server does not check that a TCP address is loopback. Routes under `/control/`."
    x-mosaica-plane: control
    x-mosaica-route-prefix: /control/
    variables:
      control-host:
        default: 127.0.0.1:8082

tags:
  - name: session
    description: Minting and revoking sessions for other principals. Authenticated by an API key holding `authorise-as`.
  - name: viewer
    description: Logging in, and the corpus as one principal sees it. Authenticated by a session token.
  - name: probes
    description: Liveness and readiness, on the viewer's listener and on a session listener of its own, unauthenticated, no body.
  - name: basemaps
    description: Map backgrounds compiled into the server, the same for every principal. Authenticated by a session token.
  - name: "control: ingest"
    description: Items, with their positions, labels and values, created and edited. Control plane.
  - name: "control: changes"
    description: Deletions, suppressions and lifted suppressions. Control plane.
  - name: "control: status"
    description: The node's state, counters and published limits, and where each compaction request is. Control plane.
  - name: "control: flush"
    description: Publishing buffered writes ahead of the next tick. Control plane.
  - name: "control: compact"
    description: Asking for a compaction fold. Control plane.
  - name: "control: attributes"
    description: Declaring attribute columns. Control plane.
  - name: "control: shards"
    description: Declaring the size of the shard items are stored in. Control plane.
  - name: "control: vocabularies"
    description: Declaring vocabularies and adding values to them. Control plane.
  - name: "control: views"
    description: Declaring plain views, and creating and dropping the views of a view group. Control plane.
  - name: "control: view groups"
    description: Declaring view groups. Control plane.
  - name: "control: layers"
    description: Registering and dropping annotation layers. Control plane.
  - name: "control: artifacts"
    description: Publishing a layer's artifacts and growing their memberships. Control plane.
  - name: "control: identity"
    description: Principals, passwords, API keys, groups, grants, OIDC providers and sessions. Control plane, `admin`.

paths:
  /session/authorise:
    post:
      tags: [session]
      operationId: authorise
      summary: Mint a session for another principal, with an API key holding `authorise-as` or the operator credential.
      description: |
        The bearer is an API key whose principal holds `authorise-as`, or the operator
        credential. The body names the principal to act as: a local principal by `principal`, or
        an OIDC identity by its `access_token`, which is checked as `POST /v1/login` checks one.
        The session carries that principal's terms and its `read` and `write`, and never its
        `admin`, `authorise-as`, `read-all` or `write-all`, so an integrator's key cannot read the
        whole corpus through a target that may. The target must hold `read`.

        With the operator credential, the body may instead name `terms`: the session holds those
        terms and `read`, for no principal of the catalogue, and no catalogue change ends it. A
        local operator reads as any set of terms this way without setting up principals. Or it
        may send `read_all: true` for a session of the superuser itself, which holds `read` and
        `read-all`: its authorised set is every item, deletions and suppressions still apply to
        it, and it satisfies every view's, layer's and artifact's label.

        The session ends at the earliest of `token_max_lifetime` after the request, the expiry of
        the key that minted it, and the target access token's `exp`. It also ends when a catalogue
        change affects the target, the minting principal or the minting key: a grant or
        membership changed, a principal disabled or deleted, a password set or cleared, a key
        revoked, or the target's provider or a group it maps to changed.

        Authorising builds nothing and takes no compute admission: the session's first read builds
        its authorised set, under that read's admission. It is `429 backpressure` only when the
        catalogue changes each time the session is resolved or about to be registered, three
        times running.
      security:
        - authoriseAsKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AuthoriseRequest"
      responses:
        "200":
          description: A session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthoriseResponse"
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`principal` names no enabled local principal."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The body names other than exactly one of `principal`, `access_token`, `terms` and `read_all`, or the access token was not accepted."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /session/revoke:
    post:
      tags: [session]
      operationId: revoke
      summary: End a session this principal minted, by its `token_id`, which is not the token itself.
      description: |
        Revocation names the session by `token_id`, so the token is sent once, when it is minted,
        and never again. Only a session minted by the caller's own principal is ended, or any
        session with the operator credential. A `token_id` that names no such live session is
        also answered `204`, since any other answer would let a caller enumerate sessions.
      security:
        - authoriseAsKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RevokeRequest"
      responses:
        "204":
          description: Revoked, or never live. No body.
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          $ref: "#/components/responses/Contract"

  /healthz:
    get:
      tags: [probes]
      operationId: healthz
      summary: Liveness. Unauthenticated, no body.
      security: []
      responses:
        "200":
          description: The process is up.

  /readyz:
    get:
      tags: [probes]
      operationId: readyz
      summary: Readiness. Unauthenticated, no body.
      description: |
        `200` when the write executor is running and no partition is serving a stepped-down
        manifest, and `503` otherwise.
      security: []
      responses:
        "200":
          description: Ready to serve.
        "503":
          description: Not ready. No body and no `Retry-After`, because a figure that varied with the write executor's state would tell an unauthenticated caller about it.

  /v1/basemaps/world:
    get:
      tags: [basemaps]
      operationId: basemapWorld
      summary: The built-in world outline, the same bytes for every principal.
      description: |
        Land, coastlines and country borders from Natural Earth 1:50m, which is in the public
        domain ("Made with Natural Earth. Free vector and raster map data @ naturalearthdata.com.").
        The body is compiled into the server, says nothing about the corpus or the node, and is
        the same for every session. Not subject to compute admission.

        **The body is not JSON.** All integers are little-endian, with no padding:

        - 4 bytes, `MBW1`. A change to the format changes the digit.
        - Land: a `u32` polygon count; per polygon a `u16` ring count, the outer ring first and
          then its holes; per ring a `u32` point count and then the points.
        - Coastlines, then borders: a `u32` line count; per line a `u32` point count and then the
          points.
        - A point is `i16 x, i16 y`: longitude is `x * 180 / 32767` and latitude
          `y * 90 / 32767`.
      security:
        - sessionToken: []
      parameters:
        - name: If-None-Match
          in: header
          required: false
          description: An `etag` this route sent. A match is answered `304` with no body; the token is checked first.
          schema:
            type: string
      responses:
        "200":
          description: The outline.
          headers:
            etag:
              description: A strong tag derived from the body, quoted. It changes only when the body does.
              required: true
              schema:
                type: string
            cache-control:
              description: "`public, max-age=31536000, immutable`."
              required: true
              schema:
                type: string
                const: "public, max-age=31536000, immutable"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: The `MBW1` outline.
        "304":
          description: "`If-None-Match` named this body's `etag`, or was `*`. No body; `etag` and `cache-control` as on the `200`."
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"

  /v1/login:
    post:
      tags: [viewer]
      operationId: login
      summary: Mint a session for the principal a password, an API key or an OIDC access token authenticates.
      description: |
        The credential travels in the body, so the route takes no bearer. A password is checked
        with argon2id, and failed attempts are limited per name presented: past the limit an
        attempt is refused as a wrong password is. An API key authenticates its principal with
        the key's own permissions where it has them. An access token is checked against its
        provider's published keys, then issuer, audience, `exp` and `nbf`, and its claims are
        mapped to terms and groups by the provider's claim rules and role mappings.

        The principal must hold `read`. The session carries its terms and its `read`, `write`,
        `read-all` and `write-all`. A session holding `read-all` has every item as its authorised
        set, with deletions and suppressions applied, and satisfies every view's, layer's and
        artifact's label. It ends at the earliest of `token_max_lifetime` after the request, the key's expiry and the
        access token's `exp`, and when a catalogue change affects the principal, the key or the
        provider.

        An access token whose `typ` header is present and is not `JWT`, `at+jwt` or
        `application/at+jwt` is refused, so a logout token or another typed JWT is not accepted
        as an access token. An OpenID Connect ID token usually carries `typ: JWT`, so the audience
        check is what refuses it: its `aud` is the client's id, where the provider's `audience`
        names the API. A token that more than one provider with its issuer accepts is refused.
        When a refetch of a provider's keys fails, the keys held are used until they are 24 hours
        old, and the provider's tokens are refused after that until a fetch succeeds.

        Every reason a credential is not accepted answers the same `401`, so the answer does not
        show whether a principal exists. Password checks have their own admission limit, about one
        per core with as many waiting; past it the request is answered `429` with `Retry-After`,
        whether or not the name exists. A login takes no compute admission. It is also
        `429 backpressure` when the catalogue changes each time the session is resolved or about
        to be registered, three times running.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
            examples:
              password:
                summary: A local principal's name and password
                value: {"password": {"principal": "ada", "password": "correct horse battery staple"}}
              api-key:
                summary: An API key
                description: "The whole key, as `POST /control/principals/{name}/keys` issued it, once."
                x-mosaica-fill:
                  body:
                    /api_key: {operation: createKey, path: {name: ada}, body: {}, read: {pointer: /key}, like: {type: string, pattern: "^msk_[0-9a-f]{12}_[0-9a-f]{64}$"}}
                value: {"api_key": "msk_3f9c20d1a7b4_6e1d8a0c4b9f27e35d10a6c8f4b2e97d0c3a5f81b6e4d29c7a0f3e85b1d6c42a"}
      responses:
        "200":
          description: A session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
              examples:
                password:
                  summary: "A session for `ada`"
                  x-mosaica-vary: [/token, /expires_at]
                  value:
                    {
                      "expires_at": 1792877942,
                      "token": "4fe038881ffeb54bd1b1e6088080bd3b531de2f2056f961b694ead91cc36b07a"
                    }
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          $ref: "#/components/responses/Contract"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/logout:
    post:
      tags: [viewer]
      operationId: logout
      summary: End the session of the token sent. No body is read.
      security:
        - sessionToken: []
      responses:
        "204":
          description: The session has ended. No body.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"

  /v1/meta:
    get:
      tags: [viewer]
      operationId: meta
      summary: The deployment's shape as this principal may know it.
      description: |
        `views`, `groups`, `scoped_scalars`, the scoped entries of `filter_operands`, and `layers`
        are filtered per principal: a view, group, family or layer this principal may not reach is
        absent, as a name nobody declared is. Everything else is the same for every principal. A
        client does not reuse this document across principals.

        Not subject to compute admission.
      security:
        - sessionToken: []
      responses:
        "200":
          description: The document. `declared_scalars` is the whole column schema; `filter_operands` is the subset a request may filter on, with each column's operators; `selection` carries the sampling parameters a client needs to read a count of drawn points as a density.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Meta"
              examples:
                arxiv:
                  summary: "The arXiv example dataset, as a session holding `read-all` sees it"
                  description: "Two views, six declared fields and three layers: the Toponymy clustering, the names of its topics, and the arXiv taxonomy."
                  value:
                    {
                      "api_version": 1,
                      "bundle_format": 52,
                      "declared_scalars": [
                        {
                          "analyser": null,
                          "arrow_type": "u64",
                          "category": null,
                          "homes": [
                            "record"
                          ],
                          "index": false,
                          "name": "id",
                          "render": false,
                          "unique": true
                        },
                        {
                          "analyser": null,
                          "arrow_type": "u8",
                          "category": {
                            "kind": "declared",
                            "visibility": "public",
                            "vocabulary": "archive"
                          },
                          "homes": [
                            "rendered",
                            "value_column"
                          ],
                          "index": true,
                          "name": "archive",
                          "render": true,
                          "unique": false
                        },
                        {
                          "analyser": null,
                          "arrow_type": "u16",
                          "category": {
                            "kind": "declared",
                            "visibility": "public",
                            "vocabulary": "primary_category"
                          },
                          "homes": [
                            "rendered",
                            "value_column"
                          ],
                          "index": true,
                          "name": "primary_category",
                          "render": true,
                          "unique": false
                        },
                        {
                          "analyser": null,
                          "arrow_type": "timestamp_us",
                          "category": null,
                          "homes": [
                            "rendered"
                          ],
                          "index": false,
                          "name": "submitted_at",
                          "render": true,
                          "unique": false
                        },
                        {
                          "analyser": "unicode/icu4x-2.2/p1",
                          "arrow_type": "text",
                          "category": null,
                          "homes": [
                            "record"
                          ],
                          "index": true,
                          "name": "title",
                          "render": false,
                          "unique": false
                        },
                        {
                          "analyser": null,
                          "arrow_type": "keyword",
                          "category": null,
                          "homes": [
                            "value_column"
                          ],
                          "index": true,
                          "name": "arxiv_id",
                          "render": false,
                          "unique": false
                        }
                      ],
                      "filter_operands": [
                        {
                          "column": "id",
                          "family": "numeric",
                          "operands": [
                            "eq",
                            "in"
                          ]
                        },
                        {
                          "column": "archive",
                          "family": "category",
                          "operands": [
                            "eq",
                            "in"
                          ]
                        },
                        {
                          "column": "primary_category",
                          "family": "category",
                          "operands": [
                            "eq",
                            "in"
                          ]
                        },
                        {
                          "column": "submitted_at",
                          "family": "numeric",
                          "operands": [
                            "eq",
                            "in",
                            "range"
                          ]
                        },
                        {
                          "column": "title",
                          "family": "text",
                          "operands": [
                            "match",
                            "phrase"
                          ]
                        },
                        {
                          "column": "arxiv_id",
                          "family": "keyword",
                          "operands": [
                            "eq",
                            "in",
                            "prefix",
                            "contains"
                          ]
                        }
                      ],
                      "groups": [],
                      "layers": [
                        {
                          "computed_content": [
                            "centroid",
                            "box",
                            "hull"
                          ],
                          "depends_on": [],
                          "hierarchy": {
                            "kind": "tiered",
                            "prune_children": false
                          },
                          "levels": [
                            {
                              "level": 0,
                              "title": "16 topics, 832+ papers each",
                              "zoom": null
                            },
                            {
                              "level": 1,
                              "title": "46 topics, 263+ papers each",
                              "zoom": null
                            },
                            {
                              "level": 2,
                              "title": "161 topics, 78+ papers each",
                              "zoom": null
                            },
                            {
                              "level": 3,
                              "title": "574 topics, 20+ papers each",
                              "zoom": null
                            }
                          ],
                          "membership": "enumerated",
                          "name": "clusters/toponymy",
                          "shape": "derived",
                          "supplied_content": [],
                          "title": "Toponymy clusters",
                          "version": 2,
                          "views": [
                            "topics",
                            "topics-wide"
                          ]
                        },
                        {
                          "computed_content": [
                            "centroid",
                            "box"
                          ],
                          "depends_on": [],
                          "hierarchy": {
                            "kind": "tiered",
                            "prune_children": false
                          },
                          "levels": [
                            {
                              "level": 0,
                              "title": "archive",
                              "zoom": null
                            },
                            {
                              "level": 1,
                              "title": "subject class",
                              "zoom": null
                            }
                          ],
                          "membership": "enumerated",
                          "name": "taxonomy/arxiv",
                          "shape": null,
                          "supplied_content": [],
                          "title": "arXiv classification",
                          "version": 1,
                          "views": [
                            "topics",
                            "topics-wide"
                          ]
                        },
                        {
                          "computed_content": [],
                          "depends_on": [
                            "clusters/toponymy"
                          ],
                          "hierarchy": {
                            "kind": "flat",
                            "prune_children": false
                          },
                          "levels": [],
                          "membership": "enumerated",
                          "name": "topics/toponymy",
                          "shape": null,
                          "supplied_content": [
                            "text"
                          ],
                          "title": "Toponymy topics",
                          "version": 3,
                          "views": [
                            "topics",
                            "topics-wide"
                          ]
                        }
                      ],
                      "scoped_scalars": [],
                      "selection": {
                        "k_max_marks": 500,
                        "k_min": 2,
                        "max_aggregate_bins": 1000,
                        "max_aggregate_cells": 1048576,
                        "max_aggregate_groupings": 16,
                        "max_aggregate_named": 1000,
                        "max_aggregate_top": 1000,
                        "max_artifacts_per_tile": 1000,
                        "max_browse_rows": 200,
                        "max_category_values": 1000,
                        "max_k": 1000,
                        "max_page_bytes": 67108864,
                        "max_page_rows": 100000,
                        "max_region_cells": 262144,
                        "max_region_vertices": 10000,
                        "max_shape_vertices": 1000000,
                        "max_suggest_set_entities": 10000000,
                        "max_suggestion_walk": 100000,
                        "max_suggestions": 20,
                        "max_tiles_per_request": 262144,
                        "max_underlay_offset": 4,
                        "theta_target_marks": 16
                      },
                      "views": [
                        {
                          "display_name": "arXiv topic map",
                          "group": null,
                          "id": "topics",
                          "key": null,
                          "metadata": null,
                          "projection": "none",
                          "quantisation": {
                            "x_max": 25.968835859298707,
                            "x_min": -12.610199003219606,
                            "y_max": 23.92222264289856,
                            "y_min": -14.656812219619752
                          },
                          "tile": null,
                          "tile_scheme": null,
                          "world_aspect": null
                        },
                        {
                          "display_name": "arXiv topic map, wide neighbourhoods",
                          "group": null,
                          "id": "topics-wide",
                          "key": null,
                          "metadata": null,
                          "projection": "none",
                          "quantisation": {
                            "x_max": 18.218203592300416,
                            "x_min": -6.582906293869019,
                            "y_max": 17.498645114898682,
                            "y_min": -7.302464771270753
                          },
                          "tile": null,
                          "tile_scheme": null,
                          "world_aspect": null
                        }
                      ]
                    }
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/categories/{column}:
    get:
      tags: [viewer]
      operationId: categories
      summary: What a category column's codes stand for, filtered by the vocabulary's `visibility`.
      description: |
        Two forms under one rule. `?codes=` resolves codes the caller already holds. The bare form
        lists values in ascending key order, resuming after `?after=` and bounded by `?limit=`,
        whose ceiling and default are `selection.max_category_values`. A `limit` above the ceiling
        is clamped, and `limit=0` is `422`. A `derived` vocabulary is filtered to the values this
        principal can see before either form runs, and a page is cut after the filter, so its
        length counts only values the principal may see.

        A code that resolves to nothing is left out and not refused, and an empty `values` is a
        `200`. No such code, a code no key explains, and a value this principal cannot see give
        one answer. This route serves no counts; `/v1/categories/{column}/suggest` serves a count
        beside each value a prefix matches.

        Not subject to compute admission. A `public` vocabulary is a walk of an in-memory map. A
        `derived` one composes this principal's visible set and probes one memory-mapped index
        entry per value it walks, over the whole vocabulary.
      security:
        - sessionToken: []
      parameters:
        - name: column
          in: path
          required: true
          description: |
            Any column in `/v1/meta`'s `declared_scalars` that carries a `category` block,
            including one with neither `render` nor `index`, which no filter can name. Or a
            group-scoped category family from `filter_operands`, which has one column per view of
            its group and so is addressed with a view: pin one here as `<column>@<key>`, or name
            the request's own view in `view`. A group-scoped column named with neither is `422`
            naming the group, and a pin naming no view of the group is the `404` an unknown view
            gets. A pin on an entity-scoped column is `422`, since it has one value set for the
            corpus.
          schema:
            type: string
            pattern: "^[A-Za-z0-9_-]+(@[A-Za-z0-9_.:-]+)?$"
          examples:
            list:
              value: archive
            next-page:
              value: archive
            codes:
              value: primary_category
        - name: view
          in: query
          required: false
          description: |
            The request's own view, for a group-scoped category. It is resolved through the
            session's reachable views as a viewer request's `view` is, and before the column, so a
            `view` naming nothing, or a view this principal may not reach, is the unknown-view
            `404` even where the column is entity-scoped. The scope decides only whether the view
            chooses a column.
          schema:
            type: string
        - name: codes
          in: query
          required: false
          description: Comma-separated `u32` codes to resolve. Present, the route resolves them; absent, it lists values. `next` is always `null` in this form.
          schema:
            type: string
          examples:
            codes:
              summary: "What three codes of `primary_category` stand for, as a client reads them from a points frame"
              value: "33,50,164"
        - name: after
          in: query
          required: false
          description: Resume listing after this value key, the previous page's `next`.
          schema:
            type: string
          examples:
            next-page:
              summary: The next five archives, after the page that ended at `astro-ph`
              description: "`after` is the `next` of the answer to `list`."
              x-mosaica-fill: {operation: categories, example: list, read: {pointer: /next}, like: {type: string}, same: true}
              value: astro-ph
        - name: limit
          in: query
          required: false
          description: Page size. Clamped to `selection.max_category_values`, its default; `0` is `422`.
          schema:
            type: integer
            minimum: 1
          examples:
            list:
              summary: The first five archives, in key order
              value: 5
            next-page:
              value: 5
      responses:
        "200":
          description: The values this principal may be told about, in key order for the bare form and in request order for `?codes=`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CategoriesResponse"
              examples:
                list:
                  summary: "The first five archives, and the key to resume after"
                  description: "`GET /v1/categories/archive?limit=5`."
                  value:
                    {
                      "column": "archive",
                      "next": "astro-ph",
                      "values": [
                        {
                          "code": 1,
                          "key": "acc-phys",
                          "title": "Accelerator Physics"
                        },
                        {
                          "code": 2,
                          "key": "adap-org",
                          "title": "Adaptation, Noise, and Self-Organizing Systems"
                        },
                        {
                          "code": 3,
                          "key": "alg-geom",
                          "title": "Algebraic Geometry"
                        },
                        {
                          "code": 4,
                          "key": "ao-sci",
                          "title": "Atmospheric-Oceanic Sciences"
                        },
                        {
                          "code": 5,
                          "key": "astro-ph",
                          "title": "Astrophysics"
                        }
                      ]
                    }
                codes:
                  summary: "Three codes of `primary_category` resolved"
                  description: "`GET /v1/categories/primary_category?codes=33,50,164`."
                  value:
                    {
                      "column": "primary_category",
                      "next": null,
                      "values": [
                        {
                          "code": 33,
                          "key": "cs.CL",
                          "title": "Computation and Language"
                        },
                        {
                          "code": 50,
                          "key": "cs.LG",
                          "title": "Machine Learning"
                        },
                        {
                          "code": 164,
                          "key": "quant-ph",
                          "title": "Quantum Physics"
                        }
                      ]
                    }
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: No such column, a column that is not a category, or a column whose vocabulary is missing. The three give one answer, so this route says no more about the schema than `/v1/meta` does.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/Contract"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/categories/{column}/suggest:
    get:
      tags: [viewer]
      operationId: suggestCategoryValues
      summary: Typeahead over a category vocabulary, filtered by the vocabulary's `visibility`.
      description: |
        Returns the values whose key, title, or a word within either, starts with `q`.

        **Matching.** `q` and every indexed string are normalised alike: Unicode NFKC, then
        default case folding, then whitespace collapsed and trimmed. Each value contributes its
        whole folded key, its whole folded title where it has one, and the folded title, or the
        key where there is no title, from each word boundary after the first. A value matches when
        `q` is a prefix of one of these entries. There is no infix match, no fuzzy match and no
        stemming.

        **Order.** Ascending by the matched entry's folded string, then by entry kind (key, then
        title, then word start), then by key. The order is fixed when the index is built and is the
        same for every principal; it does not depend on frequency, recency or the count. A value
        appears once, at its first matching entry.

        **Visibility.** As on `/v1/categories/{column}`: a `public` vocabulary is suggested as
        declared, and a `derived` one offers a value only where at least one item carrying it is
        visible to this principal. The column and `view` are resolved as that route resolves
        them, `@key` pins and the group's `404` included.

        **No cursor.** A typeahead does not page; the user types another character. `more` says
        the walk stopped early: the page filled, or `selection.max_suggestion_walk` values were
        examined. Where the server answers from the session's set of visible values, which it
        builds for a principal whose visible set holds at most
        `selection.max_suggest_set_entities` items, `more` means only that the page filled.

        **Counts under a filter.** `POST` to this path takes the same fields as a JSON body, with a
        filter. This form carries none.

        A request with `view` and `counts=true` opens that view as `/v1/viewport` does, which may
        build this session's view of the map, so it is subject to compute admission. Any other
        request is not. One suggest runs per session at a time: a second while one is running is
        `429 backpressure`, refused before any work, with `Retry-After`. A request with `view` and
        `counts=true` is also `429 backpressure` when the compute queue is full, when no permit
        frees within `admission_timeout_ms`, and when another request is building the view of the
        map this session reads and does not finish within `single_flight_wait_ms`.
      security:
        - sessionToken: []
      parameters:
        - name: column
          in: path
          required: true
          description: "As on `/v1/categories/{column}`: any column carrying a `category` block, or a group-scoped family pinned as `<column>@<key>`."
          schema:
            type: string
            pattern: "^[A-Za-z0-9_-]+(@[A-Za-z0-9_.:-]+)?$"
          examples:
            prefix:
              value: primary_category
            counts:
              value: primary_category
        - name: q
          in: query
          required: true
          description: The text typed, echoed in the response as received, not folded, so a client can match a response to its request. Empty matches every value and returns the first `limit` visible ones in index order. Over 256 bytes is `422`.
          schema:
            type: string
            maxLength: 256
          examples:
            prefix:
              summary: The first five subject classes starting with "cond-mat"
              value: cond-mat
            counts:
              summary: The same, with how many papers in the `topics` view carry each
              value: cond-mat
        - name: limit
          in: query
          required: false
          description: Page size. Clamped to `selection.max_suggestions`, its default; `0` is `422`.
          schema:
            type: integer
            minimum: 1
          examples:
            prefix:
              value: 5
            counts:
              value: 5
        - name: counts
          in: query
          required: false
          description: "`true` serves `count` beside each value: how many items carrying it this principal may see, exact, computed per request. Without `view`, an item ingested and not yet flushed counts. With `view`, only the items `/v1/viewport` would show this session in that view count: an item holds a row there once it is flushed and, for a session already served that view, the refresh after the flush reaches this session's grant (`x-mosaica-pin` describes that window), and a value this principal can see only in other views counts `0`. Without `counts`, `count` is absent. It does not affect the order."
          schema:
            type: boolean
          examples:
            counts:
              value: true
        - name: view
          in: query
          required: false
          description: The request's own view. It resolves a group-scoped category as on `/v1/categories/{column}`, and with `counts=true` it limits each count to the items in this view. It does not change which values are offered. An unknown view, or one this principal may not reach, is `404`.
          schema:
            type: string
          examples:
            counts:
              value: topics
      responses:
        "200":
          description: At most `limit` values this principal may be told about, in matched-entry order.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuggestResponse"
              examples:
                counts:
                  summary: "Five subject classes starting with \"cond-mat\", with their counts"
                  description: "`GET /v1/categories/primary_category/suggest?q=cond-mat&limit=5&counts=true&view=topics`. `match` says where `q` matched in the served key; `more` that other values match."
                  value:
                    {
                      "column": "primary_category",
                      "more": true,
                      "q": "cond-mat",
                      "total": 50000,
                      "values": [
                        {
                          "code": 18,
                          "count": 209,
                          "key": "cond-mat",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Condensed Matter"
                        },
                        {
                          "code": 19,
                          "count": 230,
                          "key": "cond-mat.dis-nn",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Disordered Systems and Neural Networks"
                        },
                        {
                          "code": 20,
                          "count": 1212,
                          "key": "cond-mat.mes-hall",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Mesoscale and Nanoscale Physics"
                        },
                        {
                          "code": 21,
                          "count": 1103,
                          "key": "cond-mat.mtrl-sci",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Materials Science"
                        },
                        {
                          "code": 22,
                          "count": 126,
                          "key": "cond-mat.other",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Other Condensed Matter"
                        }
                      ]
                    }
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: No such column, a column that is not a category, or a column whose vocabulary is missing, as on `/v1/categories/{column}`; and a group-scoped family whose group this principal may not reach.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/Contract"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

    post:
      tags: [viewer]
      operationId: suggestCategoryValuesFiltered
      summary: Typeahead over a category vocabulary, with counts taken under a filter.
      description: |
        The `GET` form's page, with its fields in a JSON body and one more, `filters`: the filter
        expression `/v1/viewport` and `/v1/items` take, `region` and `member_of` leaves included,
        evaluated in `view`. A filter needs `view`; `filters` without it is `422`. The body is JSON
        because a `region` leaf may hold up to `selection.max_region_vertices` vertices, too many
        for a query string, and because every other route taking a filter reads it from a body.

        **The filter changes the counts and nothing else.** Which values are offered, and their
        order, are decided as on the `GET` form: by the vocabulary's `visibility`, over every item
        this principal may see. With `counts=true`, a value's `count` is the number of items in
        `view` that carry it, that this principal may see, and that pass the filter. A value the
        filter excludes, including one excluded by a filter on this same column, is offered with
        `count` `0`. Without `counts=true` the filter is parsed and refused as on `/v1/viewport`
        and changes nothing.

        Matching, order, visibility, `view` and `more` are as on the `GET` form.

        A request with `view` and `counts=true`, and so every request counting under `filters`, is
        subject to compute admission, as on the `GET` form. Any other request is not. Every request
        is the session's one running suggest.

        **`429 backpressure`** has these causes, each with `Retry-After` and told apart by `detail`:
        another suggest for this session is running, refused before any work; the compute queue is
        full, or no permit freed within `admission_timeout_ms`; another request is building this
        session's view of the map and did not finish within `single_flight_wait_ms`. The last two
        arise only for a request with `view` and `counts=true`.
      security:
        - sessionToken: []
      parameters:
        - name: column
          in: path
          required: true
          description: As on the `GET` form.
          schema:
            type: string
            pattern: "^[A-Za-z0-9_-]+(@[A-Za-z0-9_.:-]+)?$"
          examples:
            prefix:
              value: primary_category
            counts-filtered:
              value: primary_category
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SuggestRequest"
            examples:
              prefix:
                summary: The first five subject classes starting with "cond-mat"
                description: The `GET` form's request, as a body.
                value: {"q": "cond-mat", "limit": 5}
              counts-filtered:
                summary: The same, counting only the papers submitted since 2020
                description: "Each value's `count` is the papers in `topics` carrying it that were submitted on or after 1 January 2020, and `total` is all such papers. The values offered, and their order, are the ones the unfiltered request gets."
                value: {"q": "cond-mat", "limit": 5, "counts": true, "view": "topics", "filters": {"submitted_at": {"range": {"gte": 1577836800000000}}}}
      responses:
        "200":
          description: At most `limit` values this principal may be told about, in matched-entry order.
          headers:
            x-mosaica-region:
              description: "Present where counts were taken and `filters` carries a `region` leaf, as on `/v1/viewport`: `exact`, or `cover; depth=<d>` where a shape's edge crossed more than `max_region_cells` cells and the counts are exact for a larger shape covering it."
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuggestResponse"
              examples:
                counts-filtered:
                  summary: "The same five values, counted over the papers submitted since 2020"
                  description: "The answer to the `counts-filtered` request. `cond-mat` is still offered, with `count` 0: none of its papers was submitted since 2020."
                  value:
                    {
                      "column": "primary_category",
                      "more": true,
                      "q": "cond-mat",
                      "total": 17881,
                      "values": [
                        {
                          "code": 18,
                          "count": 0,
                          "key": "cond-mat",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Condensed Matter"
                        },
                        {
                          "code": 19,
                          "count": 42,
                          "key": "cond-mat.dis-nn",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Disordered Systems and Neural Networks"
                        },
                        {
                          "code": 20,
                          "count": 307,
                          "key": "cond-mat.mes-hall",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Mesoscale and Nanoscale Physics"
                        },
                        {
                          "code": 21,
                          "count": 370,
                          "key": "cond-mat.mtrl-sci",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Materials Science"
                        },
                        {
                          "code": 22,
                          "count": 10,
                          "key": "cond-mat.other",
                          "match": {
                            "field": "key",
                            "len": 8,
                            "start": 0
                          },
                          "title": "Other Condensed Matter"
                        }
                      ]
                    }
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: As on the `GET` form.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: "`422 contract`: as on the `GET` form, and a malformed `filters`, or `filters` without `view`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/viewport:
    post:
      tags: [viewer]
      operationId: viewport
      summary: Masked counts per tile and a sample of points, over a region at a depth.
      description: |
        **The body is not JSON.** It is a sequence of frames, each a `u8` kind, a `u32`
        little-endian payload length and the payload, which is a complete Arrow IPC stream, or JSON
        for the trailer. The kinds:

        - `1` tiles: exactly one, first.
        - `2` sub-cells: exactly one when `underlay_offset` is present and not zero.
        - `3` points: zero or more, each holding whole tiles.
        - `4` trailer: exactly one, last.

        A body without a trailer is incomplete, whatever the transport said. An unknown kind is a
        decoding error. [Wire framing](../openapi/README.md) gives each frame's columns and a
        worked decode in Python and in JavaScript.

        **Region and sample.** The request names exactly one of `bbox` and `tiles`. `k` is the
        most points a tile returns: it defaults to `selection.k_max_marks` and is capped by
        `selection.max_k`. A tile may return fewer than `k` while more are visible, which is how
        the client reads density. `k = 0` asks for counts only: the tiles frame as at any `k`, and
        no points frame. A client keeps `k` the same or raises it as it zooms in; the server cannot
        check this.

        **Layers.** Each layer `layers` names that serves an artifact holding a served point adds
        a `membership:<layer>` column to the points frames: the `mosaica_id` of the deepest
        artifact of the layer the point belongs to that this principal is served, or null. The
        artifacts themselves are served by `POST /v1/artifacts/viewport`. A nested or `dag`
        layer is cut to `artifact_budget`, met by tagging with ancestors in place of their
        descendants.

        **Highlight.** A request carrying `highlight` gains two columns and loses none:
        `highlighted` fifth on the tiles frame (`uint64`, present on every response and equal to
        `matched` where there is no highlight), and `highlighted` on the points frame (`bool`,
        after the render columns and before the membership columns, present only with a
        highlight). The rows served are the same with and without a highlight.

        Subject to compute admission.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ViewportRequest"
            examples:
              whole-map:
                summary: The whole map at zoom 2
                description: "Every tile of the `topics` view at zoom 2 that the box touches. `k` caps the papers drawn in a tile at 200; the deployment's density rules decide how many each tile draws below that."
                value: {"view": "topics", "zoom": 2, "bbox": [-15, -15, 25, 25], "k": 200}
              filtered:
                summary: Computer science papers since 2020 with "neural" in the title
                description: "A filter combining a category, a range and a text match. `submitted_at` is in microseconds since the Unix epoch, so the range starts on 1 January 2020. Each tile's `matched` count, and the points drawn, are the papers that pass all three."
                value: {"view": "topics", "zoom": 4, "bbox": [5, 0, 15, 10], "filters": {"all_of": [{"archive": {"eq": "cs"}}, {"submitted_at": {"range": {"gte": 1577836800000000}}}, {"title": {"match": "neural"}}]}}
              highlighted:
                summary: High-energy physics, with the papers about black holes lit
                description: "`filters` narrows the map to three archives. `highlight` marks, among the papers drawn, those whose title holds the phrase \"black holes\": each tile's `highlighted` count is at most its `matched`, and each point carries a `highlighted` bit."
                value: {"view": "topics", "zoom": 3, "bbox": [-15, -15, 25, 25], "filters": {"archive": {"in": ["hep-th", "hep-ph", "gr-qc"]}}, "highlight": {"title": {"phrase": "black holes"}}}
              tagged:
                summary: Points tagged with their Toponymy topic
                description: "Each point carries a `membership:clusters/toponymy` column holding the `mosaica_id` of its topic at level 1, the 46 topics, which `POST /v1/artifacts/viewport` serves. `point_rows` asks for one render column beside the position, the one a client colours by."
                value: {"view": "topics", "zoom": 3, "bbox": [-15, -15, 25, 25], "layers": ["clusters/toponymy"], "levels": [1], "point_rows": ["primary_category"]}
              counts-only:
                summary: Counts per tile, no points
                description: "`k: 0` serves the tiles frame alone, with the counts of every tile, here at zoom 5 across the second layout."
                value: {"view": "topics-wide", "zoom": 5, "bbox": [-6, -8, 18, 18], "k": 0}
      responses:
        "200":
          description: The framed body. Its headers carry the coordinates a client caches and revalidates by.
          headers:
            etag:
              description: The content key, quoted. It moves with the principal's identity, the served watermark, the overlay's version and the process, and is what a held band of tiles may be stored under. The server does not read `If-Match`, so a request carrying one is answered in full.
              required: true
              schema:
                type: string
            x-mosaica-identity-key:
              description: The identity key, derived from the credential, its mask fragment in each shard of the view, and the view. A fragment's identity is keyed by its shard's identity, which is derived from the bundle's, so a rebuilt bundle, whose `mosaica_id`s are all new, gives a new identity key. A held band may be drawn only under the same key, so a client partitions its cache by it.
              required: true
              schema:
                type: string
            x-mosaica-pin:
              description: The generation this response was computed from, as JSON `{"prefix", "segments_version"}`. It names the generation of this session's visible set, one generation for every shard the response reads. A session's first read in a view is answered at the live generation. After that, until the refresh that follows a flush reaches this session's grant, it is the previous generation, and the flush's new rows are not yet visible; deletions, suppressions, segments and the overlay are always current. Send it back as `pin`; its only effect is `x-mosaica-stale`.
              required: true
              schema:
                type: string
            x-mosaica-stale:
              description: "`1` where the request's `pin` differs from this response's `x-mosaica-pin`, and `0` otherwise. It moves at every flush, merge and compaction, whether or not anything this principal can see changed, and after a flush it moves when the refresh reaches this session's grant. Advisory."
              required: true
              schema:
                type: string
                enum: ["0", "1"]
            x-mosaica-region:
              description: "Present exactly when the request's `filters` carry a `region` leaf. `exact` where every region is answered exactly against each point's stored position; `cover; depth=<d>` where a shape's edge crossed more than `max_region_cells` cells, and the answer is exact for a larger shape covering it, taken at depth `d`. It depends on the shape and the grid alone, not on the rows. With several region leaves it is the coarsest. A leaf naming an artifact is always exact."
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
            x-mosaica-server-us:
              description: Server time from admission until the first frames were ready, in microseconds. The whole stream's time is the trailer's `stream_us`.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
            x-mosaica-admission-us:
              description: Time spent waiting for compute admission, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: Framed Arrow IPC streams and one JSON trailer.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: Unknown `view`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The request needs to change:

            - `zoom` above 16;
            - neither or both of `bbox` and `tiles`, a `bbox` that is not finite or has a maximum below its minimum, or a tile prefix with bits above `zoom`;
            - an `underlay_offset` beyond `selection.max_underlay_offset`, past depth 16, or over the sub-cell budget, which is refused and not clamped;
            - an unknown filter column, or an operator outside its column's family (an unknown value is an empty operand and is not refused);
            - a `layers`, `levels` or `point_rows` value outside its schema;
            - a `point_rows` list naming a column that is not one of the view's render columns.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/items:
    post:
      tags: [viewer]
      operationId: items
      summary: Every item this principal may see in one view that matches a filter, page by page, with the fields the caller names.
      description: |
        **The body is not JSON.** It is framed as the viewport's is, with these kinds:

        - `6` head: JSON `ItemsHead`, exactly one, first.
        - `7` records: one Arrow IPC stream holding one batch of up to `page_rows` rows. One or
          more, each followed by a page end; none only in a response cancelled before its first
          page, by the stream deadline or because the client has gone.
        - `8` page end: JSON `PageEnd`, carrying the cursor to resume after that page.
        - `4` trailer: JSON `RecordsTrailer`, exactly one, last.

        A body without a trailer is incomplete. A client whose connection drops resumes from the
        last page end it received, and discards a records frame that no page end follows. An
        unknown kind is a decoding error. [Wire framing](../openapi/README.md) walks a decode in
        Python and in JavaScript.

        **Reading a whole result.** A response carries at most `pages` pages and runs within the
        deployment's byte and time budgets. Pass the trailer's `next` back as `cursor`, unchanged,
        until it is null. The server keeps nothing between requests: every page is built from the
        latest data with the visible set composed again, so a deletion or suppression accepted
        during a read applies from the next page. A read returns each row at most once. A row
        inserted behind the cursor during a read is not returned, and one inserted ahead of it is.
        `fields`, `position`, `labels`, `filters`, `keep_unmatched` and `page_rows` may change
        between the requests of one read, and the page schema changes with them.

        **Columns.** `mosaica_id` (`uint64`) first, then the named fields in the order named, then
        the position under `position`, then the labels under `labels`, then `mosaica:matched`
        (`bool`) under `keep_unmatched`. Every named field is present whether or not an item
        carries a value. A number or boolean arrives as its declared type, `timestamp_us` as
        `timestamp[us, UTC]`, a keyword or text field as `utf8`, and a category as
        `dictionary<int32, utf8>` holding its keys, each frame's dictionary holding only the keys
        its rows carry. An absent value is null. The position is two `float64` columns,
        `mosaica:x` and `mosaica:y`: the centre of the step of the view's 32-bit grid the item is
        stored in, converted back through the view's projection and frame, so degrees on a
        geographic view, and within half a step of the position the item was given. The labels are
        `list<utf8>` as `mosaica:labels`: the clauses of the item's labels this principal
        satisfies, as the item card serves them. A rendered field is read from `view`, where the
        item card reads the first view the principal can reach, so the two can differ. An item
        joined into `view` before its own ingest is flushed has null record fields until that
        flush.

        **Group-scoped fields** resolve as a filter leaf on them does: under a view of the field's
        group, or of a group sharing its views, they read that view's column; elsewhere they are
        pinned as `<field>@<key>`.

        **Order.** `map` returns rows by map cell in `view`, then by `mosaica_id`. `stored` returns
        them shard by shard, in shard number order, and within a shard in the order its record
        store holds items, which groups a shard's items by their full set of access labels. The
        two orders return the same rows. With neither `order` nor `cursor`, the
        server takes `stored` when any named field's `homes` in `/v1/meta` is `["record"]`, and
        `map` otherwise; that choice may change. Reading a field held only in the record store in
        map order was measured 1.3 to 1.8 times slower than in stored order at 100,000-row pages,
        and 8 to 17 times slower at 1,000-row pages.

        **Pages.** A page ends at `page_rows` rows, or before the row that would take its Arrow
        bytes past `selection.max_page_bytes`; a single larger row is sent alone. A page holds at
        least one row while any remain, with one exception: a response that would otherwise carry
        no page carries one page of no rows, so that every response gives the read's columns and
        their types. That page's end carries the trailer's `next`, and `ended_by` `end` where
        `next` is null and `time` otherwise, and the trailer counts it as a page. When the
        response's time budget runs out, or the stream deadline cancels it, a page holding rows is
        sent short and the trailer follows. Every response moves the cursor on, including one
        that found no row under a sparse filter. A response can run past its time budget by one
        evaluation of the filter over a stretch of rows and one chunk of a page's scan.

        **Cursor.** Opaque, encrypted and authenticated under a key derived from the bundle's
        identity key, and bound to the route, the view and its incarnation, the order, and the
        credential that received it. A cursor that fails any of these, or was issued by another
        bundle, is one `422` with one detail. A cursor survives flushes, merges, compactions and
        restarts.

        Runs under its own admission limit, `serve.bulk_admission`, so a long read takes no slot
        from the viewport and item routes. A read past the limit is refused at once with `429`.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ItemsRequest"
            examples:
              named-fields:
                summary: The first 100 papers, with four fields each
                description: One page of 100 rows. The trailer's `next` resumes the read.
                value: {"view": "topics", "fields": ["arxiv_id", "title", "primary_category", "submitted_at"], "page_rows": 100, "pages": 1}
              filtered:
                summary: Computation and language papers about translation, with their positions
                description: "Every paper in `cs.CL` whose title matches \"translation\", with its position in `topics` as `mosaica:x` and `mosaica:y`."
                value: {"view": "topics", "fields": ["arxiv_id", "title"], "position": true, "filters": {"all_of": [{"primary_category": {"eq": "cs.CL"}}, {"title": {"match": "translation"}}]}}
              counted:
                summary: How many quantum physics papers there are, and the first 50
                description: "`count` puts `visible` and `matched` in the head: the papers this viewer may see in `topics`, and those in `quant-ph`."
                value: {"view": "topics", "fields": ["arxiv_id", "title"], "filters": {"archive": {"eq": "quant-ph"}}, "count": true, "page_rows": 50, "pages": 1}
              next-page:
                summary: The next page of the first read
                description: "`cursor` is the `next` of the trailer that answered `named-fields`, sent back unchanged by the same session. The fields may change between the requests of one read; the view and the order may not."
                x-mosaica-fill:
                  body:
                    /cursor: {operation: items, example: named-fields, read: {trailer: next}, like: {type: string, pattern: "^[A-Za-z0-9_-]+$"}}
                value: {"view": "topics", "fields": ["arxiv_id", "title", "primary_category", "submitted_at"], "page_rows": 100, "pages": 1, "cursor": "0-NxVGI77sEeTY5GzuaLZkoxm7JubG24OaNE9YpO_Dt28xcgnw1QyiB9nU72cXbSfJxg9Be-YjwrUw"}
      responses:
        "200":
          description: The framed body. The headers are the viewport's, without its cache coordinates.
          headers:
            x-mosaica-identity-key:
              description: The viewport's identity key for this principal and view, equal to the one a viewport over the same view returns. Absent only from a response cancelled before its first page, which is a head and a trailer ending by `deadline`.
              required: false
              schema:
                type: string
            x-mosaica-region:
              description: "Present exactly when the request's `filters` carry a `region` leaf, with the viewport's values: the coarsest answer the head's count and the first page reached."
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
            x-mosaica-server-us:
              description: Server time from admission to the head, in microseconds. The whole response's time is the trailer's `stream_us`.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
            x-mosaica-admission-us:
              description: Time spent being admitted, in microseconds. Near zero, since a bulk read does not wait for a slot.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: A head, one or more records frames each followed by a page end, and a trailer.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: Unknown `view`, or a pin naming no view of its field's group that this principal can reach.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The request needs to change:

            - the viewport's filter refusals;
            - an unknown key, a missing `view` or `fields`, a `position` or `labels` that is not a boolean, or an `order` or `compression` outside its values;
            - an unknown or repeated field;
            - an unpinned group-scoped field outside its group, a pin on a field that is not group-scoped, or a group-scoped `text` field;
            - `page_rows` or `pages` of 0;
            - `count` with a `cursor`;
            - a cursor that does not open for this request, or whose order differs from `order`.

            `page_rows` above `selection.max_page_rows` is capped, not refused.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/items/{mosaica_id}:
    post:
      tags: [viewer]
      operationId: item
      summary: One item's whole record, if this principal may see it.
      description: |
        The record is keyed by declared column name. A category appears as its vocabulary's key,
        and every number as a JSON number. A field the item has no value in is absent from
        `fields`, not `null`.

        `views` names every view the item is in that this principal may reach, each with the
        position that view places it at, and `scoped` carries the group-scoped values by the
        group's key. Both are filtered to the session's reachable views, so an item held only in
        views this principal cannot reach has an empty `views` and no scoped values, and nothing on
        this route names a view the principal cannot reach.

        `404 unknown` is the same status, code, detail and work for an identifier that names
        no item, an artifact's or a layer's among them, and one that names an item this principal
        may not see. Subject to compute admission.
      security:
        - sessionToken: []
      parameters:
        - $ref: "#/components/parameters/MosaicaId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ItemRequest"
            examples:
              plain:
                summary: One paper's whole record
                description: "The `mosaica_id` in the path comes from an earlier answer: a point of `POST /v1/viewport`, or a row of `POST /v1/items`."
                x-mosaica-fill:
                  path:
                    mosaica_id: {operation: items, example: named-fields, read: {column: mosaica_id}, like: {type: string, pattern: "^[0-9]+$"}}
                value: {}
              pinned:
                summary: The same, with the generation the map was drawn from
                description: "`pin` is the `x-mosaica-pin` header of an earlier viewport response, sent back. It is advisory and changes nothing in the answer."
                x-mosaica-fill:
                  path:
                    mosaica_id: {operation: items, example: named-fields, read: {column: mosaica_id}, like: {type: string, pattern: "^[0-9]+$"}}
                  body:
                    /pin: {operation: viewport, example: whole-map, read: {header: x-mosaica-pin}, like: {$ref: "#/components/schemas/Pin"}, same: true}
                value: {"pin": {"prefix": "v00000", "segments_version": 0}}
      responses:
        "200":
          description: The record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ItemResponse"
              examples:
                plain:
                  summary: "One paper's record"
                  description: "Every declared field the paper holds, the label clause that lets this session see it, and its position in each view."
                  value:
                    {
                      "fields": {
                        "archive": "math",
                        "arxiv_id": "1507.01919",
                        "id": 1000572,
                        "primary_category": "math.FA",
                        "submitted_at": 1436295105000000,
                        "title": "A usufel lemma for Lagrange multiplier rules in infinite dimension"
                      },
                      "labels": [
                        "math.FA"
                      ],
                      "scoped": {},
                      "views": [
                        {
                          "id": "topics",
                          "x": 2021186995,
                          "y": 688805871
                        },
                        {
                          "id": "topics-wide",
                          "x": 3152550863,
                          "y": 2062599003
                        }
                      ]
                    }
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          $ref: "#/components/responses/Unknown"
        "422":
          $ref: "#/components/responses/Contract"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/artifacts:
    post:
      tags: [viewer]
      operationId: artifacts
      summary: Every artifact of one layer this principal is served, page by page, with the properties the caller names.
      description: |
        The body is framed as `POST /v1/items` frames its body: a `6` head (JSON `ArtifactsHead`),
        one or more `7` records frames each followed by an `8` page end (JSON `PageEnd`), and a
        `4` trailer (JSON `RecordsTrailer`). Its pages, budgets, page ends, trailer and cursor follow that
        route's rules, and a read is carried across responses by passing each response's `next`
        back as `cursor` until it is null. Every page is built from the latest data with the
        visible set composed again, so a deletion or suppression accepted during a read applies
        from the next page.

        **Cursor.** As `POST /v1/items`' cursor, bound also to this route, to `layer` and `level`,
        and to the set of `ids`: a cursor sent with any of them changed is `422`.

        **Rows.** Every artifact of `layer` this principal is served when its page is built,
        ordered by level and then by publication order within the level. Each page applies the
        viewport's own test to each artifact it walks: the artifact's label, its layer's existence
        criterion over this principal's visible members, and, where the layer declares content,
        whether this principal may read it. An attached artifact, such as a clustering's label, is
        served only while the artifact it is attached to is. Publication order is the publisher's
        and is not grouped by access labels.

        **Narrowing.** `ids` reads the artifacts those `mosaica_id`s name, such as the tags of
        points a client holds no artifact for; one this principal is not served has no row, as
        one naming nothing does. `level` reads one level of a levelled layer. `parent` reads the artifacts
        naming that artifact among their parents; a parent this principal is not served answers
        as a parent with no children does. `q` keeps the artifacts whose key, or whose first
        served text content, contains it, ignoring case; an authored shape is not text and is not
        searched. `filters` keeps the artifacts with at least one visible member matching it, and
        adds a `matched_count` column; under `keep_unmatched` every served artifact is returned,
        those with no matching member with a zero. An attached artifact is counted by the artifact
        it is attached to: its `matched_count` is its target's, it is kept or dropped as its
        target is, and the head's `matched` counts it so, as the viewport gives it its target's
        matched bit. A request with `filters` evaluates the filter over the whole view once per
        response, and again when a write lands during the response that moves the view's rows or
        changes which of them this principal can see.

        **A layer that goes during a read.** A layer dropped, or no longer published to this
        principal, while a read is under way ends the read: the next page finds no artifact, and
        the response ends by `end`.

        **Columns.** `mosaica_id` (`uint64`) first, then the named properties in the order named,
        then `matched_count` (`uint64`) whenever the request carries `filters`.

        - `key`: `utf8`, null where the publisher supplied none.
        - `level`: `uint32`, the declared level, 0 on a layer without levels.
        - `parents`: `list<uint64>`, the parents this principal is also served.
        - `target`: `uint64`, the artifact this one is attached to, null where none.
        - `masked_count`: `uint64`, how many members this principal can see.
        - `content`: `list<utf8>`, the supplied content this principal is served, in the order of
          the layer's `supplied_content`, with an authored shape's slot empty, since that shape is
          served as `shape`.
        - `centroid`: `float64` columns `centroid_x` and `centroid_y`, over the members this
          principal can see, in the view's coordinates as `POST /v1/items` converts `position`,
          null where it sees none. A centroid is the members' mean in the view's projected space,
          converted, so on a geographic view it is not the mean of their longitudes and latitudes.
        - `box`: `float64` columns `box_x_min`, `box_y_min`, `box_x_max` and `box_y_max`, on the
          same terms.
        - `shape`: `binary`, a WKB `MultiPolygon` in the view's coordinates, null on a layer that
          draws no geometry: a hull over the members this principal can see, or a spatial or
          authored shape whole, as `POST /v1/artifacts/{mosaica_id}` serves it. Every ring is
          closed and holds at least four points; a hull over one or two members, or over members
          on one line, is a zero-area polygon over their positions, repeated.
        - `slot`: `uint8`, this principal's palette slot for the artifact among `palette_size`
          colours, as `POST /v1/artifacts/viewport` serves it; null where the request names no
          `palette_size`, and for an artifact holding no member this principal can see.

        **Cursor.** Bound to the route, the view and its incarnation, the layer as it was
        registered, the `level` named and the credential that received it. A layer dropped and
        registered again under the same name is another layer and refuses the cursor. A cursor that
        fails any of these, or was issued by another bundle, is one `422` with one detail.

        Runs under the bulk-read admission limit, `serve.bulk_admission`, with `POST /v1/items`. A
        read past the limit is refused at once with `429`.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArtifactsRequest"
            examples:
              one-level:
                summary: The 16 coarsest Toponymy clusters, with their sizes and centres
                description: "Level 0 of the tiered layer `clusters/toponymy`. `masked_count`, `centroid` and `box` are over the papers this viewer may see."
                value: {"view": "topics", "layer": "clusters/toponymy", "level": 0, "fields": ["key", "masked_count", "centroid", "box"]}
              by-ids:
                summary: Three clusters, read by their identifiers
                description: "`ids` are `mosaica_id`s from an earlier answer: here the first three rows of `one-level`. A client holding a point's `membership:clusters/toponymy` tag from the viewport reads the cluster it names the same way."
                x-mosaica-fill:
                  body:
                    /ids: {operation: artifacts, example: one-level, read: {column: mosaica_id, rows: 3}, like: {type: array, minItems: 3, maxItems: 3, items: {type: string, pattern: "^[0-9]+$"}}, same: true}
                value: {"view": "topics", "layer": "clusters/toponymy", "ids": ["14057532449301325932", "15058582009882476427", "7092569839109733218"], "fields": ["key", "level", "parents", "masked_count"]}
              search:
                summary: The Toponymy topic names that mention black holes
                description: "`q` keeps the label artifacts whose key, or whose first served text content, contains it, ignoring case. A name's first content is the name, and `target` is the cluster it names."
                value: {"view": "topics", "layer": "topics/toponymy", "q": "black hole", "fields": ["key", "target", "content"]}
      responses:
        "200":
          description: The framed body, with `POST /v1/items`' headers.
          headers:
            x-mosaica-identity-key:
              description: As on `POST /v1/items`.
              required: false
              schema:
                type: string
            x-mosaica-region:
              description: "Present exactly when the request's `filters` carry a `region` leaf, with the viewport's values."
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
            x-mosaica-server-us:
              description: Server time from admission to the head, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
            x-mosaica-admission-us:
              description: Time spent being admitted, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: A head, one or more records frames each followed by a page end, and a trailer, as on `POST /v1/items`.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: Unknown `view`, or one this principal cannot reach.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The request needs to change:

            - the viewport's filter refusals;
            - an unknown key, or a missing `view`, `layer` or `fields`;
            - a layer that is unknown or not published to this principal, with one detail for both;
            - `level` on a layer whose kind has one level, or naming a level the layer does not hold;
            - an unknown or repeated property;
            - `parent` with `q`;
            - `page_rows` or `pages` of 0;
            - `count` with a `cursor`;
            - a `compression` outside its values;
            - a cursor that does not open for this request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/artifacts/viewport:
    post:
      tags: [viewer]
      operationId: viewportArtifacts
      summary: The artifacts of the named layers in each tile, largest first up to a quota, over a region at a depth.
      description: |
        **What things are here.** The body is framed as `POST /v1/viewport`'s is. The kinds:

        - `5` artifacts: first, one whose `tile` column is null, holding the artifacts of the
          nested and `dag` layers named. Absent where it would hold none.
        - `5` artifacts: then exactly one for each tile, in the request's order with repeats
          removed, or in the order the `bbox` resolves to as on `POST /v1/viewport`. A tile holding
          none is a frame of no rows.
        - `4` trailer: exactly one, last: `stream_us`, `arrow_serialise_ns`, `rows` (the rows
          sent) and `frames`.

        A body without a trailer is incomplete. Every frame is whole, so a client may keep each
        tile's frame as it arrives.

        **A tile's frame.** For each layer named, and each of its levels selected, the artifacts
        this principal is served that hold a member the principal can see inside the tile, at most
        `per_tile` of them, the largest first: by `masked_count`, descending, and then by
        `mosaica_id`. An artifact with visible members in several tiles is in each of their frames,
        with the same `masked_count`, centroid and box: every number is over the artifact's whole
        membership as this principal sees it, never over the tile. Levels follow the layers'
        order, a layer after any it depends on, and each level's rows follow their own.

        **The treed frame.** A nested or `dag` layer is walked over every requested tile together
        and cut to `budget`, met by serving ancestors in place of their descendants, never by
        sampling, as one frame with no tile. `rung` there is the depth in that frame's own parent
        links.

        **Columns.** Eighteen, at fixed positions: `layer` (dictionary-encoded), `mosaica_id`,
        `key`, `masked_count`, `centroid_x`, `centroid_y`, `box_min_x`, `box_min_y`, `box_max_x`,
        `box_max_y`, `content`, `parent_ids`, `rung`, `matched`, `highlighted`, `target`, `tile`
        (`uint32`, the tile's Morton prefix at `zoom`) and `slot` (`uint8`, null where the request
        names no `palette_size`; see **Slots**). A null in a geometry column means
        the layer declares no such property or the request asked for none. `parent_ids` names the
        parents that are rows of the same frame. `target` is the `mosaica_id` of the artifact a row
        is attached to, such as the cluster a label names, in the same frame or the treed frame;
        null where the row is attached to nothing. A dependent is served in a tile only where its
        target is in that tile's frame or the treed frame, when the request names its target's
        layer, and one that is not takes no place in the quota. A label's `masked_count` is its own. `matched`
        is whether a member this principal can see inside the tile matches `filters`, and
        `highlighted` the same for `filters` and `highlight` together; each is null where the
        request carries no such expression, and a label takes its target's. A filter changes
        nothing else: the same artifacts are served with the same counts, filtered or not. No
        frame carries a shape; `POST /v1/artifacts/{mosaica_id}` serves an artifact's.

        **Slots.** With `palette_size`, every row carries `slot`, a number below it that a client
        maps to a colour. It is this principal's and fixed for them: worked out once over every
        artifact of the layer they are served with a member they can see, so that artifacts drawn
        beside each other differ, and the same at any zoom, box, budget or filter, in every frame
        and on every route that serves the artifact. A child with the most visible members keeps
        its parent's slot where no neighbour holds it. A change moves the slots of the artifacts
        beside what it changed and, where it changes which child is a parent's heir, of that child
        and its neighbours, and then at each depth below of the children of every artifact whose
        slot moved and of the artifacts drawn beside them: a reach that widens by a ring of
        neighbours a depth. A deletion, a suppression or a publication is answered with slots worked
        out over it, and so is any change on a layer whose criterion is a fraction or whose content
        has a generating set; on any other layer an ingest or a growth of a membership is answered
        with the slots held until they are worked out again after that response.

        Subject to its own admission, `serve.artifact_admission`. Cancelled between tiles when the
        client goes away.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ViewportArtifactsRequest"
            examples:
              one-layer:
                summary: The eight largest Toponymy topics in each tile
                description: One level of a tiered layer, the 46 topics of level 1, with at most eight in each tile, largest first.
                value: {"view": "topics", "zoom": 2, "bbox": [-15, -15, 25, 25], "layers": ["clusters/toponymy"], "levels": [1], "per_tile": 8}
              with-labels:
                summary: The 16 coarsest Toponymy topics, with their names and colours
                description: "`topics/toponymy` is attached to `clusters/toponymy`, so each name is served in the tiles its cluster is served in, with the cluster's `mosaica_id` as its `target`. `palette_size` gives each cluster a colour slot that differs from its neighbours'."
                value: {"view": "topics", "zoom": 2, "bbox": [-15, -15, 25, 25], "layers": ["clusters/toponymy", "topics/toponymy"], "levels": [0], "per_tile": 5, "palette_size": 8}
      responses:
        "200":
          description: The framed body. Its headers are `POST /v1/viewport`'s, and carry the same coordinates.
          headers:
            etag:
              description: The content key, quoted, as on `POST /v1/viewport`.
              required: true
              schema:
                type: string
            x-mosaica-identity-key:
              description: The identity key, as on `POST /v1/viewport`.
              required: true
              schema:
                type: string
            x-mosaica-pin:
              description: The generation this response was computed from, as on `POST /v1/viewport`.
              required: true
              schema:
                type: string
            x-mosaica-stale:
              description: "`1` where the request's `pin` differs from this response's `x-mosaica-pin`, and `0` otherwise."
              required: true
              schema:
                type: string
                enum: ["0", "1"]
            x-mosaica-region:
              description: Present exactly when `filters` or `highlight` carry a `region` leaf, as on `POST /v1/viewport`.
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
            x-mosaica-server-us:
              description: Server time from admission until the first frame was ready, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
            x-mosaica-admission-us:
              description: Time spent waiting for admission, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: Framed Arrow IPC streams and one JSON trailer.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: Unknown `view`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The request needs to change:

            - `zoom` above 16;
            - neither or both of `bbox` and `tiles`, a `bbox` that is not finite or has a maximum below its minimum, or a tile prefix with bits above `zoom`;
            - more tiles than `selection.max_tiles_per_request`;
            - a `per_tile` above `selection.max_artifacts_per_tile`;
            - an unknown filter column, or an operator outside its column's family;
            - a `layers`, `levels` or `computed` value outside its schema.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/artifacts/browse:
    post:
      tags: [viewer]
      operationId: browseArtifacts
      summary: "One page of a layer's hierarchy, by lineage: its roots, an artifact's children and parents, or a name search."
      description: |
        This route does not depend on the viewport. A layer whose artifacts are spread across the
        map may draw nothing at any zoom the viewport's budget reaches; this route serves the same
        artifacts by relation instead of by position, each row carrying the masked count
        `POST /v1/artifacts/viewport` carries.

        **Three forms.** With neither `parent` nor `q` it returns the layer's roots, the artifacts
        with no served parent. `parent` returns the artifacts naming that one among their parents,
        with the named artifact's own served parents in `parents`, which is `[]` in the other two
        forms. `q` returns the artifacts whose key or `name` contains it, ignoring case. Both
        together is `422`.

        **Names.** A row's `name` is the artifact's own first supplied text. An artifact with none,
        such as a cluster whose topic is published in a separate label layer, is named by the first
        text of an artifact attached to it that `POST /v1/artifacts` would serve this principal
        when reading that label layer. Label layers are taken in the order `/v1/meta` lists them;
        within one, by level, then artifacts with a key in key order, then those without in
        publication order. A
        label this principal would not be served names nothing and matches no `q`, and `filters`
        does not change a name.

        **Visibility first.** Every artifact served has passed its own existence criterion,
        evaluated over the form's whole candidate set before `limit` and `cursor` apply, so a
        page's fill and its `next` count only artifacts this principal may see, and a withheld
        artifact leaves no gap. The order is total: count descending, then `mosaica_id` ascending,
        so a cursor over tied counts neither repeats nor skips a row. A relation is named only
        where both ends are served: a child whose parent is withheld is a root here, and a parent
        below this principal's criterion is absent from `parents`.

        **Refusals.** They name deployment schema and never an artifact. An unknown layer, an
        attached layer, a `level` on a kind with one level, a level the layer does not hold,
        `limit = 0`, and both `parent` and `q`, are each `422`. A `parent` that names nothing, one
        of another layer, one suppressed, and one below this principal's criterion answer an empty
        page, since a refusal would tell the caller the artifact exists.

        Subject to compute admission.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BrowseRequest"
      responses:
        "200":
          description: One page of the layer's hierarchy as this principal sees it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrowseResponse"
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: "`404 unknown`: no such `view`, or one this principal may not reach. The two give one answer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: "`422 contract`: an unknown or attached layer, a `level` the kind or the layer does not have, `limit = 0`, both `parent` and `q`, or a malformed `filters`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/artifacts/{mosaica_id}:
    post:
      tags: [viewer]
      operationId: artifact
      summary: One annotation artifact, with its masked count and geometry for this principal.
      description: |
        A separate route from `/v1/items/{mosaica_id}`, so the shape of an answer does not say
        which of the two an identifier names. `masked_count` is this principal's count of the
        artifact's members, not its size. Geometry is computed per principal from the members this
        principal can see, in the same 32-bit-per-axis grid units as the viewport's `code`, so a
        client does not keep one principal's geometry for another. An absent geometry field means
        the layer declares no such property; an artifact whose content cannot be served is a
        `404` whole.

        Nothing by that identifier, a point's identifier, a layer this principal cannot reach, a
        suppressed artifact, and one below its layer's existence criterion all give one `404`.
        Subject to compute admission.
      security:
        - sessionToken: []
      parameters:
        - $ref: "#/components/parameters/MosaicaId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ArtifactRequest"
      responses:
        "200":
          description: The artifact as this principal sees it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactResponse"
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: "`404 unknown`: nothing this principal may see by that identifier. An unknown `view` gets the same answer."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/Contract"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /v1/aggregate:
    post:
      tags: [viewer]
      operationId: aggregate
      summary: How the items this principal may see in one view are distributed, as one table per grouping of the figures it names, counts exact unless a histogram asks for a sample, and the figures of number or timestamp fields over the visible set.
      description: |
        **The body is not JSON.** It is framed as `POST /v1/items` frames its body, with these
        kinds:

        - `9` table head: JSON `AggregateTableHead`, before a table's first page in this
          response, including a table continued from a cursor.
        - `7` records: one Arrow IPC stream holding one batch of a table's rows.
        - `8` page end: JSON `PageEnd`.
        - `4` trailer: JSON `RecordsTrailer`, exactly one, last.

        Tables arrive in the order of `groupings`, one per grouping. A table may span pages, and a
        response may end part-way through one. There is no response head. Every response a
        client receives carries at least one page: the stream deadline ends a response only after
        its first. An unknown kind is a decoding error. [Wire framing](../openapi/README.md) walks
        a decode.

        **The sets.** `filters` is the set every count is taken over, in the viewport's grammar
        and evaluated in `view`; absent is every item this principal may see there. `reference` is
        a second set in the same grammar, drawn from the same visible set; `{}` is the whole
        visible set. An item with no position in `view` is counted in neither.

        **Groupings.** Each grouping is one table. It names the figures each row holds
        (**Figures**), and has an optional outer level, `by`, and an optional inner level,
        `cells`. With neither level, its table is one row, so `{"count": true}` is the size of the
        set. `by` names a category `field` or the artifacts of a `layer`, and lists `top` groups by
        count or the groups named in `values` or `artifacts`; or it names a number or timestamp
        `field` and counts its values in `bins`, a histogram, which takes no `cells`. `cells`
        divides the set, or each group, into the cells of `view` at `depth`: depths 0 to 16 are the cells of the
        map's tiles at that zoom, and 17 to 32 divide them down to the stored position.
        `cells.area` is a bbox in the form and units the viewport's `bbox` takes, absent for the
        view's whole extent, and an area partly outside the extent is clamped to the edge cells as
        the viewport's `bbox` is; only the cells at `depth` that intersect it are listed, as a
        viewport lists tiles for its `bbox`, each with all of its items. The table head's `total`, `reference_total` and `groups`, and the
        counts that rank `top`, stay the whole set's. The cells at `depth` in the area, a matter of
        geometry alone, may number at most `selection.max_aggregate_cells`, however many groups
        the grouping has.

        **Rows.** A field with `top`: the `top` values with the most items, ties by key; then
        `rest`, the items with any other value; then `none`, the items with no value. Each item
        holds one value, so a table's counts sum to the set, and within a cell to that cell's
        count. A field with `values`: the named values in the order named, a repeat once, then
        `rest` and `none`; a named value appears, even with a count of 0, when
        `/v1/categories` would list it to this principal in this view, and otherwise gets no row
        and is not counted in `rest`. A layer: the listed artifacts as for a field; `rest`, the
        items in at least one artifact of the level this principal is served and in none of the
        listed ones; `none`, the items in no such artifact. Artifacts can overlap, so a layer's
        rows can sum to more than the set. An artifact is listed on the viewport's test against
        the principal's visible set, never the filtered set, and an id that names nothing or
        nothing this principal is served gets no row. Cells: one row per non-empty cell, by
        ascending `cell`, within each group in group order. With `reference`, a row appears where
        either count is non-zero. `rest` and `none` appear only where non-zero.

        **A tree's cut.** On a `nested` or `dag` layer, `top` takes `cut`, and is refused without
        it. The artifacts the table ranks are those the treed frame of
        `POST /v1/artifacts/viewport` serves for the same `view`, and `cut`'s `zoom`, `bbox` and
        `budget` as that request's: the artifacts this principal is served with a member it may
        see in the tiles, cut to the budget by drawing ancestors in place of their descendants.
        On a layer declared with `prune_children: false`, which draws ancestors beside their
        descendants, only the drawn artifacts with no drawn artifact beneath them on any path are
        ranked. `rest` and `none` are counted against the ranked artifacts: `rest` holds the items
        in a ranked artifact other than the listed ones, and `none` the items in no ranked
        artifact, so an item held only by a drawn ancestor counts in `none`. The filter never
        decides which artifacts are drawn; it changes only their counts and so their order.
        `cut` on any other layer is refused.

        **Bins.** A field with `bins`: one row per bin, by ascending edge, each appearing even with
        a count of 0; then `rest`, the items whose value lies in no bin; then `none`, the items
        with no value. A bin holds the values from its `lower` edge up to but not including its
        `upper` edge, and the last bin also holds its `upper` edge, so a filter's `range` with
        `gte` the lower edge and `lt` the upper matches exactly a bin's items, and `lte` for the
        last. On a timestamp field, and on an integer field unless a bound of `range` is
        fractional, the edges are whole numbers, worked out exactly, and every value is compared
        with them exactly.

        With `range` on a float field, where `lower` and `upper` are multiples of one readable
        width (below) `n` widths apart, the edges are those multiples; otherwise edge `i` of `n`
        is `lower × (1 − i/n) + upper × (i/n)` in `float64`, the last exactly `upper`. Where those
        edges do not rise, as in a range a few `float64` values wide, the table has one bin from
        `lower` to `upper`. On an integer field where either bound is fractional, the bins are cut
        as on a float field, the values compared with them as `float64`, and the edges served as
        `float64`. On a timestamp field the bounds are whole microseconds, and on an integer field
        with whole bounds, as there, edge `i` is `lower + floor((upper − lower) × i / n)`; an edge
        past what the edge column holds is served as that column's smallest or largest value, and
        the bins are counted as served. `rest` holds the values outside the range.

        Without `range`, the edges are readable values around the smallest and largest value
        among every item this principal may see in `view`, whatever `filters`, `reference` and any
        region say, so they stay put as the filters or the viewport change. A principal with no
        visible value gets no bin, and `rest` holds only NaN and infinite values. On a number
        field the bins share one width, `1`, `2`, `2.5` or `5` times a power of ten and a whole
        number on an integer field, the narrowest that needs at most `bins` bins; the first edge
        is the multiple of the width at or below the smallest value, and the last the first
        multiple at or above the largest, or above it on an integer field. Where every visible
        value is the same, the width is the largest power of ten at or below its magnitude, or 1
        for 0. With `bins` of 1, values either side of 0 have no such width, since 0 is a multiple
        of every width; their one bin runs from the smallest value to the largest, or to one above
        it on an integer field. On a float field, values whose readable edges would pass the
        `float64` range are cut as a `range` from the smallest to the largest would be. On a
        timestamp field the width is the finest of 1, 2 or 5 times a power of ten microseconds up
        to 500,000; 1, 2, 5, 10, 15 or 30 seconds or minutes; 1, 2, 3, 6 or 12 hours; 1 or 2 days;
        a week; 1, 2, 3 or 6 months; and 1, 2 or 5 times a power of ten years, that needs at most
        `bins` bins. A fixed width's bins start at multiples of it from 1970-01-01T00:00Z, a
        week's on Mondays, a month's on the first of a month whose number from January of year 0
        is a multiple of the width, and a year's on 1 January of a year that is a multiple of the
        width, in UTC. The last edge is above the largest value. Where every visible value is the
        same instant, its bin is its day.

        The smallest and largest value are the field's `min` and `max` (**Figures**), exact over
        every item this principal may see in `view`, so no visible value lies outside the bins. A build and a
        compaction store the figures of the built rows, so default edges cost no pass over the
        values. Sending the first
        answer's first `lower` and last `upper` back as `range`, with `bins` the number of bins it
        returned, gives the same edges, except on a timestamp field binned by months or years,
        whose bins differ in width, and where an edge was served as its column's smallest or
        largest value.

        **Sampled bins.** With `sample`, a set of `N` items where `N` is more than `sample` is
        counted over its items whose `mosaica_id` is below the cut `floor(sample × 2^64 / N)`,
        about `sample` of them, and each row's `count` is the count among them times `N` over
        the items counted, rounded to the nearest whole number with a half rounded up. The cut is
        one for the whole set: no tile, cell or group has a floor or a cap. `N` is `total`, the
        set's exact size. The reference set is sampled the same way at its own cut, and `lift`
        is taken from the scaled counts. Where `N` is at most `sample`, every item is counted
        and the counts are exact. Every item is also counted, exactly, where the cut is above
        2^58, `sample` being more than about one item in 64 of the set, since no identity band
        holds so wide a cut. Whether a set is sampled depends on `N` and `sample` alone. Without
        `range`, the edges are the exact ones above, from every value this principal may see in
        `view`, whether or not the counts are sampled, so a sampled table's edges are the
        unsampled table's. The sample is drawn from inside the principal's visible set, so an item
        it may not see is never counted and never moves `N`. The table head's `sample` says how
        the table was counted. A larger `sample` gives counts nearer the exact ones, at the cost
        of reading more items.

        **Figures.** A grouping names what its rows hold, as keys beside `by` and `cells`, and
        names at least one. `count: true` counts the items of the set in each row. `values`,
        `missing`, `min`, `max` and `mean` each list number or timestamp fields declared with
        `index` or `render`, resolved as `by`'s `field` is, and give one column per field: the
        items holding a finite value, the items holding no value, the smallest and largest finite
        value, and the mean of the finite values. `summary` lists fields and names all five of
        those figures over each. A figure named twice for one field gives one column; **Columns**
        gives their order.

        These field figures are taken over every item this principal may see in `view`, the
        visible set, and only over it: a grouping that names one has neither `by` nor `cells`, and
        a request that names one carries neither `filters` nor `reference`, so its `count` is
        every visible item in `view`. A grouping whose only figures are field figures has no
        `count` column; its head's `total` is the visible set's, and it has no `groups`. Every
        field figure is exact. A deletion or a suppression applies to them from the next response,
        and an item this principal may not see, or an item without a row in `view`, moves none of
        them. A build and a compaction store, for
        each view, the figures of its built rows for each set of access labels its items carry; a
        response joins those of the sets this principal satisfies and corrects them for its own
        deletions, suppressions and rows written since, so it reads only what has changed. A field
        declared at a running service has no stored figures, and its built rows are read once for
        each principal's terms.

        A histogram's table is always one page, of its bins, `rest` and `none`, whatever
        `page_rows` and `serve.aggregate_page_bytes` say, so its edges and counts are drawn
        together, under one state of the corpus. Beside groupings that page, the tables still
        come in order, and the histogram's page counts as one of the response's `pages`.

        **Columns**, in this order, each only where stated: `group` (`dictionary<int8, utf8>`, with
        `by`): `listed`, `rest` or `none`. `key` (with `by`): a field's vocabulary key as
        `dictionary<int32, utf8>`, or an artifact's `mosaica_id` as `uint64`; null on `rest` and
        `none`. `title` (`dictionary<int32, utf8>`, with `by` on a layer, and with `by` on a field
        whose vocabulary holds a title for any value): a value's title as `/v1/categories` gives
        it, or an artifact's name as `/v1/artifacts/browse` gives it to this principal; null where
        it has none. A field's vocabulary holds a title once one is declared for any of its values,
        and holds one from then on; a table read through a cursor keeps the columns of its first
        page. `slot` (`uint8`, with `by` on a layer): this
        principal's palette slot for the artifact among the grouping's `palette_size` colours, as
        `POST /v1/artifacts/viewport` serves it; null on `rest` and `none` and where the grouping
        names no `palette_size`. `lower` and `upper` (with `bins`, in
        place of `key` and `title`): the bin's edges, `int64` on a signed integer field, `uint64` on
        an unsigned one, `float64` on a float field and on an integer field whose `range` has a
        fractional bound, and `timestamp[us, UTC]` on a timestamp field; null on `rest` and `none`.
        `cell` (`uint64`, with `cells`): the first `2·depth` bits of the item's 64-bit Morton
        position, right-aligned; at a depth of 16 or less it is the tile code the viewport uses at
        that zoom. `count` (`uint64`, with `count`): the items of the set in the row's group and
        cell. `reference_count` (`uint64`, with `count` and `reference`): the same over the
        reference set. `lift` (`float64`, with `count` and `reference`):
        `(count / total) / (reference_count / reference_total)`, null where `reference_count` or
        `total` is 0.

        Then the field figures, named `<figure>_<field>` with the field as the request wrote it, in
        the order `values`, `missing`, `min`, `max`, `mean`. Each figure's columns run over the
        fields of its own list in order, then over those of `summary` not already listed, a field
        named twice giving one column: `values_<field>` (`uint64`), the items holding a finite
        value; `missing_<field>` (`uint64`), the items holding no value, so an item holding NaN or
        an infinity is in neither `values_<field>` nor `missing_<field>`; `min_<field>` and `max_<field>`, the smallest and largest finite
        value, typed as `lower` and `upper` are on a histogram of the field without `range`, null
        where `values_<field>` is 0; and `mean_<field>`, the mean of the finite values, null where
        `values_<field>` is 0. The values are summed exactly, whatever their size and order, and
        the mean is the exact sum over `values_<field>` rounded once: on a timestamp field to the
        nearest whole microsecond, served as `timestamp[us, UTC]`, and on any other to the nearest
        `float64`, served as one, a tie to even either way.

        **Reading a whole result.** A response carries at most `pages` pages and runs within its own
        byte budget, `serve.aggregate_response_bytes` (16 MiB by default), and the bulk reads' time
        budget. A page, a histogram's apart, holds at most `page_rows` rows and
        `serve.aggregate_page_bytes` (4 MiB by default); one can hold fewer rows than `page_rows`
        before the table ends. Both budgets count the Arrow column bytes of the rows and of the
        page's dictionaries; the framing, the schema and buffer padding add a little to each page
        on the wire. Pass the trailer's `next` back as `cursor`, unchanged, until it is null. Every page
        composes the visible set again, so a deletion or suppression accepted during a read applies
        from the next page, and the pages of one table can count different states of a changing
        corpus. Which groups a table lists under `top` is fixed at its first page and carried in the
        cursor. A histogram is one page, so no edge is carried.

        **Cursor.** As `POST /v1/items`' cursor, bound also to this route and to the request's
        `filters`, `reference` and `groupings`, figures included: a cursor sent with any of them
        changed is `422`.

        **Admission.** Every request runs under the viewport's admission, since a client sends it
        beside viewport requests, and holds its place until the response ends; the response's
        byte budget bounds what it holds, and a larger table continues through the cursor. A
        client that disconnects stops the work.

        **The trailer** carries `recomposed: true` where a page of this response counted a
        different state of the corpus from the page before it, in this response or in the one
        the cursor came from.
      security:
        - sessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AggregateRequest"
            examples:
              top-categories:
                summary: The ten subject classes with the most papers
                description: "One table: the ten largest classes, then `rest` for every other class, so the counts sum to the papers this viewer may see in `topics`."
                value: {"view": "topics", "groupings": [{"count": true, "by": {"field": "primary_category", "top": 10}}]}
              by-layer:
                summary: The eight arXiv archives with the most papers, by the taxonomy layer
                description: "`taxonomy/arxiv` is tiered, so the grouping names a level: 0 is the archives. On a layer whose artifacts overlap, the rows can sum to more than the set."
                value: {"view": "topics", "groupings": [{"count": true, "by": {"layer": "taxonomy/arxiv", "level": 0, "top": 8}}]}
              histogram:
                summary: Papers by submission date, in at most 20 bins
                description: Without `range`, the edges are readable dates around the earliest and latest submission this viewer may see.
                value: {"view": "topics", "groupings": [{"count": true, "by": {"field": "submitted_at", "bins": 20}}]}
              summary:
                summary: The papers, and their earliest, latest and mean submission date
                description: "One row over every paper this viewer may see in `topics`: `count`, then `values_submitted_at`, `missing_submitted_at`, `min_submitted_at`, `max_submitted_at` and `mean_submitted_at`."
                value: {"view": "topics", "groupings": [{"count": true, "summary": ["submitted_at"]}]}
              density:
                summary: Statistics papers counted in each cell of a 32 by 32 grid
                description: "`cells` at depth 5 divides the view into the tiles of zoom 5. Each row is one non-empty cell and its count."
                value: {"view": "topics", "filters": {"archive": {"eq": "stat"}}, "groupings": [{"count": true, "cells": {"depth": 5}}]}
              comparison:
                summary: Which archives papers with "quantum" in the title come from, against the whole map
                description: "`reference: {}` is every paper this viewer may see. Each row gains `reference_count` and `lift`, the group's share of the filtered set over its share of the reference."
                value: {"view": "topics", "filters": {"title": {"match": "quantum"}}, "reference": {}, "groupings": [{"count": true, "by": {"field": "archive", "top": 10}}]}
      responses:
        "200":
          description: The framed body, with the headers `POST /v1/items` sends.
          headers:
            x-mosaica-identity-key:
              description: The viewport's identity key for this principal and view. Absent only from a response cancelled before its first page.
              required: false
              schema:
                type: string
            x-mosaica-region:
              description: "Present exactly when `filters` or `reference` carries a `region` leaf, with the viewport's values: the coarsest answer either reached on the first page."
              required: false
              schema:
                type: string
                pattern: "^(exact|cover; depth=[0-9]+)$"
            x-mosaica-server-us:
              description: Server time from admission to the start of the body, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
            x-mosaica-admission-us:
              description: Time spent being admitted, in microseconds.
              required: true
              schema:
                type: string
                pattern: "^[0-9]+$"
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
                description: For each table, a table head and one or more records frames each followed by a page end; then a trailer.
        "401":
          $ref: "#/components/responses/BadCredential"
        "403":
          $ref: "#/components/responses/ExpiredToken"
        "404":
          description: Unknown `view`, or a pin naming no view of its field's group that this principal can reach.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The request needs to change. One detail, naming the limit where one is exceeded:

            - the viewport's filter refusals, in `filters` or `reference`;
            - an unknown key, a missing `view` or `groupings`, no grouping, or more than `selection.max_aggregate_groupings`;
            - `by` naming neither or both of `field` and `layer`, or carrying neither or both of `top` and `values` or `artifacts`, or `values` on a layer, or `artifacts` or `level` on a field;
            - `top` of 0 or above `selection.max_aggregate_top`; `values` or `artifacts` empty or longer than `selection.max_aggregate_named`; an `artifacts` entry that is not a `mosaica_id`;
            - `bins` beside `top` or `values`, or on a layer; `range` without `bins`; `bins` of 0 or above `selection.max_aggregate_bins`; a `range` whose lower bound is not below its upper, or that is not two numbers or strings; a fractional bound on a timestamp field, or a text bound that is not a date or date-time with an offset and at most six fractional-second digits; `bins` with `cells`; `sample` without `bins`, on a layer, or of 0;
            - a grouping that names no figure, `{}` and a `by` alone among them, with a detail saying to write `"count": true`; `count` other than `true`, `null` among them; a `values`, `missing`, `min`, `max`, `mean` or `summary` that is `null` or an empty list;
            - `values`, `missing`, `min`, `max`, `mean` or `summary` in a grouping with `by` or `cells`, or in a request carrying `filters` or `reference`, with a detail saying they are computed over the whole visible set only; a field in one of them that is not a number or timestamp, and a number or timestamp field declared with neither `index` nor `render`, with a detail saying to name a number or timestamp field declared with `index` or `render`;
            - `bins` on a field that is not a number or timestamp, each kind with its own detail; on a `bool`, with a detail saying it has no bins; and on a number or timestamp field declared with neither `index` nor `render`, with a detail saying to name a number or timestamp field declared with `index` or `render`;
            - an unknown field, and a field that is not a category, each with its own detail; a category declared with none of `index`, `render` or a `derived` vocabulary, with a detail saying to declare it with `index` or `render`;
            - a layer outside the list `/v1/meta` publishes to this principal; no `level` on a layer with several, a `level` the layer does not hold, or one given on a layer with a single level;
            - `top` on a `nested` or `dag` layer without `cut`; `cut` on any other layer or on a field; a `cut` whose `zoom` is above 16, whose `bbox` is not `[x0, y0, x1, y1]` with `x0 ≤ x1`, `y0 ≤ y1`, all finite, or which spans more tiles than `max_tiles_per_request`;
            - `depth` above 32, whatever its size; an `area` that is not `[x0, y0, x1, y1]` with `x0 ≤ x1`, `y0 ≤ y1`, all finite;
            - more cells at `depth` in the area than `selection.max_aggregate_cells`: one detail naming the count, the limit and the deepest depth at which the area fits;
            - `page_rows` or `pages` of 0;
            - a cursor that does not open for this request.

            An unknown value in `values`, and an `artifacts` id that names nothing this principal is served, are not refused: they get no row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/ingest:
    post:
      tags: ["control: ingest"]
      operationId: ingest
      summary: Create, edit and add items to a view, from a batch of rows.
      description: |
        The body is the batch's rows. `content-type` chooses the encoding: `application/json`,
        `application/x-ndjson` or none at all is JSON, an array of objects or one object per line,
        each keyed by column name; `application/vnd.apache.arrow.stream` is an Arrow IPC stream of
        one or more record batches. Any other `content-type` is `422`.

        **The view.** The batch's view is the one `x-mosaica-view` names. With no header, a
        deployment with one view takes that view, and one with several gives the batch no view,
        whose rows carry no coordinates and change only what is stored in no view.

        **Which item a row names.** A row names an item by its `mosaica_id` and the value of every
        unique column it carries that is not null. A row whose values name no
        item creates one. A row whose values name one item addresses it: where every value the
        row carries is the one the item stores, the row changes nothing and is counted in
        `unchanged`; where the item has no row in the batch's view, and the row carries a position
        there and changes nothing else, the row adds the item to the view, counted in `added`: the
        item stays served in its other views and is served in this one from the next flush. Any
        other row edits the item and is counted in `edited`: it changes a value, the label or a
        position. An edited item keeps its `mosaica_id`, its views, its layer memberships and a suppression; a label the edit narrows
        applies from the answer on. A row whose one change is placing the item in artifacts that
        do not hold it changes the artifacts, not the item: it is counted in `unchanged`, and the
        memberships it adds in `joined`.

        **Columns.** Every column may be left out, and in JSON every key. A value left out keeps
        what the item stores; a new item has no value there. A null clears it: on an item that
        holds a value it is a change. The coordinates are `x` and `y` on a view whose projection is
        `none`, and `lon` and `lat` in degrees on any other, `float32` or `float64` in Arrow; a row
        carries both or neither. A row that creates an item needs them: a row naming no item and
        carrying none is refused as naming no item. A batch in which no row carries coordinates
        creates nothing and only addresses items, so it needs a `mosaica_id` or a unique column,
        null in every row or not; without one it is `422`.
        A coordinate outside the view's extent is stored on its edge and counted in `clamped`; one
        outside the projection's domain is counted in `clipped`; a non-finite one is `422`.
        `access` holds the row's access labels: in JSON a string or a list of strings, in Arrow a
        column of strings, lists of strings or a dictionary of strings. Each label is an access
        expression, such as `secret&(team_a|team_b)`, and a list admits a viewer who satisfies any
        one of them; a label that is not an expression is `422`. Unicode whitespace outside quotes
        is ignored, and a quoted term such as `"team a"` is taken exactly as written; a quoted term
        with whitespace at either end is `422`, since a credential's terms are trimmed and none
        could hold it. The same grammar applies to every label this API takes. Left out or null,
        it keeps the item's label, and a new item takes the view's `point_visibility.default`. An
        empty list, and a label empty once trimmed, is the view's default; a new item with no label
        on a view with no default is `422`. `mosaica_id` is a string of decimal digits and names an
        item; a row whose `mosaica_id` names no live or suppressed item is refused, since a new
        item is given its `mosaica_id` when it is created. `node_id` is accepted and not stored. Every other column is a declared
        attribute, a group-scoped attribute of the view's group, or an enumerated layer; any other
        name is `422`. A category takes its vocabulary's key: under a `closed` vocabulary an
        unknown key is `422`, and under an `open` one it is added. An integer is read exactly,
        from a JSON number or a string of digits, optionally after `-` or `+`; `timestamp_us` is
        microseconds since the Unix epoch, as a number or a string of digits, or a string holding
        an RFC 3339 date (`2020-01-01`, midnight UTC) or date-time with an offset
        (`2020-01-01T09:30:00Z`, `2020-01-01 10:30:00.25+01:00`), the date and time parted by `T`
        or a space. A date-time with no offset, or with more than six
        fractional-second digits, is `422`; a leap second, `23:59:60`, is the second after
        `23:59:59`.

        **Layers.** A column named after an enumerated layer places each row in that layer's
        artifacts: a key, or a list of keys whose meaning follows the layer's `hierarchy`, `null`
        or `-1` for none. A `level` column places the keys at a level. On a `closed` layer an
        unknown key is `422`; on an `open` one it creates the artifact, counted in `minted`. A
        layer scoped to a view group needs the batch's view to be one of that group's. A row that
        changes nothing names only artifacts that already hold its item.

        **Refused rows.** A row is refused where its values name more than one item, where its
        `mosaica_id` names no item, where it names no item and carries no coordinates, where it
        names an item an earlier row of the batch names, or where it sets a unique value an earlier
        row sets: of two rows naming one item or setting
        one value, the first is kept. A refused row writes nothing. The batch applies its other
        rows, lists each refused row in `refused` by position with its reason, and answers `null`
        in its place in `mosaica_ids`. With `strict=true` the batch is `409` at its first refused
        row instead, naming rows by position, values as sent and items by `mosaica_id`, and nothing
        is written. A deleted item names nothing, so its values may be given to a new item. A null
        is no value.

        **The writer's terms.** To a caller holding `write` and not `write-all` (`Permission`), a
        `mosaica_id` naming an item outside its terms is `unknown_mosaica_id`, and a unique value
        such an item holds names nothing, so a row addressing an item by it alone is
        `names_no_item`. A row that would create an item, or set a value on the item it names, with
        a unique value an item outside holds is refused as `value_held`: its `mosaica_id` is
        `null`, nothing names the holder, and a strict batch's `409` names the row and the value as
        sent. A row whose `access` labels, or a new item's view default, the terms do not satisfy
        is refused as `label_not_held`, and the batch's other rows apply. A view `x-mosaica-view`
        names, or a layer a column names, out of the caller's reach is answered as one not
        declared, and a refusal listing views lists only those it reaches. A layer column's key
        naming an artifact outside the terms names nothing: on a `closed` layer it is the unknown
        key's `422`, and on an `open` one `422` the key held, naming no artifact.

        **Shards.** The server places new items in its shards itself, and no batch is refused for
        the shard size `PUT /control/shards` declares. A batch is `409` and writes nothing only
        where its new items would need a shard past the last an identifier can name, or its edits
        would take a shard's ids past their end. Each batch sent beside it is taken or refused on
        its own.

        **Retries.** `x-mosaica-batch-id` names the batch among its caller's: another principal's
        batch under the same id is another batch, written and answered on its own. The same bytes
        sent again by the same caller under the same id answer `200` with `replayed: true`, the
        first acceptance's `mosaica_ids` and `refused`, and change nothing, whatever `strict` says;
        different bytes under an id the caller has used are `409`. How long an id is remembered is
        `x-mosaica-batch-id`'s.

        **Limits.** The body is at most `ingest.ingest_max_batch_bytes` and the batch at most
        `ingest.ingest_max_batch_rows` rows; either over is `422`, and `/control/status` publishes
        both under `limits.ingest`. Past `ingest.ingest_admission` requests in flight across this
        route, and while `ingest.ingest_buffer_max_items` items are
        buffered, the request is `429`. A `401` is answered before any other refusal, and a `429`
        for admission before the body is decoded.

        The answer follows the write-ahead log's fsync. The rows become visible in the publication
        cycle `publication` names.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/BatchId"
        - $ref: "#/components/parameters/WriteView"
        - $ref: "#/components/parameters/Wait"
        - $ref: "#/components/parameters/Strict"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IngestRecords"
          application/x-ndjson:
            schema:
              type: string
              description: One JSON object per line, each an `IngestRecord`. Blank lines are skipped.
          application/vnd.apache.arrow.stream:
            schema:
              type: string
              format: binary
              description: An Arrow IPC stream whose columns are the JSON form's, at the Arrow types this route names.
      responses:
        "200":
          description: The batch was accepted, or was a replay of an accepted batch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestResponse"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`x-mosaica-view` names no view."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: |
            Nothing was written. The detail names rows by position, values as sent and items by `mosaica_id`.

            - The batch id was used for a different body
            - With `strict=true`, a row is refused: its values name more than one item, as when a row gives an item a unique value another item holds; its `mosaica_id` names no live or suppressed item; it names no item and carries no coordinates; it names an item, or sets a unique value, an earlier row does; it sets a unique value an item outside the writer's terms holds; or it sets a label the writer's terms do not satisfy
            - The batch's new items would need a point shard past the 524,288 a bundle holds, or its edits would carry a shard's high water past 4,294,967,295, where the shard's ids end; the detail gives the figures
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            The detail names a row by its index. Nothing was written.

            - The body is over the byte cap or cannot be read
            - `x-mosaica-batch-id` is missing
            - `content-type` names another encoding
            - No `x-mosaica-view` on a deployment with several views
            - A column is misnamed, undeclared or of the wrong type
            - A cell does not fit its column
            - A key is not in a closed vocabulary or layer
            - A row creates an item with no label on a view with no default
            - No row carries coordinates, and the batch has neither a `mosaica_id` nor a unique column
            - A row carries one coordinate and not the other, or coordinates in a batch naming no view
            - The batch is over the row cap
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          description: "`503 not-ready`: the write executor is not running, or a partition is serving a stepped-down manifest. Nothing was taken."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /control/changes:
    post:
      tags: ["control: changes"]
      operationId: changes
      summary: Delete, suppress or lift the suppression of items, artifacts and layers.
      description: |
        `delete` removes an item from every view and every count; the compaction fold removes its
        rows from disc. `suppress` hides an item from every viewer until `unsuppress` lifts it.
        There is no edit here: an item's access labels change by an ingest row naming the item with
        the new labels, and `op: "predicate"` is refused with that advice.

        A `mosaica_id` may also name an annotation artifact or a layer, as a publication's or a
        registration's answer gives it. A deleted artifact stops being served at once and its key
        is free, so a publication under the key creates a new artifact. Its record and supplied
        content leave at the compaction of the last shard that lists it. A suppressed layer stops
        being served with all its artifacts until it is unsuppressed. A layer is not deleted here: a
        request that deletes one by its own `mosaica_id` is `422` and applies nothing, and
        `DELETE /control/layers/{name}` removes it.

        A change names its item in `match`, by its `mosaica_id`, a string of decimal digits, and
        by the values of fields declared unique: a keyword as a string, an integer or a timestamp
        as a number or a string of decimal digits, and a timestamp also as an RFC 3339 date or
        date-time with an offset, as an ingest row writes one. A `null` names nothing. Every value names the
        item that holds it, and a change whose values name no item, or name two, is refused; so is
        a change whose `mosaica_id` names no item. A refused change is listed in `refused` by
        position with its reason, and the others are applied. With `strict=true` the request is
        refused at its first refused change instead, `404` where it names no item and `409` where
        it names two, and nothing is applied. A change whose item is deleted, or leaves the
        caller's terms, after the request arrived and before it applies is listed in `refused`
        and does not refuse a strict request. Many changes may name one item. To a caller holding
        `write` and not `write-all`, an item outside its terms names nothing, and the change is
        answered as one naming no item is. Deleting an artifact deletes every artifact attached to
        it, those outside such a caller's terms included, as the dependent layer's `depends_on`
        declares. Every item is checked before any is applied, so a request with a malformed item
        applies nothing. The accepted
        changes are written as one record: they are durable together or not at all. A request
        carries at most 10,000 items in at most 2 MiB; over either is `422`, and `/control/status`
        publishes both under `limits.changes`.

        A deletion or suppression is in force from the moment it is accepted, for every request,
        and the `200` follows the write-ahead log's fsync. This route is never shed with `429` and
        has no readiness check. A `500` means a change may be in force without being durable:
        sending the same request again is safe, and treating the items as visible is not.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/Wait"
        - $ref: "#/components/parameters/Strict"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: "#/components/schemas/ChangeItem"
      responses:
        "200":
          description: Every accepted change is applied and durable. `publication` is the next cycle; the changes do not wait for it.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChangesApplied"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: With `strict=true`, a change names no item the deployment holds. Nothing was applied. The detail names the change by its index in the request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: With `strict=true`, a change's values name two items. Nothing was applied. The detail names the change by its index in the request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            Nothing was applied.

            - The body is not a JSON array of change items, is over 2 MiB or carries more than 10,000 items
            - An unknown `op`, or an item without `match`
            - No change names its item by an identifying column
            - A `mosaica_id` that is not a decimal string
            - A `match` value that is not one of its field's
            - A `delete` naming a layer by its own `mosaica_id`; the detail names the `DELETE /control/layers/{name}` to send instead
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          description: "`500 fail-closed`: the request was attempted and did not complete. The detail says whether a deletion or suppression in it may be in force. Send the whole request again."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "503":
          description: "`503 not-ready`: the request did not reach the write executor, which is not running. Nothing was applied."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /control/status:
    get:
      tags: ["control: status"]
      operationId: status
      summary: The node's state, its counters and the write routes' limits.
      description: |
        One JSON document, read fresh on every request, including corpus-wide figures such as
        `entity_id_high_water`. Counters count from the process's start.

        `readiness` and `write_executor.ready` report whether the write executor's `posture` is
        `running`, and nothing else. `/readyz` also fails while a partition serves a stepped-down
        manifest, so it can answer `503` while this document reads ready.
      security:
        - controlCredential: []
      responses:
        "200":
          description: The status document.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Status"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"

  /control/flush:
    post:
      tags: ["control: flush"]
      operationId: flush
      summary: Publish buffered writes at the next tick instead of waiting for it.
      description: |
        Pulls the next publication cycle forward and answers `202` without waiting for it; two
        requests before one tick share it. `publication` names the first cycle certain to include
        every write buffered before the request. With `wait=visible` the `202` is held until that
        cycle has run, or until `serve.visible_wait_max_secs` passes. A tick flushes one view's
        buffered rows in one shard, the view and shard holding the row longest in the log. Where
        several hold rows, the cycle stays open, and `publication` does not reach its number,
        until every one of them has been published. A request body is not read.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/Wait"
      responses:
        "202":
          description: The cycle is requested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicationAck"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          description: "`wait` has a value other than `visible`, or the query names another parameter. No cycle was requested."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /control/compact:
    post:
      tags: ["control: compact"]
      operationId: compact
      summary: Ask for a compaction of every shard with something to reclaim.
      description: |
        Sets a flag the write executor reads at its next tick, and answers `202` with the
        request's number. A compaction rewrites one shard with its deleted rows removed, and runs
        for minutes to hours on a large corpus. The executor compacts every shard with something to
        reclaim, one after another. Where no shard has anything to reclaim, the shard the schedule
        ranks first is compacted. At most one compaction is in flight. A request that arrives while
        one runs is held until it lands.

        A compaction can still be refused when its turn comes: for want of memory or disc, while
        the write-ahead log is poisoned, where the shard holds nothing to compact, or because the
        node has since reached one of the states the `503` below names (`overlay_diverged`,
        `prefix_diverged`, `stepped_down`). `GET /control/compact/{request}` reports it.

        The request is answered once every shard it asked for has been compacted, has failed or
        been discarded, or has been refused. `GET /control/compact/{request}` says whether it is
        answered, how, and with which compaction's record.

        If the write executor stops after the `202`, the request is never answered, and
        `write_executor.posture` reads `dead`.

        Where no compaction could start before the node is restarted, the request is refused at
        once with `503` and nothing is requested. The query string is not read.
      security:
        - controlCredential: []
      responses:
        "202":
          description: The compaction is requested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompactionAck"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          description: "`503 not-ready`: nothing was requested. Either the write executor is not running, or this node is in a state that lasts until it is restarted: its deletion overlay no longer matches its log, a compaction was published on disc that it could not switch to, or a partition is serving an older manifest than the newest on disc. The detail says which, and what to do."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /control/compact/{request}:
    get:
      tags: ["control: status"]
      operationId: compactionRequest
      summary: Where one compaction request is, and how it was answered.
      description: |
        A request is pending until every shard it asked for has been compacted, has failed or
        been discarded, or has been refused. While it is pending, `job` is the record of the
        compaction serving it, once a tick has taken the request and while one is in flight, and
        `null` otherwise. Once it is answered, `answer` says how, and `job` is the record of the
        compaction that answered it. A client that waits for a compaction reads this route until
        `answered` is `true`, showing `job` as it goes, and then reads the outcome.

        Requests made before a tick takes them, or while an earlier one is being answered, are
        answered together, and each of them is served the same answer. The server keeps the
        latest 1,024 answers, so a request answered before them is `410`.

        Each run of the server numbers its requests from the wall clock in milliseconds when it
        started, one more for each request, so a number an earlier run gave is below every number
        this run gives. A number below this run's first, or above `compaction.requests.accepted`
        on `/control/status`, is `404`: the request was made before a restart and was lost with
        it. Numbering assumes the clock was not set back between the two runs, and that no run gave
        more numbers than the milliseconds it lasted.
      security:
        - controlCredential: []
      parameters:
        - name: request
          in: path
          required: true
          description: The number `POST /control/compact` answered.
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Where the request is.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompactionRequest"
        "400":
          $ref: "#/components/responses/PathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/Unknown"
        "503":
          description: "`503 not-ready`: the request is pending and the write executor has stopped, so it will not be answered. Restarting the server loses the request; ask for a compaction again after it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "410":
          description: "`410 gone`: the request was answered, and its answer is older than the latest 1,024 the server keeps. `compaction.folds` and `compaction.fold_failures` on `/control/status` count the compactions that published and failed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/Contract"

  /control/attributes:
    put:
      tags: ["control: attributes"]
      operationId: declareAttribute
      x-mosaica-permission: write-all
      summary: Declare an attribute column.
      description: |
        The body is a corpus declaration's `[[attribute]]` block in JSON, without the keys that
        say where a build reads the values. The column is added after every column declared
        before it. From the answer on, a batch may carry the column, `/v1/meta` lists it, and every
        item holds no value in it until one is written.

        The name is the column's identity. Declaring it again with the same fields answers `200`
        and changes nothing; with any field but `unique` different it is `409`. A name a layer
        holds is `422`. A column declared with `render = true` joins every view's points frame at
        once; a point stored before the declaration draws it as no value until a batch gives the
        item one.

        `unique` is the one field a column that exists may change. Declaring it `true` builds the
        column's index over every value stored, and the answer waits for the build, as every write
        on this plane answers once it is in force: `200` once it is in force, `409` naming how many
        values more than one item holds and up to ten of them, and the column is then as it was.
        The build reads each stored value once, so the wait grows with the column's values.
        Ingests and denies are answered while it runs, and no fold starts until it
        ends. A caller that stops waiting does not stop the build, and the same declaration sent
        again answers once it ends. Declaring it `false` drops the index at once and keeps the
        values.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttributeDeclaration"
      responses:
        "200":
          description: The name already carries this declaration, or carried it with `unique` different and now carries it as sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttributeDeclared"
        "201":
          description: The column is declared.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttributeDeclared"
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The name is declared with a different type, vocabulary, analyser, flags or scope, which is written into every row and cannot change; or `unique` was declared on a column in which more than one item holds one value, and the detail says how many values and names up to ten.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field or type
            - `render = true` on a `keyword` or `text` column
            - `unique` on a type other than `keyword`, an integer or `timestamp_us`, or on a group-scoped column
            - A name that is not a valid column name or is a layer's
            - A category with no `vocabulary`, or one naming no vocabulary
            - `vocabulary` on another type
            - `analyser` on a type other than `text`, or an unknown analyser
            - A group scope naming no view group, or a group that takes its views from another
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/shards:
    put:
      tags: ["control: shards"]
      operationId: declareShards
      x-mosaica-permission: write-all
      summary: Declare the shard size.
      description: |
        The body is a corpus declaration's `[shards]` table in JSON. The size is the high water at
        which the open shard seals and the next opens for new items. An edit stays in its item's
        shard and may take a sealed shard past the size. Without a declaration the size is
        4,026,531,840. `GET /control/status` reports it under `shards`.

        A size at or below the open shard's high water seals it, and a sealed shard stays sealed.
        Declaring the size the bundle has answers `200` and changes nothing.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ShardsDeclaration"
      responses:
        "200":
          description: The bundle already has this size.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShardsDeclared"
        "201":
          description: The size is declared.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShardsDeclared"
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field
            - A size that is not a positive integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/vocabularies/{name}:
    put:
      tags: ["control: vocabularies"]
      operationId: declareVocabulary
      x-mosaica-permission: write-all
      summary: Declare a vocabulary, the value set a category column draws on.
      description: |
        The body is a corpus declaration's `[[vocabulary]]` block in JSON, without the keys that
        say where a build reads the values. The server draws every value's code; a body naming a
        `code` is `422`.

        The name, `value_set`, `visibility`, `width` and `reserved` are the vocabulary's identity.
        Declaring it again with the same identity answers `200`, applies `values` as a page, as
        `PATCH /control/vocabularies/{name}/values` does, and changes nothing else. With a
        different identity it is `409`.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/VocabularyName"
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VocabularyDeclaration"
      responses:
        "200":
          description: The name already carries this identity, and the values were applied as a page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyDeclared"
        "201":
          description: The vocabulary is declared with the values sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyDeclared"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The name is declared with a different value set, visibility, width or reserved list. A code's width is written into every row that carries one, so it cannot change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field, such as `code`
            - A name that is not a valid identifier
            - A `width` other than `u8`, `u16` or `u32`
            - A reserved code that is 0 or past the width
            - An empty key, or a key named twice
            - More values than the width holds
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/vocabularies/{name}/values:
    patch:
      tags: ["control: vocabularies"]
      operationId: addVocabularyValues
      summary: Add a page of values to a vocabulary, and set their titles.
      description: |
        Adds each key the vocabulary does not hold, with a code the server draws. A key it holds
        keeps its code. A title sent for a held key replaces the key's title, so a client draws the
        new name while every row carrying the code keeps its meaning. A page removes nothing. A
        page that adds no key and changes no title writes nothing.

        A caller holding `write` and not `write-all` is not told, of a `derived` vocabulary, which
        keys it added.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/VocabularyName"
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VocabularyValues"
      responses:
        "200":
          description: The page is applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyValuesAdded"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: No vocabulary has this name. A page does not create one, since a vocabulary's width and visibility are its declaration's to state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: An unknown field; an empty key, or a key named twice; more values than the vocabulary's width holds. Nothing was added.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/view_groups/{name}:
    put:
      tags: ["control: view groups"]
      operationId: declareViewGroup
      x-mosaica-permission: write-all
      summary: Declare a view group, whose views are created one key at a time.
      description: |
        The body is a corpus declaration's `[[view_group]]` block in JSON, without its roster and
        its source. The group starts with no views; `PUT /control/views/{group}/{key}` creates
        them. A group's name and a plain view's name share one namespace.

        The name is the group's identity. Declaring it again with the same fields answers `200`
        and changes nothing; with any field different it is `409`.
      security:
        - controlCredential: []
      parameters:
        - name: name
          in: path
          required: true
          description: The group's name, which its views' ids begin with.
          schema:
            type: string
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ViewGroupDeclaration"
      responses:
        "200":
          description: The name already carries this declaration. Nothing was written.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewGroupDeclared"
        "201":
          description: The group is declared, with no views.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewGroupDeclared"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "`members` names no view group."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: The name is declared with a different title, frame, projection, visibility, point default, metadata or `members`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field
            - A name that is not a valid key, or a plain view's
            - An unknown projection
            - An extent that is not finite, has a maximum below its minimum, is in degrees on a view with no projection, or is `auto`
            - `members` naming a group that takes its own views from another, or with `metadata`
            - A metadata name declared twice
            - A `category` metadata name with no `vocabulary`, or one naming no vocabulary; `vocabulary` on another type
            - A `visibility` label or point default that is not an access expression
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/views/{name}:
    put:
      tags: ["control: views"]
      operationId: declarePlainView
      x-mosaica-permission: write-all
      summary: Declare a plain view, a coordinate system that belongs to no group.
      description: |
        The body is a corpus declaration's `[[view]]` block in JSON, without its source. The view
        starts with no rows and takes them at its first flush. A plain view's name and a group's
        name share one namespace, and a plain view has no drop route.

        The name is the view's identity. Declaring it again with the same fields answers `200` and
        changes nothing; with a different title, frame, projection, visibility or point default it is
        `409`.
      security:
        - controlCredential: []
      parameters:
        - name: name
          in: path
          required: true
          description: The view's id, as a viewer request names it.
          schema:
            type: string
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlainViewDeclaration"
      responses:
        "200":
          description: The name already carries this declaration. Nothing was written.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlainViewDeclared"
        "201":
          description: The view is declared, with no rows.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlainViewDeclared"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: The name is declared with a different title, frame, projection, visibility or point default.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field
            - A name that is not a valid key, or a view group's
            - An unknown projection
            - An extent that is not finite, has a maximum below its minimum, is in degrees on a view with no projection, or is `auto`
            - A `visibility` label or point default that is not an access expression
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/views/{group}/{key}:
    put:
      tags: ["control: views"]
      operationId: createView
      x-mosaica-permission: write-all
      summary: Create a view of a view group.
      description: |
        The view's id is `<group>:<key>`. It starts with no rows, and takes the group's frame and
        projection. A viewer reaches it only where they satisfy the group's `visibility` and the
        view's own, where one is sent. Through a group that names this one in `members`, the view
        is reached by that group's `visibility` alone, and the view's own does not apply. A session
        authorised before the view was created does not reach it until it authorises again. Its metadata carries one value per name the group
        declares, each typed by its JSON form: a boolean, an integer, a number with a fraction, or
        a string. The record cannot be changed after it is created.

        A key a live view holds with this record answers `200` with that view, and creates
        nothing; with any other record it is `409`. A dropped key may be created again, and the
        new view starts with none of the old one's rows or artifacts.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/ViewGroup"
        - $ref: "#/components/parameters/ViewKey"
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ViewRecord"
      responses:
        "200":
          description: A live view holds the key with this record. Nothing was created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewCreated"
        "201":
          description: The view is created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewCreated"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: No view group has this name.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A live view holds this key with another record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field
            - A key that is not valid
            - A metadata value that is null, an array or an object, or that does not match the type the group declares
            - A `category` metadata key its `closed` vocabulary does not list
            - A metadata name the group does not declare, or one it declares left out
            - A `visibility` label that is not an access expression
            - A group that takes its views from another
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"
    delete:
      tags: ["control: views"]
      operationId: dropView
      x-mosaica-permission: write-all
      summary: Drop a view of a view group and free its key.
      description: |
        The view leaves every roster at once, including the rosters of groups sharing its views,
        and its rows stay on disc, unreachable, until each shard's compaction. The artifacts scoped
        to it are deleted, as `POST /control/changes` deletes an artifact: they are unreachable
        with it and their keys are free at once. Rows buffered for it and not yet flushed are
        discarded.

        Every item the drop leaves with a row in no view, flushed or buffered, is deleted, as
        `POST /control/changes` deletes it, and `deleted` counts them. A deleted item names
        nothing afterwards: a later row carrying one of its unique values creates a new item, and
        one carrying its `mosaica_id` is refused.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/ViewGroup"
        - $ref: "#/components/parameters/ViewKey"
        - $ref: "#/components/parameters/Wait"
      responses:
        "200":
          description: The view is dropped.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewDropped"
        "400":
          $ref: "#/components/responses/PathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: No live view of this group holds this key, or no group has this name.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            - `wait` has a value other than `visible`
            - The query names another parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/layers:
    put:
      tags: ["control: layers"]
      operationId: registerLayer
      x-mosaica-permission: write-all
      summary: Register an annotation layer.
      description: |
        The body is a corpus declaration's `[[layer]]` block in JSON. `views` may name a view
        group, which stands for every view the group holds, and for each view created in it later.
        The answer follows the write-ahead log's fsync and carries the layer's `mosaica_id`, the
        address `POST /control/changes` suppresses it by.

        A name already registered with this declaration, as written, answers `200` with that layer,
        and registers nothing; with any other declaration it is `422`. A dropped layer's name may
        be registered again. A refusal means the layer does not exist.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/Wait"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LayerDeclaration"
      responses:
        "200":
          description: A layer of this name already holds this declaration. Nothing was registered.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LayerRegistered"
        "201":
          description: The layer is registered, with no artifacts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LayerRegistered"
        "400":
          $ref: "#/components/responses/NotJson"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: Every one of the 2³¹ layer spaces has been given to a layer, or the layer declares more levels than one layer space holds. Nothing was registered. The detail gives the figures.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "413":
          $ref: "#/components/responses/DeclarationTooLarge"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: |
            - An unknown field
            - A name that is empty or `all`, or registered with another declaration
            - A view that is neither a view nor a view group
            - A view outside the group a group-scoped layer names
            - A `depends_on` layer that is not registered, or the layer itself
            - Levels on a `nested` or `dag` layer, none on a `stacked` or `tiered` one, levels not numbered densely from 0, or a level whose `zoom` range holds no depth
            - A `fraction` outside (0, 1], or on a `spatial` or attribute layer
            - `shape` on a layer that is not `spatial`
            - Two drawn geometries
            - Content, dependencies, levels, labels or a layout on an attribute layer
            - An unknown computed property
            - A repeated view, level title or content name
            - A label that is not an access expression
            - A `spatial` layer over both projected and unprojected views
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/layers/{name}:
    delete:
      tags: ["control: layers"]
      operationId: dropLayer
      x-mosaica-permission: write-all
      summary: Drop a layer.
      description: |
        The layer and its artifacts stop being served at once. Its name may be registered again, as
        a new layer: none of the dropped layer's `mosaica_id`s names the new layer or its artifacts.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/LayerName"
        - $ref: "#/components/parameters/Wait"
      responses:
        "200":
          description: The layer is dropped.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicationAck"
        "400":
          $ref: "#/components/responses/PathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          description: |
            - No layer is registered under this name
            - `wait` has a value other than `visible`
            - The query names another parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/layers/{name}/artifacts:
    put:
      tags: ["control: artifacts"]
      operationId: publishArtifacts
      summary: Publish artifacts into one level of a layer, all or none.
      description: |
        The body is JSON only; `application/vnd.apache.arrow.stream` is `422`. Each artifact names
        its members by `members`, or by `excluding`, the view's items it leaves out. Each is a
        `MemberTable`: one row per member, naming it by `mosaica_id` and the values of fields
        declared unique. Members are resolved where the publication applies, so a member names the
        item it names then, however edits and compactions have moved it since the request
        arrived. A member whose values name no item, a deleted item included, or name two, is
        refused: it is left out and listed in `refused` with its reason, and the rest are
        published. With `strict=true` the request is refused at its first refused member instead,
        `404` where it names no item and `409` where it names two. A content's `generated_from`
        is refused whole at any refused member, strict or not, since a viewer must see the whole
        set to be served the content. A caller holding `write` and not `write-all` names only the
        items its terms reach: a member outside them names nothing, as an absent one does, and
        `excluding` leaves out of the view's items those outside its terms as well.

        **The writer's terms, for layers and artifacts.** To a caller holding `write` and not
        `write-all`, a layer whose `visibility` its terms do not satisfy is one not registered, and
        an artifact is outside its terms unless `POST /v1/artifacts` would serve it to a session
        holding them, suppression aside. A parent or an attachment target outside is answered as
        one no artifact is. A key an artifact outside holds is `409`; nothing else of the holder is
        said. An artifact whose `access` labels the terms do not satisfy is refused alone: it is
        listed in `refused` as `{artifact, reason: label_not_held}`, its `mosaica_id` is `null`,
        and the others are published; with `strict=true` the request is `409` instead.

        An artifact whose `key` the level already holds is not created again: its members are
        added to the held membership, and a part it lacks (its parents, attachment, contents,
        shape or access labels) is filled. A part it holds with a different value is `409`, and the
        detail never names the held value. A content is filled at its position in `content`, which
        is its rank. A content at a rank holding none, withdrawn or past the last, is a new content
        at the next free rank, with the generating set it was sent with, since a withdrawn rank is
        never taken again. A deleted artifact's key is held by no artifact, so a publication under
        it creates a new one. The answer is `201` if the batch created an
        artifact, and `200` if every key was held.

        A large membership is published with its first page here and the rest with `PATCH`.
        Per request: at most `ingest.max_artifacts_per_request` artifacts, `excluding` at most
        `ingest.max_excluded_per_request` long, a body of at most `ingest.publish_max_body_bytes`,
        and a shape of at most `serve.max_shape_vertices` vertices after it is fitted to each view.
        Over any is `422`; `/control/status` publishes them under `limits.publish`.

        The answer gives each artifact's `mosaica_id` in the order sent, and never an ordinal or a
        membership's size. `shapes` reports what fitting each shape to each of the layer's views
        did.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/LayerName"
        - $ref: "#/components/parameters/Wait"
        - $ref: "#/components/parameters/Strict"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishRequest"
      responses:
        "200":
          description: Every key was held; the batch added members or filled parts, or changed nothing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactsPublished"
        "201":
          description: The batch created at least one artifact.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ArtifactsPublished"
        "400":
          $ref: "#/components/responses/PathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: A member of a content's generating set, or with `strict=true` any member, names nothing the deployment holds. The detail names it by its artifact, list and row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: A held artifact holds a part with a different value, or a second `excluding` for a held key; a key is held by an artifact outside the caller's terms; a member of a content's generating set, or with `strict=true` any member, names two items, or an artifact sets an access label the caller's terms do not satisfy; or the artifacts the batch creates need more ids than the level's layer space has left, which the detail gives. Nothing was written.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            - The body is not JSON, is over its cap or is Arrow
            - An unknown field
            - No artifacts, or more than the cap
            - An artifact with neither `members` nor `excluding` that attaches to nothing, or with both
            - An exclusion list over its cap
            - `view` missing on a layer scoped to a group or present on another
            - A member table whose columns differ in length, or tables left with no identifying column
            - A `mosaica_id` that is not a decimal string, or a value that is not one of its field's
            - An unknown layer or level, a layer the caller does not reach included, or a layer whose membership is an attribute
            - A shape on a layer that declares none, none on one that does, the wrong kind, or one over the vertex cap
            - Labels the layer does not take, or none where it requires them
            - A parent or attachment that does not exist, was deleted, or is outside the caller's terms
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"
    patch:
      tags: ["control: artifacts"]
      operationId: growMemberships
      summary: Add members to artifacts a level holds, remove them from generating sets, and fill missing parts.
      description: |
        Each row names an artifact by the key it was published under; an unknown key, a deleted
        artifact's included, is `422`, and this verb creates nothing. `members` joins the
        membership. With a `rank`, `members` and `leaving` move the generating set of the content
        at that rank instead, and a page that empties the set withdraws the content, reported as
        `withdrawn`. A rank is a content's position, and keeps its number when a content before it
        is withdrawn; a withdrawn rank names no content, and a page naming one is `422`. A part the
        artifact lacks is filled; one it holds with a different value is `409`. A content at a
        rank holding none, withdrawn or past the last, is filled as a new content at the next free
        rank. The batch commits whole. Only an `enumerated` layer's artifacts can be grown; a
        `spatial` or attribute layer is `422`.

        `members` and `leaving` are `MemberTable`s, as on a publication. A member whose values name
        no item, or name two, is left out and listed in `refused`; with `strict=true` the request
        is `404` or `409` at the first instead. To a caller without `write-all`, as on a
        publication, a member outside its terms names nothing, a key naming an artifact outside
        them is the unknown key's `422`, and a row filling an access label its terms do not satisfy
        is refused alone. On a row with a `rank`, a member joining the generating set and naming no
        item, or two, refuses the request, strict or not, as a publication's `generated_from`
        does.

        The body is JSON, or an Arrow IPC stream as `application/vnd.apache.arrow.stream`. The
        Arrow form carries one row per artifact: `key` as `utf8`, `members` as `list<struct>` or
        `large_list<struct>` whose struct fields are the member table's columns, each named once:
        `mosaica_id` as `utf8`, `large_utf8` or an unsigned integer (as a read sends it), a unique
        field as `utf8` or `large_utf8` or, for an integer or timestamp field, as a signed or
        unsigned integer of any width or as `timestamp[us]`, read as its microseconds since the
        epoch. Any other type in those columns is `422`, a timestamp in another unit included; cast
        it to `timestamp[us]`. Any other column is ignored whatever its type. Optionally `view` as
        `utf8` and `access` in any form ingest's `access` takes, with `level` in the schema's
        metadata as a string. It carries joining members and access labels only; every other part travels as
        JSON. Any other column or metadata key is `422`.

        At most `ingest.max_members_per_request` members, joining and leaving together, and a body
        of at most `ingest.publish_max_body_bytes`; over either is `422`, and `/control/status`
        publishes both under `limits.grow`. The answer never carries an ordinal or a membership's
        size.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/LayerName"
        - $ref: "#/components/parameters/Wait"
        - $ref: "#/components/parameters/Strict"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GrowRequest"
          application/vnd.apache.arrow.stream:
            schema:
              type: string
              format: binary
              description: One row per artifact, with the envelope in the schema's metadata, as this route describes.
      responses:
        "200":
          description: The batch is applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MembershipsGrown"
        "400":
          $ref: "#/components/responses/PathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: With `strict=true`, a member names nothing the deployment holds. The detail names it by its artifact, list and row.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: An artifact holds a part with a different value, or with `strict=true` a member names two items or a row fills an access label the caller's terms do not satisfy. Nothing was written.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          description: |
            - The body cannot be read or is over its cap
            - An unknown field, column or metadata key
            - No artifacts
            - More members than the cap
            - An unknown layer, level or key, a deleted artifact's key, a layer the caller does not reach and an artifact outside its terms included
            - A layer whose membership is not `enumerated`
            - `leaving` without a `rank`, or a `rank` naming no content, a withdrawn one included
            - `view` missing on a layer scoped to a group or present on another
            - A member table whose columns differ in length, or tables left with no identifying column
            - A `mosaica_id` that is not a decimal string, or a value that is not one of its field's
            - A shape the layer does not take
            - A content without a generating set on a layer whose content requires every member visible
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/Backpressure"
        "500":
          $ref: "#/components/responses/WriteFailed"
        "503":
          $ref: "#/components/responses/ExecutorNotReady"

  /control/principals:
    get:
      tags: ["control: identity"]
      operationId: listPrincipals
      summary: "Every local principal."
      security:
        - controlCredential: []
      responses:
        "200":
          description: "The principals, by name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrincipalList"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/FailClosed"
    post:
      tags: ["control: identity"]
      operationId: createPrincipal
      summary: "Create a local principal, a person or a service."
      description: |
        A new principal holds no password, no key, no term and no permission.
      security:
        - controlCredential: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PrincipalCreate"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: "A principal of that name exists."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The name is empty or holds a control character, or `kind` is not `person` or `service`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/principals/{name}:
    get:
      tags: ["control: identity"]
      operationId: showPrincipal
      summary: "One local principal: its kind, flags, terms, permissions and groups."
      description: |
        The terms and permissions are those granted to the principal directly; its groups' are listed with each group.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      responses:
        "200":
          description: "The principal."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PrincipalRecord"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    patch:
      tags: ["control: identity"]
      operationId: changePrincipal
      summary: "Disable or enable a principal."
      description: |
        Disabling a principal ends its sessions and those minted with its keys, and no credential
        of it is accepted until it is enabled.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PrincipalChange"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The body sets neither field."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    delete:
      tags: ["control: identity"]
      operationId: deletePrincipal
      summary: "Delete a principal with its password, API keys, grants and memberships."
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/principals/{name}/password:
    put:
      tags: ["control: identity"]
      operationId: setPassword
      summary: "Set a principal's password, replacing any previous one."
      description: |
        A password shorter than `[catalogue] min_password_length` is refused. The principal's
        sessions end.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PasswordSet"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The password is shorter than the configured minimum."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    delete:
      tags: ["control: identity"]
      operationId: clearPassword
      summary: "Remove a principal's password, so no password authenticates it."
      description: |
        The principal's sessions end where it had a password.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/principals/{name}/keys:
    get:
      tags: ["control: identity"]
      operationId: listKeys
      summary: "A principal's API keys, by prefix, without their secrets."
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      responses:
        "200":
          description: "The keys."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KeyList"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    post:
      tags: ["control: identity"]
      operationId: createKey
      summary: "Issue an API key for a principal."
      description: |
        The whole key, `msk_<prefix>_<secret>`, is in this answer and nowhere else; the catalogue
        keeps its SHA-256. With `permissions`, a credential made from the key holds at most those,
        and never more than its principal holds. With `expires_at`, the key and every session
        authorised with it end then.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/PrincipalName"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KeyCreate"
      responses:
        "200":
          description: "The key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IssuedKey"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "A permission is not one of `read`, `write`, `authorise-as`, `admin`, `read-all` and `write-all`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/keys/{prefix}:
    delete:
      tags: ["control: identity"]
      operationId: revokeKey
      summary: "Revoke an API key."
      description: |
        Every session authorised with the key ends, including those minted with it through
        `authorise-as`.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/KeyPrefix"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No key has the prefix."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/groups:
    get:
      tags: ["control: identity"]
      operationId: listGroups
      summary: "Every local group."
      security:
        - controlCredential: []
      responses:
        "200":
          description: "The groups, by name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupList"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/FailClosed"
    post:
      tags: ["control: identity"]
      operationId: createGroup
      summary: "Create a local group."
      security:
        - controlCredential: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupCreate"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: "A group of that name exists."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The name is empty or holds a control character."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/groups/{name}:
    get:
      tags: ["control: identity"]
      operationId: showGroup
      summary: "One group: its terms, permissions and members."
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/GroupName"
      responses:
        "200":
          description: "The group."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupRecord"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No group has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    delete:
      tags: ["control: identity"]
      operationId: deleteGroup
      summary: "Delete a group with its grants and memberships."
      description: |
        Its members' sessions end, and every session authorised through a provider with a role
        mapping to it.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/GroupName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No group has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/groups/{name}/members/{principal}:
    put:
      tags: ["control: identity"]
      operationId: addMember
      summary: "Add a principal to a group."
      description: |
        The principal's sessions end where it was not a member.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/GroupName"
        - $ref: "#/components/parameters/MemberName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No group or no principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    delete:
      tags: ["control: identity"]
      operationId: removeMember
      summary: "Remove a principal from a group."
      description: |
        The principal's sessions end where it was a member.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/GroupName"
        - $ref: "#/components/parameters/MemberName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No group or no principal has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/grants:
    post:
      tags: ["control: identity"]
      operationId: grant
      summary: "Grant terms or a permission to a principal or a group."
      description: |
        A grant already held changes nothing and ends no session. Otherwise the sessions of the
        principal, or of every member of the group and of every identity a role mapping gives it,
        end, so they pick up the grant when they authorise again.
      security:
        - controlCredential: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Grant"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal or no group has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The body does not name exactly one grantee and one grant, a term is `public` or holds a control character, or the permission is unknown."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/grants/revoke:
    post:
      tags: ["control: identity"]
      operationId: revokeGrant
      summary: "Revoke terms or a permission from a principal or a group."
      description: |
        A grant not held changes nothing and ends no session. Otherwise the sessions it affected end.
      security:
        - controlCredential: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Grant"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No principal or no group has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The body does not name exactly one grantee and one grant, or the permission is unknown."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/providers:
    get:
      tags: ["control: identity"]
      operationId: listProviders
      summary: "Every OIDC provider, declared through the API or in `mosaica.toml`."
      security:
        - controlCredential: []
      responses:
        "200":
          description: "The providers, by name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderList"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/providers/{name}:
    get:
      tags: ["control: identity"]
      operationId: showProvider
      summary: "One OIDC provider."
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/ProviderName"
      responses:
        "200":
          description: "The provider."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderRecord"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No provider has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    put:
      tags: ["control: identity"]
      operationId: putProvider
      summary: "Declare an OIDC provider, or replace one whole."
      description: |
        A `jwks_url` is `https`, or `http` to `localhost`, `127.0.0.1` or `[::1]`, unless the
        service runs with `MOSAICA_ALLOW_INSECURE_JWKS=1`. A claim rule whose template could produce
        only `public`, or holds a control character, is refused. Replacing a provider ends every
        session authorised through it.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/ProviderName"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProviderDeclaration"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: "The provider is declared in `mosaica.toml`; change it there and restart. Or another provider has the same issuer and audience, so a token from either would be accepted by both."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "A field is empty, the JWKS URL is refused, or a claim path or template is malformed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"
    delete:
      tags: ["control: identity"]
      operationId: dropProvider
      summary: "Remove an OIDC provider."
      description: |
        Every session authorised through it ends.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/ProviderName"
      responses:
        "200":
          description: "Committed, and every session it affected has ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: "No provider has the name."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: "The provider is declared in `mosaica.toml`; remove it there and restart."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/sessions:
    get:
      tags: ["control: identity"]
      operationId: listSessions
      summary: "Live sessions: all of them, a local principal's, or a provider's."
      description: |
        A local principal's sessions include those minted for it through `authorise-as`.
      security:
        - controlCredential: []
      parameters:
        - $ref: "#/components/parameters/SessionsPrincipal"
        - $ref: "#/components/parameters/SessionsProvider"
      responses:
        "200":
          description: "The sessions, by `token_id`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionList"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          description: "Both `principal` and `provider` are sent, or the query names another parameter."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

  /control/sessions/end:
    post:
      tags: ["control: identity"]
      operationId: endSessions
      summary: "End one session by `token_id`, or every session of a local principal or a provider."
      description: |
        An ended session's token is answered `403 expired-token`.
      security:
        - controlCredential: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SessionsEnd"
      responses:
        "200":
          description: "The sessions have ended. The body says how many."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CatalogueChange"
        "400":
          $ref: "#/components/responses/NotJsonOrPathNotUtf8"
        "401":
          $ref: "#/components/responses/ControlBadCredential"
        "403":
          $ref: "#/components/responses/Forbidden"
        "415":
          $ref: "#/components/responses/NotJsonContentType"
        "422":
          description: "The body does not name exactly one of `token_id`, `principal` and `provider`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/FailClosed"

components:
  securitySchemes:
    authoriseAsKey:
      type: http
      scheme: bearer
      description: "An API key, `msk_<prefix>_<secret>`, whose principal holds `authorise-as`, or the operator credential, which authenticates the superuser. It mints a session for any principal, so it belongs to an integrator's backend and is never handed to a browser. A key that is not accepted is `401`; an accepted key without `authorise-as` is `403 forbidden`. Checked before the body is read. An open server needs none (`info.description`)."
    controlCredential:
      type: http
      scheme: bearer
      description: "The operator credential, which authenticates the built-in superuser holding every permission; an API key; or an OIDC access token, whose permissions come from its provider's role mappings. Every path on the control listener requires one, checked before the request's query or body is read, so a caller without one is answered `401` ahead of any other refusal. Writes, flush and compaction need `write`; a declaration and a drop, the operations marked `x-mosaica-permission: write-all`, need `write-all` beside it; status and the catalogue's verbs need `admin`. A credential without them is `403 forbidden`. What a `write` caller may name is bounded by its terms (`Permission`). An open server needs none (`info.description`)."
    sessionToken:
      type: http
      scheme: bearer
      description: "The `token` `POST /v1/login` or `POST /session/authorise` minted. A missing, malformed or unrecognised token is `401 bad-credential`; a recognised token whose session has ended, by its deadline, a revocation, a logout or a catalogue change, is `403 expired-token`, and `401` once the registry has swept it. Either means the session has ended. An open server needs none (`info.description`)."

  parameters:
    PrincipalName:
      name: name
      in: path
      required: true
      description: The local principal's name. A `/` in it is sent as `%2F`.
      schema:
        type: string
    MemberName:
      name: principal
      in: path
      required: true
      description: The local principal's name. A `/` in it is sent as `%2F`.
      schema:
        type: string
    GroupName:
      name: name
      in: path
      required: true
      description: The group's name. A `/` in it is sent as `%2F`.
      schema:
        type: string
    KeyPrefix:
      name: prefix
      in: path
      required: true
      description: The API key's prefix, as the key listing names it.
      schema:
        type: string
    ProviderName:
      name: name
      in: path
      required: true
      description: The provider's name.
      schema:
        type: string
    SessionsPrincipal:
      name: principal
      in: query
      required: false
      description: Only this local principal's sessions, including those minted for it.
      schema:
        type: string
    SessionsProvider:
      name: provider
      in: query
      required: false
      description: Only the sessions authorised through this provider.
      schema:
        type: string
    MosaicaId:
      name: mosaica_id
      in: path
      required: true
      description: The item's or artifact's opaque identifier, a `u64` in decimal. It is a keyed permutation of the internal identity that blinds it, under a key generated when the bundle was built. It is stable across sessions and restarts, and a rebuilt bundle issues new ones. It is not encryption.
      schema:
        type: string
        pattern: "^[0-9]+$"
    Wait:
      name: wait
      in: query
      required: false
      description: "`visible` holds the answer until the publication cycle this write is in has run, or until `serve.visible_wait_max_secs` passes, and the answer's `visible` says which. Absent answers at once. Any other value, or a query parameter the route does not define, is `422`."
      schema:
        type: string
        enum: [visible]
    Strict:
      name: strict
      in: query
      required: false
      description: "`true` refuses the whole request at its first refused row, and nothing is written. Absent or `false`, the request applies the rows the identity rule accepts and lists each refused one in the answer's `refused`, with its reason."
      schema:
        type: boolean
        default: false
    BatchId:
      name: x-mosaica-batch-id
      in: header
      required: true
      description: The batch's identity among the caller's batches, chosen by the caller, for retries. Another principal's batch under the same id is another batch. The same body sent again by the same caller under the same id is answered as it was and changes nothing; a different body under an id the caller has used is `409`. The id is remembered while the log holds the batch's record, and past that for `ingest.flush_max_age_secs` from its acceptance, or from the restart that read its record, or until `ingest.flush_max_items` rows of later batches are accepted, whichever ends first; a retry after that is a new batch. Missing, or not UTF-8, is `422`.
      schema:
        type: string
    WriteView:
      name: x-mosaica-view
      in: header
      required: false
      description: The view the batch writes into, by id. Required where the deployment has more than one view. A name no view has is `404`; a value that is not UTF-8 is `422`.
      schema:
        type: string
    VocabularyName:
      name: name
      in: path
      required: true
      description: The vocabulary's name, as an attribute's `vocabulary` names it.
      schema:
        type: string
    ViewGroup:
      name: group
      in: path
      required: true
      description: The view group's name.
      schema:
        type: string
    ViewKey:
      name: key
      in: path
      required: true
      description: The view's key within its group. The view's id is `<group>:<key>`.
      schema:
        type: string
    LayerName:
      name: name
      in: path
      required: true
      description: The layer's name. A `/` in it is sent as `%2F`.
      schema:
        type: string

  responses:
    ControlBadCredential:
      description: "`401 bad-credential`: the credential is missing, or is not the operator credential, an accepted API key or an accepted OIDC access token. Nothing about the request was read."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotJson:
      description: The JSON body does not parse. The HTTP framework answers this before the handler runs, with a plain-text body.
      content:
        text/plain:
          schema:
            type: string
    PathNotUtf8:
      description: A path segment is not UTF-8 once percent-decoded. The HTTP framework answers this before the handler runs, with a plain-text body.
      content:
        text/plain:
          schema:
            type: string
    NotJsonOrPathNotUtf8:
      description: The JSON body does not parse, or a path segment is not UTF-8 once percent-decoded. The HTTP framework answers this before the handler runs, with a plain-text body.
      content:
        text/plain:
          schema:
            type: string
    NotJsonContentType:
      description: "The request does not carry `content-type: application/json`. The HTTP framework answers this before the handler runs, with a plain-text body."
      content:
        text/plain:
          schema:
            type: string
    DeclarationTooLarge:
      description: The body is over 2 MiB, the declaration routes' cap, published on `/control/status` as `limits.declarations.max_body_bytes`. The HTTP framework answers this before the handler runs, with a plain-text body.
      content:
        text/plain:
          schema:
            type: string
    WriteFailed:
      description: "`500 fail-closed`: the write could not be made durable, or the write executor stopped while holding it. It may have been applied in full, so this is not a refusal; the detail says so. Sending the same request again is safe."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ExecutorNotReady:
      description: "`503 not-ready`: the write executor is not running. Nothing was taken."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    BadCredential:
      description: "`401 bad-credential`: the credential is missing, malformed or not accepted. On a session token that worked before, the session ended and has since been swept, and a client treats it as it treats `403`."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: "`403 forbidden`: the credential was accepted and does not hold the permission the route needs. The detail names what is missing."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ExpiredToken:
      description: "`403 expired-token`: the token is recognised and its session has ended: `expires_at` has passed, it was revoked or logged out, or a catalogue change affected what authorised it. Authorise again."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unknown:
      description: "`404 unknown`: nothing this principal may see by that identifier. The answer is the same whether or not something exists under it."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Contract:
      description: "`422 contract`: the request is wrong. Change it before sending it again."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Backpressure:
      description: "`429 backpressure`: the whole request was shed, and nothing was answered in part. Send it again unchanged after `Retry-After`, which agrees with the body's `retry_after_s`."
      headers:
        Retry-After:
          required: true
          description: Seconds. `1` for compute admission and for a request waiting on another's build of the same projection or fragment; the control plane derives its figure from the write executor's service time. Read it rather than assuming it.
          schema:
            type: string
            pattern: "^[0-9]+$"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/BackpressureError"
    FailClosed:
      description: "`500 fail-closed`: the server could not compute the answer, for example a mask or a composition failed. No partial result is sent."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    Error:
      type: object
      description: The one error envelope, on every plane. The code list is closed.
      additionalProperties: false
      required: [error, detail]
      properties:
        error:
          type: string
          enum:
            - bad-credential
            - expired-token
            - forbidden
            - unknown
            - conflict
            - contract
            - backpressure
            - fail-closed
            - not-ready
            - gone
        detail:
          type: string
          description: Human-readable. On the viewer plane a `404`'s detail is the same for every reason, so it tells a caller nothing about what exists. On the control plane it says what was refused. It may name the caller's own batch id or values, as the ingest route's `409`s do, and otherwise names a row or member by its position in the batch.
        retry_after_s:
          type: integer
          minimum: 0
          description: Present on every `429` and on nothing else, and equal to the `Retry-After` header.

    BackpressureError:
      description: An `Error` whose code is `backpressure` and whose `retry_after_s` is present.
      allOf:
        - $ref: "#/components/schemas/Error"
        - type: object
          required: [retry_after_s]
          properties:
            error:
              const: backpressure

    AuthoriseRequest:
      type: object
      additionalProperties: false
      description: Exactly one of the four fields.
      properties:
        principal:
          type: string
          description: The local principal to act as, by name.
        access_token:
          type: string
          description: The access token of the OIDC identity to act as, as its provider issued it.
        terms:
          type: array
          description: "The terms the session holds, with `read` and no other permission, for no principal of the catalogue. Accepted with the operator credential alone; an API key sending it is `403 forbidden`. Each term is trimmed, and one that is empty, holds a control character or is `public` in any case is dropped; every session holds `public`. No catalogue change ends the session."
          items:
            type: string
        read_all:
          const: true
          description: "A session of the superuser itself, holding `read` and `read-all`. It satisfies every index key, including one promoted after it was minted, so its authorised set is every item the corpus holds, and an item a flush places under a new term or a new label joins it from that publication. Deletions and suppressions still apply, and it satisfies every view's, group's, layer's and artifact's own label. Accepted with the operator credential alone; an API key sending it is `403 forbidden`."
      oneOf:
        - required: [principal]
          not:
            anyOf:
              - required: [access_token]
              - required: [terms]
              - required: [read_all]
        - required: [access_token]
          not:
            anyOf:
              - required: [principal]
              - required: [terms]
              - required: [read_all]
        - required: [terms]
          not:
            anyOf:
              - required: [principal]
              - required: [access_token]
              - required: [read_all]
        - required: [read_all]
          not:
            anyOf:
              - required: [principal]
              - required: [access_token]
              - required: [terms]

    AuthoriseResponse:
      type: object
      additionalProperties: false
      required: [token, token_id, expires_at]
      properties:
        token:
          type: string
          description: "The session token, sent as `Authorization: Bearer <token>` on the viewer plane. The server does not log it, and the session plane does not take it back."
        token_id:
          type: integer
          minimum: 0
          description: The handle `POST /session/revoke` and `POST /control/sessions/end` take, which grants nothing.
        expires_at:
          type: integer
          minimum: 0
          description: Seconds since the Unix epoch. The earliest of the time of the request plus `token_max_lifetime`, the minting key's expiry and the target access token's `exp`.

    RevokeRequest:
      type: object
      additionalProperties: false
      required: [token_id]
      properties:
        token_id:
          type: integer
          minimum: 0

    LoginRequest:
      type: object
      additionalProperties: false
      description: Exactly one credential, named by its key.
      minProperties: 1
      maxProperties: 1
      properties:
        password:
          type: object
          additionalProperties: false
          required: [principal, password]
          properties:
            principal:
              type: string
            password:
              type: string
        api_key:
          type: string
          description: "`msk_<prefix>_<secret>`, as `POST /control/principals/{name}/keys` issued it."
        access_token:
          type: string
          description: An OIDC access token, as a declared provider issued it.

    LoginResponse:
      type: object
      additionalProperties: false
      required: [token, expires_at]
      properties:
        token:
          type: string
          description: "The session token, sent as `Authorization: Bearer <token>` on the viewer plane."
        expires_at:
          type: integer
          minimum: 0
          description: Seconds since the Unix epoch. The earliest of the time of the request plus `token_max_lifetime`, the key's expiry and the access token's `exp`.

    Permission:
      type: string
      enum: [read, write, authorise-as, admin, read-all, write-all]
      description: "`write` writes, flushes and compacts, naming only the items, layers, views and artifacts a session holding the principal's terms would be served, suppressed items included; anything else answers as absent. `write-all` lifts that bound, so its writes name every one, and declares and drops."

    CatalogueChange:
      type: object
      additionalProperties: false
      required: [sessions_ended]
      properties:
        sessions_ended:
          type: integer
          minimum: 0
          description: How many live sessions the change ended.

    PrincipalCreate:
      type: object
      additionalProperties: false
      required: [name, kind]
      properties:
        name:
          type: string
          description: Trimmed. Case-sensitive, and refused when empty or holding a control character.
        kind:
          type: string
          enum: [person, service]

    PrincipalChange:
      type: object
      additionalProperties: false
      required: [disabled]
      properties:
        disabled:
          type: boolean

    PrincipalRecord:
      type: object
      additionalProperties: false
      required: [name, kind, disabled, has_password, terms, permissions, groups]
      properties:
        name:
          type: string
        kind:
          type: string
          enum: [person, service]
        disabled:
          type: boolean
        has_password:
          type: boolean
        terms:
          type: array
          description: Granted to the principal directly.
          items:
            type: string
        permissions:
          type: array
          description: Granted to the principal directly.
          items:
            $ref: "#/components/schemas/Permission"
        groups:
          type: array
          items:
            type: string

    PrincipalList:
      type: object
      additionalProperties: false
      required: [principals]
      properties:
        principals:
          type: array
          items:
            $ref: "#/components/schemas/PrincipalRecord"

    PasswordSet:
      type: object
      additionalProperties: false
      required: [password]
      properties:
        password:
          type: string

    KeyCreate:
      type: object
      additionalProperties: false
      properties:
        expires_at:
          type: integer
          minimum: 0
          description: Seconds since the Unix epoch. Absent, the key does not expire.
        permissions:
          type: array
          description: Absent, the key holds its principal's permissions.
          items:
            $ref: "#/components/schemas/Permission"

    IssuedKey:
      type: object
      additionalProperties: false
      required: [prefix, key]
      properties:
        prefix:
          type: string
          description: The key's public identity, which listing and revoking use.
        key:
          type: string
          description: "The whole key, `msk_<prefix>_<secret>`. Shown here once."

    KeyRecord:
      type: object
      additionalProperties: false
      required: [prefix, principal, created_at, expires_at, permissions]
      properties:
        prefix:
          type: string
        principal:
          type: string
        created_at:
          type: integer
          minimum: 0
        expires_at:
          type: [integer, "null"]
          minimum: 0
        permissions:
          type: [array, "null"]
          items:
            $ref: "#/components/schemas/Permission"

    KeyList:
      type: object
      additionalProperties: false
      required: [keys]
      properties:
        keys:
          type: array
          items:
            $ref: "#/components/schemas/KeyRecord"

    GroupCreate:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          type: string

    GroupRecord:
      type: object
      additionalProperties: false
      required: [name, terms, permissions, members]
      properties:
        name:
          type: string
        terms:
          type: array
          items:
            type: string
        permissions:
          type: array
          items:
            $ref: "#/components/schemas/Permission"
        members:
          type: array
          items:
            type: string

    GroupList:
      type: object
      additionalProperties: false
      required: [groups]
      properties:
        groups:
          type: array
          items:
            $ref: "#/components/schemas/GroupRecord"

    Grant:
      type: object
      additionalProperties: false
      description: Exactly one of `principal` and `group`, and exactly one of `term`, `terms` and `permission`.
      properties:
        principal:
          type: string
        group:
          type: string
        term:
          type: string
          description: Trimmed. `public` in any case, an empty term and a control character are refused.
        terms:
          type: array
          description: Many terms, granted or revoked in one change, each read as `term` is. One refused term refuses them all.
          items:
            type: string
        permission:
          $ref: "#/components/schemas/Permission"
      allOf:
        - oneOf:
            - required: [principal]
              not:
                required: [group]
            - required: [group]
              not:
                required: [principal]
        - oneOf:
            - required: [term]
              not:
                anyOf:
                  - required: [terms]
                  - required: [permission]
            - required: [terms]
              not:
                anyOf:
                  - required: [term]
                  - required: [permission]
            - required: [permission]
              not:
                anyOf:
                  - required: [term]
                  - required: [terms]

    ClaimRule:
      type: object
      additionalProperties: false
      required: [claim, template]
      properties:
        claim:
          type: string
          description: "A claim path: dot-separated object keys, a key followed by `[*]` taking every element of an array, a key holding a dot or bracket written in double quotes."
        template:
          type: string
          description: "The term produced from each value, holding `{value}` at most once, such as `group:{value}`."

    RoleMapping:
      type: object
      additionalProperties: false
      required: [claim, value, group]
      properties:
        claim:
          type: string
        value:
          type: string
          description: Matched exactly, whole, and only in the claim named.
        group:
          type: string
          description: The local group whose terms and permissions an identity with the value receives, `read-all` and `write-all` among them.

    ProviderDeclaration:
      type: object
      additionalProperties: false
      required: [issuer, audience, jwks_url]
      properties:
        issuer:
          type: string
        audience:
          type: string
          description: "The `aud` an access token must hold. Name the API, not a client, so that an ID token, whose `aud` is the client's id, is refused. No two providers have the same issuer and audience."
        jwks_url:
          type: string
        claim_rules:
          type: array
          items:
            $ref: "#/components/schemas/ClaimRule"
        role_mappings:
          type: array
          items:
            $ref: "#/components/schemas/RoleMapping"

    ProviderRecord:
      type: object
      additionalProperties: false
      required: [name, issuer, audience, jwks_url, claim_rules, role_mappings, read_only]
      properties:
        name:
          type: string
        issuer:
          type: string
        audience:
          type: string
        jwks_url:
          type: string
        claim_rules:
          type: array
          items:
            $ref: "#/components/schemas/ClaimRule"
        role_mappings:
          type: array
          items:
            $ref: "#/components/schemas/RoleMapping"
        read_only:
          type: boolean
          description: Declared in `mosaica.toml`, so the API cannot change or remove it.

    ProviderList:
      type: object
      additionalProperties: false
      required: [providers]
      properties:
        providers:
          type: array
          items:
            $ref: "#/components/schemas/ProviderRecord"

    SessionRecord:
      type: object
      additionalProperties: false
      required: [token_id, principal, provider, subject, api_key, minted_by, permissions, created_at, expires_at]
      properties:
        token_id:
          type: integer
          minimum: 0
        principal:
          type: [string, "null"]
          description: The local principal, or null for an OIDC identity.
        provider:
          type: [string, "null"]
          description: The provider an OIDC identity was accepted by, or null for a local principal.
        subject:
          type: [string, "null"]
          description: An OIDC identity's `sub`.
        api_key:
          type: [string, "null"]
          description: The prefix of the key the session was authenticated or minted with.
        minted_by:
          type: [string, "null"]
          description: The principal that minted the session through `authorise-as`.
        permissions:
          type: array
          items:
            $ref: "#/components/schemas/Permission"
        created_at:
          type: integer
          minimum: 0
        expires_at:
          type: integer
          minimum: 0

    SessionList:
      type: object
      additionalProperties: false
      required: [sessions]
      properties:
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/SessionRecord"

    SessionsEnd:
      type: object
      additionalProperties: false
      description: Exactly one of the three.
      minProperties: 1
      maxProperties: 1
      properties:
        token_id:
          type: integer
          minimum: 0
        principal:
          type: string
        provider:
          type: string

    Meta:
      type: object
      additionalProperties: false
      required: [api_version, bundle_format, views, groups, declared_scalars, scoped_scalars, filter_operands, selection, layers]
      properties:
        api_version:
          type: integer
          const: 1
        bundle_format:
          type: integer
        views:
          type: array
          description: The views this principal may reach, in serving order. A view whose own `visibility` label, or whose group's, the principal does not hold is absent, and naming it in a viewer request is the `404` a name nobody declared gets. The list is only shorter; nothing in it counts the views left out.
          items:
            type: object
            additionalProperties: false
            required: [id, display_name, quantisation, projection, world_aspect, tile_scheme, tile, group, key, metadata]
            properties:
              id:
                type: string
              display_name:
                type: string
              quantisation:
                type: object
                additionalProperties: false
                required: [x_min, x_max, y_min, y_max]
                description: The extent a point's `code` is scaled against in this view, 32 bits per axis over this range. Two views of one bundle may quantise differently, so a client decodes each view's positions against that view's extent.
                properties:
                  x_min: { type: number }
                  x_max: { type: number }
                  y_min: { type: number }
                  y_max: { type: number }
              projection:
                description: What placed this view's positions, as declared, and `none` for a view that projects nothing. The same for every principal.
                type: string
                enum: [web_mercator, equirectangular, gall_isographic, none]
              world_aspect:
                description: The ratio to draw the world at, width over height. `1` for `web_mercator`, `2cos φ₁` for an equirectangular projection, and `null` for `none`, which has no world to draw.
                type: [number, "null"]
              tile_scheme:
                description: The tile scheme this view's frame lines up with, which decides whether a basemap may be drawn under it. A scheme's name rather than a boolean, because an equirectangular frame lines up with a square tiling no server publishes. `null` means draw the points and no basemap.
                type: [string, "null"]
                enum: [xyz, null]
              tile:
                description: The tile this view's frame is under `tile_scheme`. Present exactly when `tile_scheme` is.
                type: [object, "null"]
                additionalProperties: false
                required: [z, x, y]
                properties:
                  z: { type: integer, minimum: 0 }
                  x: { type: integer, minimum: 0 }
                  y: { type: integer, minimum: 0 }
              group:
                description: The view group this view belongs to, or `null` for a plain view. `group`, `key` and `metadata` are `null` together.
                type: [string, "null"]
              key:
                description: This view's key within its group, chosen by the caller when the view was created. `id` is `<group>:<key>`.
                type: [string, "null"]
              metadata:
                description: The typed values this view carries, one per name its group declares. Empty on the views of a group that shares another group's views, whose metadata belongs to the owning group. One value per view, not per item, and nothing filters on it.
                type: [object, "null"]
                additionalProperties:
                  type: object
                  additionalProperties: false
                  required: [type, value]
                  properties:
                    type:
                      type: string
                      enum: [bool, int, float, text, timestamp_us]
                    value:
                      description: The value, of `type`. A `timestamp_us` is microseconds since the Unix epoch. A name declared as a `category` is served as `text`, holding the vocabulary's key.
                      type: [boolean, number, string]
        groups:
          type: array
          description: The view groups this principal may reach, in manifest order, each listing the view ids it may reach in creation order, so a client can offer previous and next without reading meaning into a key. A group cannot be named where a viewer request names a view. A group whose `visibility` the principal does not satisfy is absent with all its views. Empty where the deployment has plain views only.
          items:
            type: object
            additionalProperties: false
            required: [name, title, members_of, views]
            properties:
              name:
                type: string
              title:
                description: The group's title, or `null` where it declares none. The same for every principal who may reach the group.
                type: [string, "null"]
              members_of:
                description: The group whose views this group lays out a second time under the same keys, or `null` where the group owns its views.
                type: [string, "null"]
              views:
                description: This group's view ids, in creation order, as `<group>:<key>`.
                type: array
                items: { type: string }
        declared_scalars:
          type: array
          description: The whole entity-scoped column schema, in declaration order. `render = true` columns arrive in the points frame after `mosaica_id` and `code`, in this order, each named by its column; under a view of a group, that group's `render = true` families (`scoped_scalars`) follow. Read the columns by name. Every such column is nullable, and a point whose item has no value is null, except in a category, where no value is code 0.
          items:
            $ref: "#/components/schemas/DeclaredScalar"
        scoped_scalars:
          type: array
          description: The group-scoped column families, with the same fields `declared_scalars` publishes plus the scope that says which views each arrives under. A family whose group this principal cannot reach is absent, as its `filter_operands` entry is.
          items:
            $ref: "#/components/schemas/ScopedScalar"
        filter_operands:
          type: array
          description: The columns a request may filter on, and each one's operators. `family` tells a client which control to draw; a category has a value list `/v1/categories/{column}` serves, and a keyword has none. `scope` is present only on a group-scoped attribute, whose values differ per view of the named group. A scoped entry whose group this principal cannot reach is absent, and a filter leaf naming it, bare or pinned, is the unknown-column `422`.
          items:
            type: object
            additionalProperties: false
            required: [column, family, operands]
            properties:
              column:
                type: string
              family:
                type: string
                enum: [category, keyword, numeric, text]
              operands:
                type: array
                description: The family's operators, or `eq` and `in` alone for a unique column with neither `render` nor `index`, whose unique index is its only filter home.
                items:
                  type: string
                  enum: [eq, in, prefix, contains, range, match, phrase]
              scope:
                type: object
                additionalProperties: false
                required: [group]
                description: The view group whose views this column's values are per. Under a view of that group, or of a group sharing its views, the request's own view chooses the column. Under any other view a leaf pins one as `<column>@<key>`. An unpinned leaf there is `422` naming the group, and a pin naming no view of it is the unknown-view `404`.
                properties:
                  group:
                    type: string
        selection:
          $ref: "#/components/schemas/Selection"
        layers:
          type: array
          description: The annotation layers this principal may know exist, and what each declares. Filtered per principal, and each layer's `views` list is filtered to the views the principal may reach. The number of artifacts is not published.
          items:
            $ref: "#/components/schemas/Layer"

    DeclaredScalar:
      type: object
      additionalProperties: false
      required: [name, arrow_type, category, analyser, render, index, unique, homes]
      properties:
        name:
          type: string
          pattern: "^[A-Za-z0-9_-]+$"
          description: The column's name, unique in the bundle and safe in a path. Not qualified by view.
        arrow_type:
          type: string
          description: The storage type's name, such as `u8`, `u16`, `u32`, `u64`, `i8` to `i64`, `f32`, `f64`, `bool`, `timestamp_us`, `keyword` or `text`. The engine derives it, and this description does not list every value.
        category:
          description: Present for a category column and `null` for any other. A client builds a legend from it, since the points frame carries a category as a bare integer.
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [vocabulary, kind, visibility]
              properties:
                vocabulary:
                  type: string
                kind:
                  type: string
                  enum: [declared, discovered]
                visibility:
                  type: string
                  enum: [public, derived]
                  description: "`public`: the values are served as declared to every session. `derived`: filtered per principal, so a value appears only where at least one item carrying it is visible."
        analyser:
          description: For a `text` column, the `<name>/<version>` of the analyser that segmented the index, which lets a client tell a text that says nothing from a query that segmented differently. `null` for every other type.
          type: [string, "null"]
        render:
          type: boolean
          description: The column arrives in the points frame, null where an item has no value, or code 0 for a category.
        index:
          type: boolean
          description: The column has a search structure. With neither flag set the column is stored with the record, returned by the item routes and not a filter operand.
        unique:
          type: boolean
          description: No two live or suppressed items hold one value. `eq` and `in` on the column answer from its unique index; a unique column with neither `render` nor `index` takes those two operators and no others.
        homes:
          type: array
          uniqueItems: true
          minItems: 1
          items:
            type: string
            enum: [rendered, value_column, record]
          description: "Where `POST /v1/items` reads the field from: `rendered`, the view's own columns; `value_column`, a per-item column in stored order; `record`, the record store. A field whose `homes` is `[\"record\"]` is read from the record store alone, fastest in `stored` order."

    ScopedScalar:
      type: object
      additionalProperties: false
      required: [name, arrow_type, scope, category, analyser, render, index, views]
      description: One group-scoped attribute's column family, which has one column per view of the group it names and otherwise declares what an entity-scoped column declares.
      properties:
        name:
          type: string
          pattern: "^[A-Za-z0-9_-]+$"
          description: The column's name, as a filter leaf spells it before any pin. Unique in the bundle, across entity-scoped columns and families.
        arrow_type:
          type: string
          description: The storage type's name, spelt as `DeclaredScalar.arrow_type` is.
        scope:
          type: object
          additionalProperties: false
          required: [group]
          description: The view group this family has a column per view of.
          properties:
            group:
              type: string
        category:
          description: Present for a category family and `null` otherwise, as `DeclaredScalar.category` is.
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [vocabulary, kind, visibility]
              properties:
                vocabulary:
                  type: string
                kind:
                  type: string
                  enum: [declared, discovered]
                visibility:
                  type: string
                  enum: [public, derived]
        analyser:
          description: For a `text` family, the `<name>/<version>` of the analyser that segmented its per-view indexes. `null` for every other family.
          type: [string, "null"]
        render:
          type: boolean
          description: The column arrives in the points frame under a view of this group, or of a group sharing its views, and under no other view. There it follows the entity-scoped render columns, and a row with no value is null.
        index:
          type: boolean
          description: The family's columns carry a per-item value column a filter can be answered from, which is what makes it a filter operand.
        views:
          type: array
          description: "Every view id whose rows carry this column and that this principal may reach: the owning group's own `<group>:<key>` ids, and the same keys under every group sharing those views. A client under a sharing group's view receives the column under that group's id. Filtered per id, as `views` is. Not always the group's whole roster: a view gains the column at its first flush after the column is declared or the view is created, and until then its points frame carries no such column, which reads as no value."
          items: { type: string }

    Selection:
      type: object
      additionalProperties: false
      description: The sampling parameters and request ceilings. They are the same for every principal, and a client needs them to read a count of drawn points as a density.
      required: [k_min, k_max_marks, max_k, theta_target_marks, max_underlay_offset, max_tiles_per_request, max_artifacts_per_tile, max_category_values, max_shape_vertices, max_region_vertices, max_region_cells, max_suggestions, max_suggestion_walk, max_suggest_set_entities, max_browse_rows, max_page_rows, max_page_bytes, max_aggregate_groupings, max_aggregate_top, max_aggregate_named, max_aggregate_bins, max_aggregate_cells]
      properties:
        k_min:
          type: integer
          minimum: 0
        k_max_marks:
          type: integer
          minimum: 0
          description: The most points a tile draws, and `k`'s default.
        max_k:
          type: integer
          minimum: 0
          description: The server's ceiling on `k`. A tile draws at most `min(k, max_k, k_max_marks)` points.
        theta_target_marks:
          type: integer
          minimum: 0
        max_underlay_offset:
          type: integer
          minimum: 0
        max_tiles_per_request:
          type: integer
          minimum: 0
          description: The most tiles a viewport request may cover. A client needs it to tell this `422` from an error in its own arithmetic.
        max_artifacts_per_tile:
          type: integer
          minimum: 0
          description: The largest `per_tile` a `POST /v1/artifacts/viewport` request may name.
        max_category_values:
          type: integer
          minimum: 0
          description: "`/v1/categories`' page ceiling and default."
        max_shape_vertices:
          type: integer
          minimum: 0
          description: The most vertices a published shape may carry once fitted to each view. Over it, `PUT /control/layers/{name}/artifacts` is `422` naming the count and the cap.
        max_region_vertices:
          type: integer
          minimum: 0
          description: The most vertices a `region` filter leaf's `polygon` may carry. Over it the request is `422` naming the count and the cap.
        max_browse_rows:
          type: integer
          minimum: 1
          description: The most rows `POST /v1/artifacts/browse` returns in one page, and its default page size. `limit` is clamped to it, and `limit = 0` is `422`. It bounds the response; what a principal may be told is decided by each artifact's existence criterion before paging starts.
        max_page_rows:
          type: integer
          minimum: 1
          description: The most rows a `POST /v1/items`, `POST /v1/artifacts` or `POST /v1/aggregate` page holds. A request's `page_rows` is capped to it.
        max_page_bytes:
          type: integer
          minimum: 1
          description: The most Arrow bytes a `POST /v1/items` or `POST /v1/artifacts` page holds before compression. A single row larger than this is sent alone. An aggregate page is held to the server's own `serve.aggregate_page_bytes`.
        max_aggregate_groupings:
          type: integer
          minimum: 0
          description: The most groupings one `POST /v1/aggregate` may carry; `serve.max_aggregate_groupings`, default 16. More is `422`.
        max_aggregate_top:
          type: integer
          minimum: 0
          description: The largest `top` of an aggregate grouping; `serve.max_aggregate_top`, default 1,000. Larger is `422`.
        max_aggregate_named:
          type: integer
          minimum: 0
          description: The most `values` or `artifacts` an aggregate grouping may name; `serve.max_aggregate_named`, default 1,000. More is `422`.
        max_aggregate_bins:
          type: integer
          minimum: 0
          description: The largest `bins` of an aggregate grouping; `serve.max_aggregate_bins`, default 1,000. Larger is `422`.
        max_aggregate_cells:
          type: integer
          minimum: 0
          description: The most cells an aggregate grouping's cell level may list, the cells at its `depth` in its `area`, whatever its groups; `serve.max_aggregate_cells`, default 1,048,576, every cell at depth 10. More is `422`.
        max_region_cells:
          type: integer
          minimum: 0
          description: The most boundary cells a `region` leaf's shape may cross at one depth. Over it the request is not refused; the server stops at the deepest depth that fits, answers exactly for a shape covering the requested one, and says so in `x-mosaica-region`.
        max_suggestions:
          type: integer
          minimum: 1
          description: "`/v1/categories/{column}/suggest`'s page ceiling and `limit`'s default; `serve.max_suggestions`, default 20. With it a client tells a short list that is everything from one the server cut, which `more` alone does not say."
        max_suggestion_walk:
          type: integer
          minimum: 1
          description: The most values one suggestion request examines before it stops and answers `more` as `true`; `serve.max_suggestion_walk`, default 100,000. It bounds the request's time, not what it discloses; the listing route walks the whole vocabulary. A client that receives `more` on a page that did not fill reads it as this budget.
        max_suggest_set_entities:
          type: integer
          minimum: 1
          description: "The size of visible set at or under which a suggestion is answered from the session's set of the values its principal can see, rather than by probing the index once per value walked; `serve.max_suggest_set_entities`, default 10,000,000. Both answer with the same page under the same visibility rule, and the response does not say which answered. What differs is `more`, which from the set means the page filled and from the probe may mean the walk budget ran out. A caller learns which side of it their own visible set falls on, which a zoom-0 viewport already returns as `visible`."

    Layer:
      type: object
      additionalProperties: false
      required: [name, title, views, membership, hierarchy, levels, computed_content, shape, supplied_content, depends_on, version]
      properties:
        name:
          type: string
        title:
          type: [string, "null"]
        views:
          type: array
          items: { type: string }
        membership:
          description: Where the layer's artifacts get their members, a stored set per artifact or a shape resolved against stored positions. The engine derives it, and this description does not list every value.
          type: string
        hierarchy:
          type: object
          additionalProperties: false
          required: [kind, prune_children]
          properties:
            kind:
              type: string
              enum: [flat, nested, dag, stacked, tiered]
            prune_children:
              type: boolean
              description: The layer's default cut; `artifact_budget` may ask for more.
        levels:
          type: array
          description: Empty for a treed layer, whose lineage is in its parent links.
          items:
            type: object
            additionalProperties: false
            required: [level, title, zoom]
            properties:
              level:
                type: integer
                minimum: 0
              title:
                type: [string, "null"]
              zoom:
                description: "`[min, max]` tile depth. A viewport request naming no `levels` is answered at the levels whose range covers its depth; naming `levels` overrides this. Absent means served at every depth."
                oneOf:
                  - type: "null"
                  - type: array
                    minItems: 2
                    maxItems: 2
                    items: { type: integer, minimum: 0 }
        computed_content:
          type: array
          description: Properties computed for each principal from the members that principal can see, from `centroid`, `hull` and `box`.
          items: { type: string }
        shape:
          description: |
            The kind of the layer's one drawn geometry, or `null` where it draws none. `derived` is
            the hull over the members this principal can see, computed per principal. `predicate`
            is a spatial layer's membership shape, the same for every principal served the
            artifact. `authored` is a supplied `polygon`, `circle` or `ellipse` content, served
            under that content's own requirement and also the same for every principal. A client
            may keep a `predicate` or `authored` shape against a `mosaica_id` across principals,
            and may not keep a `derived` one. The geometry itself is `ArtifactResponse.shape` and
            the bulk read's `shape`.
          oneOf:
            - type: "null"
            - type: string
              enum: [derived, predicate, authored]
        supplied_content:
          type: array
          description: The types of the content the publisher supplies, in the order of an artifact's `content`. An artifact whose content a principal may not read is not served to that principal at all.
          items: { type: string }
        depends_on:
          type: array
          description: The layers this layer's artifacts may attach to, as a clustering's labels attach to its clusters.
          items: { type: string }
        version:
          type: integer
          minimum: 0
          description: Moves with any change to who may reach the layer; a client compares it to notice one.

    CategoriesResponse:
      type: object
      additionalProperties: false
      required: [column, values, next]
      properties:
        column:
          type: string
        values:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [code, key, title]
            properties:
              code:
                type: integer
                minimum: 1
                description: Never `0`, which means no value.
              key:
                type: string
              title:
                type: [string, "null"]
                description: The declared title, or `null` where none was declared, as for every value a `discovered` vocabulary adds. A client shows the key in its place.
        next:
          type: [string, "null"]
          description: The last key returned, to pass as `?after=`; `null` when the list is complete, and always `null` for `?codes=`.

    SuggestRequest:
      type: object
      additionalProperties: false
      properties:
        q:
          type: string
          maxLength: 256
          description: As the `GET` form's `q`. Absent is empty, which matches every value.
        limit:
          type: integer
          minimum: 1
          description: As the `GET` form's `limit`.
        counts:
          type: boolean
          description: As the `GET` form's `counts`.
        view:
          type: string
          description: As the `GET` form's `view`. Required with `filters`.
        filters:
          description: The viewport's filter, evaluated in `view`. It narrows each `count` to the items passing it and changes nothing else.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"

    SuggestResponse:
      type: object
      additionalProperties: false
      required: [column, q, values, more]
      properties:
        column:
          type: string
          description: The column as the caller spelt it.
        q:
          type: string
          description: The query as received, not folded.
        values:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [code, key, title, match]
            properties:
              code:
                type: integer
                minimum: 1
                description: Never `0`, which means no value.
              key:
                type: string
              title:
                type: [string, "null"]
                description: The declared title, or `null` where none was declared.
              match:
                type: object
                additionalProperties: false
                required: [field, start, len]
                description: Where the match is, in characters of the served string, so a client can highlight it without folding.
                properties:
                  field:
                    type: string
                    enum: [key, title]
                    description: The field that matched. A word-start match reports the field the word came from, and is `key` for a value with no title.
                  start:
                    type: integer
                    minimum: 0
                  len:
                    type: integer
                    minimum: 0
              count:
                type: integer
                minimum: 0
                description: Present only with `counts=true`. The items carrying this value that this principal may see, within `view` where the request names one, exact and computed per request. Under a view it leaves out items not yet flushed, which hold no row in the view, and is `0` for a value this principal sees only in other views. It does not affect the order.
        more:
          type: boolean
          description: "`true` where the walk stopped before its range was exhausted: the page filled, or `selection.max_suggestion_walk` values were examined. Answered from the session's set of visible values, it means only that the page filled. There is no cursor; a client asks for more by extending `q`."
        total:
          type: integer
          minimum: 0
          description: Present only with `counts=true`. The number of items in the set each `count` is taken over. That set is the items this principal may see, within `view` where the request names one, passing `filters` where the request has them. A value's share of that set is `count / total`.

    Pin:
      type: object
      additionalProperties: false
      required: [prefix, segments_version]
      description: A generation, the JSON of the `x-mosaica-pin` header, sent back. Advisory; it grants nothing.
      properties:
        prefix:
          type: string
        segments_version:
          type: integer
          minimum: 0

    ViewportRequest:
      type: object
      additionalProperties: false
      required: [view, zoom]
      description: Exactly one of `bbox` and `tiles`.
      properties:
        view:
          type: string
          description: A view id from `/v1/meta`.
        zoom:
          type: integer
          minimum: 0
          maximum: 16
          description: The tile depth. The grid is 2¹⁶ by 2¹⁶.
        bbox:
          type: array
          minItems: 4
          maxItems: 4
          items: { type: number }
          description: "`[x0, y0, x1, y1]` in the coordinates of the view's quantisation extent, with `x0 ≤ x1`, `y0 ≤ y1`, all finite. The request is answered for the depth-`zoom` tiles it intersects."
        tiles:
          type: array
          items:
            type: integer
            minimum: 0
          description: Depth-`zoom` Morton prefixes, in place of `bbox`. Answered in the request's order with repeats removed, the first kept. A prefix with bits above `zoom` is refused. A tile the client already holds can be left out, and costs the server nothing.
        k:
          type: integer
          minimum: 0
          description: The most points a tile returns. Defaults to `k_max_marks` and is capped by `max_k`. `0` asks for counts only. Keep it the same or raise it as the zoom deepens.
        filters:
          description: The filter. It narrows which points are drawn and counted as `matched`.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"
        highlight:
          description: |
            A second expression in `filters`' grammar, answered on the same frames. A viewer
            narrowed by one clause and lit by another sends both here.

            It does not change which rows the response holds. The per-tile cap, the sampling and
            `served` run over the rows `filters` selects as they do without a highlight, so the
            same points are served with and without one, and a point stays where it is with its
            brightness changed. Use `filters` to narrow the map and `highlight` to light it.

            It is evaluated over the rows `filters` selects, so every answer is the conjunction of
            the two: the tiles frame's `highlighted` count (of the tile's `matched`, how many also
            satisfy it) and the points frame's `highlighted` bit per served point.
            `highlighted ≤ matched ≤ visible` in every tile.

            Two highlights are one expression under `all_of` or `any_of`.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"
        pin:
          $ref: "#/components/schemas/Pin"
        underlay_offset:
          type: integer
          minimum: 0
          description: Serve exact counts of the visible set at depth `zoom + underlay_offset` as the sub-cells frame. They ignore `filters` and `highlight`; for filtered counts per cell, ask `POST /v1/aggregate` for `cells`. Absent or `0` serves none and adds no bytes. Above `max_underlay_offset`, past depth 16, or over the sub-cell budget, it is refused, not clamped.
        layers:
          description: |
            The annotation layers whose artifacts tag the points, one membership column each.
            Absent or `[]` is none, at no cost. `"all"` is every layer this principal reaches. An
            array is the named layers this principal reaches: a name it does not reach is left out
            whether or not it was asked for, so naming a layer does not tell the caller whether it
            exists.
          oneOf:
            - type: array
              items: { type: string }
            - type: string
              const: all
        artifact_budget:
          type: integer
          minimum: 0
          description: The most artifacts a nested or `dag` layer tags with, met by tagging with ancestors in place of their descendants, never by sampling; without effect on any other layer. Unbounded when absent.
        levels:
          description: |
            Which of each named layer's declared levels tag the points.

            Absent follows each layer's declaration: the levels whose `zoom` range covers this
            request's `zoom`, on the same 0 to 16 depth. The string `"all"` answers for every
            level, and an array for exactly those levels, among the ones the layer holds. A level a
            layer does not hold is left out rather than refused; any other value is `422`. An empty
            array is none, as `layers: []` is.

            A layer that declares no levels, a treed or flat one, sits at level 0 and is not
            affected by this field in any form. A layer that declares levels but no `zoom` range on
            any of them serves every level when the field is absent.

            The default differs from `layers`' because naming a layer has already asked for its
            artifacts; what remains is which levels to pay for, and every level is the costly
            answer, so it is asked for by name. The field applies to every layer named, against
            each layer's own levels.
          oneOf:
            - type: array
              items: { type: integer, minimum: 0 }
            - type: string
              const: all
        point_rows:
          description: |
            The columns of each served point. The points and the per-tile `served` counts are the
            same under every value; only the columns change.

            Absent or `"full"` is every render column. A list names the render columns to serve:
            each point is `mosaica_id`, `code` and those columns, in the view's declaration order,
            followed by `highlighted` and the membership columns as under `"full"`. No other render
            column is read, so a map that colours by one column asks for that one and pays for
            nothing else; `[]` is position alone. A name that is not one of the view's render
            columns in `/v1/meta` is `422`. Fetch the rest of an item from `/v1/items`.

            `"highlight"` is the same points as `(mosaica_id, highlighted)`, the other columns
            absent from the schema. Nine bytes a point. It is for a client that changed only its
            highlight and holds every point it needs; it joins the bits to its points by
            `mosaica_id`. When `x-mosaica-stale` moves, the held points may no longer be what the
            same request serves, and the client asks again with its columns. Without a `highlight`
            it answers as `"full"`. Any other string is `422`.
          oneOf:
            - type: string
              enum: [full, highlight]
            - type: array
              items: { type: string }

    ViewportArtifactsRequest:
      type: object
      additionalProperties: false
      required: [view, zoom, per_tile]
      description: Exactly one of `bbox` and `tiles`.
      properties:
        view:
          type: string
          description: A view id from `/v1/meta`.
        zoom:
          type: integer
          minimum: 0
          maximum: 16
          description: The tile depth.
        bbox:
          type: array
          minItems: 4
          maxItems: 4
          items: { type: number }
          description: "`[x0, y0, x1, y1]`, as on `POST /v1/viewport`."
        tiles:
          type: array
          items:
            type: integer
            minimum: 0
          description: Depth-`zoom` Morton prefixes, in place of `bbox`, answered one frame each in this order with repeats removed.
        layers:
          description: The layers to answer for, as `POST /v1/viewport` names them. Absent or `[]` is none.
          oneOf:
            - type: array
              items: { type: string }
            - type: string
              const: all
        levels:
          description: Which of each named layer's declared levels to answer for, as `POST /v1/viewport` names them. Absent follows each layer's declared zoom ranges.
          oneOf:
            - type: array
              items: { type: integer, minimum: 0 }
            - type: string
              const: all
        per_tile:
          type: integer
          minimum: 0
          description: The most artifacts one level serves in one tile, at most `selection.max_artifacts_per_tile`.
        computed:
          description: Which of each layer's declared `centroid` and `box` to serve. Absent is each layer's declaration, and `[]` is none, the artifacts still served and counted. It narrows and cannot widen. Any other word is `422`; a shape is read by `mosaica_id`.
          type: array
          items:
            type: string
            enum: [centroid, box]
        filters:
          description: Answered as each row's `matched` bit, inside its tile. It changes nothing else.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"
        highlight:
          description: Answered, with `filters`, as each row's `highlighted` bit, inside its tile.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"
        pin:
          $ref: "#/components/schemas/Pin"
        budget:
          type: integer
          minimum: 0
          description: The most artifacts the treed frame holds, met by serving ancestors in place of their descendants; without effect on any other layer. Unbounded when absent.
        palette_size:
          $ref: "#/components/schemas/PaletteSize"

    PaletteSize:
      type: integer
      minimum: 2
      maximum: 32
      description: How many colours the client's palette holds. Each artifact then carries `slot`, this principal's slot for it among them, the same on every route; see `POST /v1/artifacts/viewport`'s **Slots**. Absent serves `slot` as null. Outside 2 to 32 is `422`. On a level whose layer declares a centroid or a box and that is served from its column, the slots are worked out from the centroids the artifacts viewport fills when it draws the level; with `palette_size`, every route fills that same entry where no request has, so browse, a bulk read and an aggregate grouping without `cut` serve the slots the viewport serves. Without `palette_size` no route fills it for slots. Browse and `POST /v1/artifacts` work out slots for every level whose rows they return, and on a `tiered` layer for the levels above each, so the first such request over a level pays the fill drawing it would.

    BrowseRequest:
      type: object
      additionalProperties: false
      required: [view, layer]
      properties:
        view:
          type: string
          description: The view whose rows the counts are taken over. Required, since a masked count is taken in one view's rows.
        layer:
          type: string
          description: The layer to browse. A name outside the list `/v1/meta` publishes to this principal is `422`, as is a layer that attaches to another, which has no lineage of its own.
        level:
          type: integer
          minimum: 0
          description: The level to browse on a levelled layer (`stacked`, `tiered`). `422` on `flat`, `nested` and `dag`, which have one level, and on a level the layer does not hold, since a parameter accepted and ignored would give a wrong answer that looks right.
        parent:
          description: The children form. The artifacts naming this one among their parents, with its own served parents in `parents`. A `mosaica_id`, as a number or its decimal string. One that names nothing, one of another layer, one suppressed and one below this principal's criterion all answer an empty page.
          oneOf:
            - type: integer
              minimum: 0
            - type: string
              pattern: "^[0-9]+$"
        q:
          type: string
          description: The search form. The layer's artifacts whose `key` or `name`, as the row carries it for this principal, contains this, ignoring case. Not with `parent`.
        filters:
          description: The viewport's filter. Each row then carries `matched_count`, the members this principal can see that match, and the page is ordered by it. Which artifacts are served, and their `masked_count`, do not change, and a row whose `matched_count` is zero is still served.
          allOf:
            - $ref: "#/components/schemas/FilterExpr"
        limit:
          type: integer
          minimum: 1
          description: Page size, clamped to `selection.max_browse_rows`, its default. `0` is `422`.
        cursor:
          type: string
          description: A previous page's `next`, unchanged. Opaque; it carries a count and an identifier this principal was already served.
        palette_size:
          $ref: "#/components/schemas/PaletteSize"

    BrowseResponse:
      type: object
      required: [artifacts, parents]
      properties:
        artifacts:
          type: array
          items: { $ref: "#/components/schemas/BrowseRow" }
        parents:
          type: array
          description: The requested artifact's own served parents in the children form, and `[]` in the other forms, so a client reads one shape.
          items: { $ref: "#/components/schemas/BrowseRow" }
        next:
          type: string
          description: The cursor for the next page. Absent on the last page.

    BrowseRow:
      type: object
      required: [mosaica_id, masked_count, rung, parent_ids, child_count, slot]
      properties:
        mosaica_id:
          type: string
          description: A decimal string, as every viewer-plane JSON identifier is, since a JavaScript number does not hold a `u64` exactly.
        key:
          type: string
          description: The publisher's key, where one was supplied.
        name:
          type: string
          description: The artifact's first supplied text content where it has one; otherwise the first text of an artifact attached to it that `POST /v1/artifacts` would serve this principal, chosen in the order the route description gives. Absent where neither gives text. An authored shape is not text. An artifact whose own content cannot be served is left off the page.
        masked_count:
          type: integer
          description: How many of this artifact's members this principal can see. Not its size, and not changed by `filters`.
        matched_count:
          type: integer
          description: How many of the members this principal can see match `filters`. Present exactly when the request carries `filters`.
        rung:
          type: integer
          description: The declared level on a levelled layer, the depth in the served lineage on a treed one, and `0` on a flat one.
        parent_ids:
          type: array
          description: This artifact's parents that this principal is also served, ascending. Empty for a root and for an artifact whose parents are all withheld; a client reads both as no parent here.
          items: { type: string }
        child_count:
          type: integer
          minimum: 0
          description: How many artifacts this principal is served name this one among their parents, which is how many rows the children form with this artifact as `parent` serves across its pages. A child this principal is not served is not counted. `0` for a leaf, and on a `flat` or `stacked` layer, which have no links. Not changed by `filters`.
        slot:
          type: [integer, "null"]
          minimum: 0
          maximum: 31
          description: This principal's palette slot for the artifact, as `POST /v1/artifacts/viewport` serves it. Null where the request names no `palette_size`, and for an artifact holding no member this principal can see.

    FilterExpr:
      description: |
        A boolean expression over declared columns. A node is a leaf, exactly one column name
        mapped to exactly one operator, or a combinator: `all_of`, `any_of` or `none_of` over
        sub-expressions. `all_of: []` matches everything; `any_of: []` matches nothing. `none_of`
        matches every item the viewer may see that none of its branches matches, an item holding no
        value in a column included, and its branches may name any columns; `none_of: []` matches
        everything. The deployment bounds nesting, and an expression nested deeper is refused,
        not flattened.

        Values: a category takes its vocabulary's key (a string) or code (an integer), mixed
        freely; a keyword takes byte-exact strings; a number column takes numbers, and `range`
        takes an object of `gte`, `gt`, `lte` and `lt`, at least one and at most one per side; a
        `text` column takes `match` with a string or `{"query", "minimum_should_match"}`, and
        `phrase` with a string. An integer or `timestamp_us` column also takes an integer as its
        decimal digits in a string, optionally after `-` or `+` (`"1152921504606846976"`), which is exact
        where a JSON number read as a double is not, past 2^53. A `timestamp_us` column's values
        are microseconds since the Unix epoch, and it also takes an RFC 3339 date (`"2020-01-01"`,
        midnight UTC) or date-time with an offset (`"2020-01-01T09:30:00Z"`, or with a space for
        the `T`) as a string; a date-time with no offset, or with more than six fractional-second digits, is `422`. A date
        is its first instant, so `{"lt": "2021-01-01"}` ends a range with the last of 2020, and
        `{"lte": "2020-12-31"}` leaves out all of 31 December after midnight.

        `eq` and `in` on a unique column answer from its unique index, as the set of visible items
        holding the values; a holder the viewer may not see answers as no holder does.

        An unknown column, or an operator outside the column's family, is `422`. An unknown value
        is an empty operand, so a client shows no matches alike for a value the viewer may not see
        and one that does not exist.

        `region` is a leaf of its own, not a column, and the name is reserved at the build. It is
        a set of rows over the whole view, composes with every other leaf, and can be negated with
        `none_of`. It is cached per generation across principals. `x-mosaica-region` says how exact
        its answer is.

        `member_of` is another leaf of its own, also reserved at the build: `{"layer",
        "artifact"}`, the artifact's members this principal can see, as a set of rows over the
        whole view, composed and negated as `region` is. A layer this principal's `/v1/meta` does
        not list is `422`. An artifact that names nothing, belongs to another layer, is suppressed
        or is below this principal's criterion is an empty operand, one answer for every reason.

        `mosaica_id` is a leaf over the identifiers responses hand out: `{"eq": id}` or
        `{"in": [id, ...]}`. It matches the items named that this principal may see; an identifier
        naming nothing, an artifact, or an item the principal may not see matches nothing, one
        answer for every reason. It composes and negates as a column leaf does, and a read of a
        few named items answers from the identifiers without walking the view.
      type: object
      minProperties: 1
      maxProperties: 1
      properties:
        all_of:
          type: array
          items: { $ref: "#/components/schemas/FilterExpr" }
        any_of:
          type: array
          items: { $ref: "#/components/schemas/FilterExpr" }
        none_of:
          type: array
          items: { $ref: "#/components/schemas/FilterExpr" }
        region:
          $ref: "#/components/schemas/RegionLeaf"
        member_of:
          $ref: "#/components/schemas/MemberOfLeaf"
        mosaica_id:
          $ref: "#/components/schemas/MosaicaIdLeaf"
      additionalProperties:
        $ref: "#/components/schemas/FilterLeafOperator"

    MosaicaIdLeaf:
      type: object
      description: The items named by `mosaica_id` that this principal may see.
      minProperties: 1
      maxProperties: 1
      properties:
        eq:
          $ref: "#/components/schemas/MosaicaIdValue"
        in:
          type: array
          items:
            $ref: "#/components/schemas/MosaicaIdValue"
      additionalProperties: false

    MosaicaIdValue:
      description: A `mosaica_id`, as a JSON number or its decimal digits in a string.
      oneOf:
        - type: integer
          minimum: 0
        - type: string
          pattern: "^[0-9]+$"

    MemberOfLeaf:
      type: object
      description: |
        One artifact of one layer, as a set of rows over the whole view: the artifact's members
        that this principal may see. The artifact is named by its `mosaica_id` as `artifact`, or
        by the key it was published under as `key`, with `level` where the layer has levels.

        The fields are refused differently. `layer` and `level` are deployment schema: a name
        outside the layer list `/v1/meta` publishes to this principal is `422`, and the answer is
        the same for a layer the principal may not reach, a suppressed one and one never
        registered, whatever the `level`. On a layer it lists, a `level` past the layer's last is
        `422`. `artifact` and `key` are values and are not refused: an identifier or a key that
        names nothing, an identifier of another layer, a key the level does not hold or that only
        another view of a group holds, an artifact that is suppressed or deleted, one whose layer
        this principal does not reach, and one below its own existence criterion are all the
        empty operand. The artifact's own test, its layer's `visibility`, its own label and
        `require_member_visibility` against the principal's visible set, is the one the artifacts
        frame applies, and it runs before any membership is read.
      oneOf:
        - type: object
          additionalProperties: false
          required: [layer, artifact]
          properties:
            layer:
              type: string
            artifact:
              description: The artifact's `mosaica_id`.
              oneOf:
                - type: integer
                  minimum: 0
                - type: string
                  pattern: "^[0-9]+$"
        - type: object
          additionalProperties: false
          required: [layer, key]
          properties:
            layer:
              type: string
            key:
              type: string
              description: The key the artifact was published under. On a layer scoped to a group, the key in the request's own view.
            level:
              type: integer
              minimum: 0
              maximum: 4294967295
              default: 0
              description: The level the key is held at, one the layer holds. Absent is 0, the only level of a layer that declares none.

    RegionLeaf:
      type: object
      description: |
        Exactly one of `polygon`, `bbox`, `circle` and `ellipse`, a shape in the view's own space
        (its quantisation extent, as `/v1/meta` publishes it), with `space`. `space` is `view`, the
        default, or `wgs84` on a view that declares a projection: degrees, densified before they
        are projected so each edge is straight in the plane it was declared in. `wgs84` on a view
        that projects nothing is `422`. Or `artifact`: a published shape named by its
        `mosaica_id`, answered from its stored membership under the artifact's own test. An
        artifact this principal would not be served, an identifier that names nothing, one on
        another view, and one whose layer draws an authored shape are an empty operand, one answer
        for every reason.

        A `polygon` is at least three vertices, closed implicitly, even-odd, with a point on an
        edge inside, and over `max_region_vertices` it is `422`. A `bbox` is `[x0, y0, x1, y1]`,
        closed on every side. A `circle` is `[cx, cy, r]`. An `ellipse` is
        `[cx, cy, a, b, angle_degrees]`. Coordinates are quantised to the view's 32-bit grid
        before anything is tested, so a client highlighting its own points quantises the same way.
      additionalProperties: false
      minProperties: 1
      maxProperties: 2
      properties:
        polygon:
          type: array
          minItems: 3
          items:
            type: array
            minItems: 2
            maxItems: 2
            items: { type: number }
        bbox:
          type: array
          minItems: 4
          maxItems: 4
          items: { type: number }
        circle:
          type: array
          minItems: 3
          maxItems: 3
          items: { type: number }
        ellipse:
          type: array
          minItems: 5
          maxItems: 5
          items: { type: number }
        artifact:
          oneOf:
            - type: integer
              minimum: 0
            - type: string
              pattern: "^[0-9]+$"
        space:
          type: string
          enum: [view, wgs84]

    FilterLeafOperator:
      type: object
      description: One operator from the column's `operands` in `/v1/meta`'s `filter_operands`, with its value. Value shapes depend on the family (see `FilterExpr`) and are not narrowed further here, since this description cannot know a column's family.
      minProperties: 1
      maxProperties: 1
      properties:
        eq: {}
        in:
          type: array
        prefix:
          type: string
        contains:
          type: string
        range:
          type: object
          additionalProperties: false
          minProperties: 1
          properties:
            gte: { $ref: "#/components/schemas/NumericComparand" }
            gt: { $ref: "#/components/schemas/NumericComparand" }
            lte: { $ref: "#/components/schemas/NumericComparand" }
            lt: { $ref: "#/components/schemas/NumericComparand" }
        match:
          oneOf:
            - type: string
            - type: object
              additionalProperties: false
              required: [query, minimum_should_match]
              properties:
                query: { type: string }
                minimum_should_match: { type: integer, minimum: 1 }
        phrase:
          type: string
      additionalProperties: false

    NumericComparand:
      description: A number, or on an integer or `timestamp_us` column its decimal digits as a string, optionally after `-` or `+`, which is exact past 2^53. On a `timestamp_us` column, also an RFC 3339 date (`2020-01-01`, midnight UTC) or date-time with an offset (`2020-01-01T09:30:00Z`, or with a space for the `T`) and at most six fractional-second digits.
      oneOf:
        - type: number
        - type: string
          pattern: '^([-+]?[0-9]+|[0-9]{4}-[0-9]{2}-[0-9]{2}([Tt ][0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,6})?([Zz]|[+-][0-9]{2}:[0-9]{2}))?)$'

    ItemsRequest:
      type: object
      additionalProperties: false
      required: [view, fields]
      properties:
        view:
          type: string
          description: The view whose items are read, from `/v1/meta`. An item with no position in it is not returned.
        fields:
          type: array
          items: { type: string }
          description: Declared fields to return, by name, each once. A group-scoped field may be pinned as `<field>@<key>`. Empty returns `mosaica_id` alone.
        position:
          type: boolean
          description: Return the item's position in `view` as `mosaica:x` and `mosaica:y`. Absent is false.
        labels:
          type: boolean
          description: Return the clauses of the item's labels this principal satisfies as `mosaica:labels`. Absent is false.
        filters:
          $ref: "#/components/schemas/FilterExpr"
        keep_unmatched:
          type: boolean
          description: Return every visible item with a `mosaica:matched` column, rather than the matching ones only. Without `filters` every row is marked matched.
        count:
          type: boolean
          description: Put the numbers of visible and matching items in the head. Only on a request with no `cursor`; it costs one evaluation of the whole filter.
        order:
          type: string
          enum: [map, stored]
          description: Absent takes the cursor's order, or with no cursor the server's choice. A value different from the cursor's is `422`.
        page_rows:
          type: integer
          minimum: 1
          description: Rows per page, capped by `selection.max_page_rows`. Absent is the cap.
        pages:
          type: integer
          minimum: 1
          description: The most pages this response may carry. Absent is as many as the response's budgets allow.
        cursor:
          type: string
          description: A previous response's `next`, unchanged. Absent starts from the beginning.
        compression:
          type: string
          enum: [zstd]
          description: Compress every records frame's Arrow buffers with zstd. Absent is uncompressed.

    ArtifactsRequest:
      type: object
      additionalProperties: false
      required: [view, layer, fields]
      properties:
        view:
          type: string
          description: The view whose rows the counts are taken over, from `/v1/meta`.
        layer:
          type: string
          description: A layer `/v1/meta` publishes to this principal. Attached layers are allowed.
        level:
          type: integer
          minimum: 0
          description: Only artifacts at this level, on a levelled layer.
        parent:
          oneOf:
            - type: integer
              minimum: 0
            - type: string
              pattern: "^[0-9]+$"
          description: A `mosaica_id`. Only the artifacts naming it among their parents. Not with `q`.
        q:
          type: string
          description: Only artifacts whose key, or first served text content, contains this, ignoring case. Not with `parent`.
        ids:
          type: array
          items:
            oneOf:
              - type: integer
                minimum: 0
              - type: string
                pattern: "^[0-9]+$"
          description: "`mosaica_id`s. Only the artifacts of `layer` they name that this principal is served. An identifier this principal is not served, and one naming nothing in the layer, have no row and are one answer."
        filters:
          $ref: "#/components/schemas/FilterExpr"
        keep_unmatched:
          type: boolean
          description: Return every served artifact under `filters`, including those with no matching member.
        count:
          type: boolean
          description: Put the numbers of artifacts served and of those matching in the head. Only on a request with no `cursor`.
        fields:
          type: array
          items:
            type: string
            enum: [key, level, parents, target, masked_count, content, centroid, box, shape, slot]
          description: Properties to return, each once, in this order.
        palette_size:
          $ref: "#/components/schemas/PaletteSize"
        page_rows:
          type: integer
          minimum: 1
          description: Rows per page, capped by `selection.max_page_rows`. Absent is the cap.
        pages:
          type: integer
          minimum: 1
          description: The most pages this response may carry.
        cursor:
          type: string
          description: A previous response's `next`, unchanged.
        compression:
          type: string
          enum: [zstd]
          description: Compress every records frame's Arrow buffers with zstd.

    ArtifactsHead:
      type: object
      additionalProperties: false
      required: [page_rows]
      description: The kind-6 frame of a `POST /v1/artifacts` body. `served` and `matched` are present exactly when the request asked for `count`.
      properties:
        page_rows:
          type: integer
          minimum: 1
          description: The page size used, after the cap.
        served:
          type: integer
          minimum: 0
          description: The artifacts the request selects that this principal is served, at the start of the response.
        matched:
          type: integer
          minimum: 0
          description: Of those, the ones with a visible member matching `filters`; `served` without one.

    ItemsHead:
      type: object
      additionalProperties: false
      required: [order, page_rows]
      description: The kind-6 frame. `visible` and `matched` are present exactly when the request asked for `count`.
      properties:
        order:
          type: string
          enum: [map, stored]
          description: The order this response's rows are in.
        page_rows:
          type: integer
          minimum: 1
          description: The page size used, after the cap.
        visible:
          type: integer
          minimum: 0
          description: Visible items in the view at the start of the response, the count the viewport serves.
        matched:
          type: integer
          minimum: 0
          description: Of those, the ones matching `filters`; `visible` without one.

    PageEnd:
      type: object
      additionalProperties: false
      required: [next, ended_by]
      description: The kind-8 frame, after each records frame.
      properties:
        next:
          type: [string, "null"]
          description: The cursor to resume after this page; null where no row remains.
        ended_by:
          type: string
          enum: [rows, bytes, time, end]
          description: "`rows`: the page holds `page_rows`. `bytes`: the next row would have taken it past `max_page_bytes`. `time`: the response's time ran out, or the stream deadline cancelled it, and the response ends after this page. `end`: no row remains."

    RecordsTrailer:
      type: object
      additionalProperties: false
      required: [pages, rows, next, ended_by, stream_us]
      description: The kind-4 frame of a `POST /v1/items`, `POST /v1/artifacts` or `POST /v1/aggregate` body.
      properties:
        pages:
          type: integer
          minimum: 0
        rows:
          type: integer
          minimum: 0
        next:
          type: [string, "null"]
          description: The cursor to resume from, null where no row remains. It can be past the last page end, where the scan went on without finding a row.
        ended_by:
          type: string
          enum: [end, pages, budget_bytes, budget_time, deadline]
          description: "`end`: no row remains. `pages`: the response carried `pages` pages. `budget_bytes` and `budget_time`: the response's byte or time budget ran out. `deadline`: the stream deadline cancelled it."
        recomposed:
          const: true
          description: "`POST /v1/aggregate` only, and present only as `true`: a page of this response counted a different state of the corpus from the page before it, in this response or in the one its cursor came from."
        stream_us:
          type: integer
          minimum: 0
          description: The whole response's wall time in microseconds, including waits on the client.

    AggregateRequest:
      type: object
      additionalProperties: false
      required: [view, groupings]
      properties:
        view:
          type: string
          description: The view the counts are taken in, from `/v1/meta`. An item with no position in it is not counted.
        filters:
          $ref: "#/components/schemas/FilterExpr"
          description: The set every count is taken over. Absent is every item this principal may see in `view`. A request with `filters` names no field figure; see **Figures**.
        reference:
          description: The set each count is compared with, in the same grammar and view, drawn from the same visible set. `{}` is the whole visible set. Absent adds no comparison columns. A request with `reference` names no field figure; see **Figures**.
          oneOf:
            - type: object
              additionalProperties: false
              maxProperties: 0
            - $ref: "#/components/schemas/FilterExpr"
        groupings:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/Grouping"
          description: One table each, in this order. At most `selection.max_aggregate_groupings`.
        page_rows:
          type: integer
          minimum: 1
          description: Rows per page, capped by `selection.max_page_rows`. Absent is the cap.
        pages:
          type: integer
          minimum: 1
          description: The most pages this response may carry. Absent is as many as the response's budgets allow.
        cursor:
          type: string
          description: A previous response's `next`, unchanged, under the same request.
        compression:
          type: string
          enum: [zstd]
          description: Compress every records frame's Arrow buffers with zstd. Absent is uncompressed.

    Grouping:
      type: object
      additionalProperties: false
      description: "One table: how it divides the set, and the figures each row holds, at least one; see **Figures**. `{\"count\": true}` is one row, the size of the set."
      anyOf:
        - required: [count]
        - required: [values]
        - required: [missing]
        - required: [min]
        - required: [max]
        - required: [mean]
        - required: [summary]
      properties:
        by:
          $ref: "#/components/schemas/AggregateBy"
        count:
          type: boolean
          enum: [true]
          description: "The `count` column: the items of the set in each row, and with `reference` the `reference_count` and `lift` columns."
        values:
          $ref: "#/components/schemas/AggregateFields"
        missing:
          $ref: "#/components/schemas/AggregateFields"
        min:
          $ref: "#/components/schemas/AggregateFields"
        max:
          $ref: "#/components/schemas/AggregateFields"
        mean:
          $ref: "#/components/schemas/AggregateFields"
        summary:
          $ref: "#/components/schemas/AggregateFields"
        cells:
          type: object
          additionalProperties: false
          required: [depth]
          description: Divide the set, or each group, into cells of the view.
          properties:
            depth:
              type: integer
              minimum: 0
              maximum: 32
            area:
              type: array
              minItems: 4
              maxItems: 4
              items: { type: number }
              description: "As the viewport's `bbox`: `[x0, y0, x1, y1]` in the coordinates of the view's quantisation extent, with `x0 ≤ x1`, `y0 ≤ y1`, all finite. Only the cells at `depth` that intersect it are listed, each with all of its items; a corner outside the extent is clamped to the edge cell, as the viewport's `bbox` is. Absent is the view's whole extent."

    AggregateFields:
      type: array
      minItems: 1
      items: { type: string }
      description: "Number or timestamp fields declared with `index` or `render`, resolved as `AggregateBy`'s `field` is, each giving its own column of the figure, named `<figure>_<field>`. Only in a grouping with neither `by` nor `cells`, in a request with neither `filters` nor `reference`; see **Figures**. `summary` names all five figures of each field."

    AggregateBy:
      description: |
        A grouping's outer level. With `top` or `values`, `field` is a category field declared
        with `index` or `render`, or one with a `derived` vocabulary; with `bins`, it
        is a number or timestamp field declared with `index` or `render`. A group-scoped field resolves under
        `view` as a filter leaf on it does, or is pinned as `<field>@<key>`. `layer` is one `/v1/meta` publishes to
        this principal, and `level` follows `BrowseRequest.level`: required on a layer with
        several levels and refused on one with a single level. On a `nested` or `dag` layer `top`
        requires `cut`; see **A tree's cut**.
      oneOf:
        - type: object
          additionalProperties: false
          required: [field, top]
          properties:
            field: { type: string }
            top:
              type: integer
              minimum: 1
              description: At most `selection.max_aggregate_top`.
        - type: object
          additionalProperties: false
          required: [field, values]
          properties:
            field: { type: string }
            values:
              type: array
              minItems: 1
              items: { type: string }
              description: Vocabulary keys, at most `selection.max_aggregate_named`.
        - type: object
          additionalProperties: false
          required: [field, bins]
          properties:
            field: { type: string }
            bins:
              type: integer
              minimum: 1
              description: The number of bins with `range`, and the most bins without it. At most `selection.max_aggregate_bins`.
            range:
              type: array
              minItems: 2
              maxItems: 2
              items:
                $ref: "#/components/schemas/NumericComparand"
              description: "`[lower, upper]`, with `lower` below `upper`, in the field's own units: on a timestamp field, whole microseconds since the Unix epoch, or an RFC 3339 date or date-time with an offset written as a string, as in a filter. On an integer field, a fractional bound has the bins cut, and served, in `float64`. Absent is readable edges around the values every item this principal may see in `view` holds."
            sample:
              type: integer
              minimum: 1
              description: "Count about this many of the set's items and scale each count to the set, where the set holds more; see **Sampled bins**. Absent counts every item."
        - type: object
          additionalProperties: false
          required: [layer, top]
          properties:
            layer: { type: string }
            level: { type: integer, minimum: 0 }
            top:
              type: integer
              minimum: 1
              description: At most `selection.max_aggregate_top`.
            cut:
              $ref: "#/components/schemas/AggregateCut"
            palette_size:
              $ref: "#/components/schemas/PaletteSize"
        - type: object
          additionalProperties: false
          required: [layer, artifacts]
          properties:
            layer: { type: string }
            level: { type: integer, minimum: 0 }
            artifacts:
              type: array
              minItems: 1
              description: "`mosaica_id`s, at most `selection.max_aggregate_named`."
              items:
                oneOf:
                  - type: integer
                    minimum: 0
                  - type: string
                    pattern: "^[0-9]+$"
            palette_size:
              $ref: "#/components/schemas/PaletteSize"

    AggregateCut:
      type: object
      additionalProperties: false
      required: [zoom, bbox]
      description: Where and how finely a `nested` or `dag` layer is drawn, named as `POST /v1/artifacts/viewport` names them; see **A tree's cut**.
      properties:
        zoom:
          type: integer
          minimum: 0
          maximum: 16
          description: The tile depth.
        bbox:
          type: array
          minItems: 4
          maxItems: 4
          items: { type: number }
          description: "As the viewport's `bbox`: `[x0, y0, x1, y1]` in the coordinates of the view's quantisation extent, with `x0 ≤ x1`, `y0 ≤ y1`, all finite. Its tiles at `zoom` may number at most `max_tiles_per_request`."
        budget:
          type: integer
          minimum: 0
          description: The most artifacts the cut draws, met by drawing ancestors in place of their descendants. Absent is the finest cut.

    AggregateTableHead:
      type: object
      additionalProperties: false
      required: [grouping, total, resumed]
      description: The kind-9 frame of a `POST /v1/aggregate` body, before a table's first page in a response. Its figures are those of the page that follows it.
      properties:
        grouping:
          type: integer
          minimum: 0
          description: The table's index in `groupings`.
        total:
          type: integer
          minimum: 0
          description: Items in the set.
        reference_total:
          type: integer
          minimum: 0
          description: Items in the reference set. Present exactly when the request carries `reference`.
        groups:
          type: integer
          minimum: 0
          description: "Present exactly when the grouping has `by`: the groups with an item in the set before the cut to `top` or the named list, which are the visible distinct values, or the artifacts this principal is served with a member in the set."
        resumed:
          type: boolean
          description: Whether the table continues from the request's cursor.
        sample:
          type: object
          additionalProperties: false
          description: Present exactly when the grouping has `bins` with `sample`.
          required: [sampled, items]
          properties:
            sampled:
              type: boolean
              description: Whether any count is scaled from a sample; `false` where every item of the set, and of the reference set if any, was counted, because each held no more items than `sample` or its cut was above 2^58 (see **Sampled bins**).
            items:
              type: integer
              minimum: 0
              description: The items of the set counted, `total` where the set was not sampled.
            reference_items:
              type: integer
              minimum: 0
              description: The items of the reference set counted. Present exactly when the request carries `reference`.

    ItemRequest:
      type: object
      additionalProperties: false
      properties:
        pin:
          $ref: "#/components/schemas/Pin"

    ItemResponse:
      type: object
      additionalProperties: false
      required: [fields, labels, views, scoped]
      properties:
        fields:
          type: object
          description: The record by declared column name. Values are JSON numbers, of every width, strings (a keyword, a text, or a category's key) or booleans. A field with no value is absent, not `null`.
          additionalProperties:
            type: [number, string, boolean]
        labels:
          type: array
          items:
            type: string
          description: Why this session sees the item, as label text, sorted. The item's labels are read as one disjunction, so `a|(b&c)` and the list `a`, `b&c` give the same answer. Each term among its operands that the session holds is given, and a `public` label gives `public`. Each conjunction among its operands that the session satisfies gives one clause of it, written as the held terms joined by `&`; at each disjunction the satisfied operand with fewest terms, then the first in byte order, is taken. A term is quoted where the label grammar needs it. Every term is one the session holds, so a viewer does not learn a clause they do not satisfy or a term they do not hold. Always present.
        views:
          type: array
          description: The views this item is in that this principal may reach, sorted by id, each with the position that view places it at. A view the principal may not reach is left out, as a view nobody declared is. Always present. `[]` means none of the item's views is reachable; an item in no view at all is a `404` instead.
          items:
            type: object
            additionalProperties: false
            required: [id, x, y]
            properties:
              id:
                type: string
                description: The view's id, a plain view's name or `<group>:<key>`.
              x:
                type: integer
                minimum: 0
                maximum: 4294967295
                description: The horizontal position in this view's own grid units, 32-bit fixed point against the extent `/v1/meta` publishes for this view, the quantity the viewport's Morton codes decode to. Split out on the server, since a JSON number cannot carry a 64-bit code exactly.
              y:
                type: integer
                minimum: 0
                maximum: 4294967295
                description: The vertical position, on `x`'s terms.
        scoped:
          type: object
          description: "The group-scoped values, by family name and then by the group's key: `{\"mood\": {\"2026-Q1\": \"calm\"}}`. Two views sharing a key through a group that shares another's views share one entry. `/v1/meta`'s `scoped_scalars` says which group a family's keys belong to. Filtered per key as `views` is. A family with no reachable key, one this item holds no value under, and a `text` family, which stores no per-item value, are left out. Always present; `{}` where every attribute is entity-scoped."
          additionalProperties:
            type: object
            additionalProperties:
              type: [number, string, boolean]

    ArtifactRequest:
      type: object
      additionalProperties: false
      required: [view]
      properties:
        view:
          type: string
          description: The view whose rows the count is taken over. Required, unlike on `/v1/items/{mosaica_id}`, since a masked count is taken in one view's rows.
        zoom:
          type: integer
          minimum: 0
          maximum: 16
          description: The depth the caller draws at. A predicate or authored shape is then served without vertices that would move its edge by less than a pixel at that depth. Absent serves the whole simplified shape, at most 2,048 vertices. A derived hull is not affected.

    ArtifactResponse:
      type: object
      additionalProperties: false
      required: [layer, masked_count, rung]
      properties:
        layer:
          type: string
        key:
          type: string
          description: The publisher's key, absent where none was supplied.
        masked_count:
          type: integer
          minimum: 0
          description: How many of this artifact's members the asking principal can see. Not its size.
        centroid:
          type: array
          minItems: 2
          maxItems: 2
          items: { type: number }
          description: In the viewport's 32-bit-per-axis grid units, over the members this principal can see. Absent where the layer declares none.
        box:
          type: array
          minItems: 4
          maxItems: 4
          items: { type: integer, minimum: 0 }
          description: "`[min_x, min_y, max_x, max_y]`, in grid units."
        shape:
          type: array
          description: "The artifact's one drawn geometry, as parts, then rings, then vertices, of the kind `/v1/meta`'s `shape` gives its layer: the derived hull, with each group of members its own part; the predicate shape; or the authored one. A part's first ring is its outer boundary and the rest are holes. Absent where the layer draws none."
          items:
            type: array
            description: One part, its outer ring and then its holes.
            items:
              type: array
              description: One closed ring in grid units; a derived hull's runs anticlockwise from its lowest vertex.
              items:
                type: array
                minItems: 2
                maxItems: 2
                items: { type: integer, minimum: 0 }
        content:
          type: array
          items: { type: string }
          description: The publisher's supplied content, in the order of the layer's `supplied_content` types. Absent where the layer declares none; never partial.
        rung:
          type: integer
          minimum: 0
          description: "The declared level on a levelled layer, and `0` on a treed or flat one, since this response holds one artifact and no parent links. The one field here about the artifact rather than the asking principal: two principals served it agree on it."

    PublicationAck:
      type: object
      additionalProperties: false
      required: [publication]
      description: When a write becomes visible. Every write route's answer carries these two fields.
      properties:
        publication:
          type: integer
          minimum: 0
          description: The publication cycle this write is in. It is visible once `/control/status`'s `publication` reaches this number.
        visible:
          type: boolean
          description: "Present only with `wait=visible`: `true` where the cycle ran before the answer, `false` where `serve.visible_wait_max_secs` passed first."

    CompactionAck:
      type: object
      additionalProperties: false
      required: [request]
      description: A compaction request, numbered so `GET /control/compact/{request}` can say where it is and how it was answered.
      properties:
        request:
          type: integer
          minimum: 1
          description: This request's number, which `GET /control/compact/{request}` reads. That route says how requests are numbered.

    CompactionRequest:
      type: object
      additionalProperties: false
      required: [request, answered, answer, job]
      description: One compaction request, pending or answered.
      properties:
        request:
          type: integer
          minimum: 1
        answered:
          type: boolean
        answer:
          description: How the request was answered, or `null` while it is pending.
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [first, last, outcome, ok, gate, detail, shard, need_bytes, had_bytes]
              properties:
                first:
                  type: integer
                  minimum: 1
                  description: The first of the requests answered together with this one.
                last:
                  type: integer
                  minimum: 1
                  description: The last of the requests answered together with this one.
                outcome:
                  type: string
                  enum: [compacted, nothing_to_do, refused, failed]
                  description: "`refused` where a shard the requests named was refused at `gate`; otherwise `failed` where a compaction they started failed or was discarded; otherwise `compacted` where a compaction they started was published, whether or not other shards had nothing to compact; and `nothing_to_do` where no shard they named had anything to compact."
                ok:
                  type: boolean
                  description: Whether the request succeeded, which is so for `compacted` and `nothing_to_do` alone.
                gate:
                  type: [string, "null"]
                  description: For `refused`, one of `fold_refusals_by_gate`'s names; `null` otherwise.
                detail:
                  type: [string, "null"]
                  description: For `refused`, what the gate means and what to do about it, and for `failed`, the step the compaction stopped in, each in a sentence; `null` where the request succeeded.
                shard:
                  type: [integer, "null"]
                  minimum: 0
                  description: For `refused`, the shard refused, or `null` where the request was refused because this node may not publish (`overlay_diverged` or `prefix_diverged`); `null` otherwise.
                need_bytes:
                  type: [integer, "null"]
                  minimum: 0
                  description: For a memory or disc refusal, what the compaction needs; `null` otherwise.
                had_bytes:
                  type: [integer, "null"]
                  minimum: 0
                  description: For a memory or disc refusal, what the host had; `null` otherwise.
        job:
          description: While the request is pending, the record of the compaction serving it, or `null` until a tick has taken the request or while none is in flight. Once it is answered, the last record of the compaction that answered it, the first to fail where one did and the last to end otherwise, with `state` `finished` or `failed`; a failed record names the step the compaction stopped in. `null` where no compaction ran for the request, which is always so for `nothing_to_do`.
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/Progress"

    IngestRecords:
      type: array
      description: A JSON ingest batch, one object per row.
      items:
        $ref: "#/components/schemas/IngestRecord"

    IngestRecord:
      type: object
      description: One row of a JSON ingest batch. The properties below are the route's own; any other key is a declared attribute, a group-scoped attribute of the view's group, or an enumerated layer's name.
      properties:
        mosaica_id:
          type: [string, "null"]
          pattern: "^[0-9]+$"
          description: The item the row names, as a string of decimal digits. It must name a live or suppressed item.
        x:
          type: [number, "null"]
          description: The horizontal coordinate, on a view whose projection is `none`. A row carries both coordinates or neither.
        y:
          type: [number, "null"]
          description: The vertical coordinate, on a view whose projection is `none`.
        lon:
          type: [number, "null"]
          description: Longitude in degrees, on a projected view.
        lat:
          type: [number, "null"]
          description: Latitude in degrees, on a projected view.
        access:
          description: The row's access labels, one access expression per element; the item admits a viewer who satisfies any one of them. Absent or `null` keeps the item's label, and gives a new item the view's `point_visibility.default`; `[]` is that default.
          oneOf:
            - type: string
            - type: array
              items:
                type: [string, "null"]
            - type: "null"
        node_id:
          type: [string, "null"]
          description: Accepted and not stored.
        level:
          type: [integer, string, "null"]
          description: The level at which this row's layer keys are placed, as an integer or a string of digits. Read only where the batch carries a layer column.
      additionalProperties:
        description: "An attribute's value, or a layer's key or list of keys. A category takes its key; an integer is a JSON number or a string of digits; `timestamp_us` is microseconds since the Unix epoch, as a number or a string of digits, or an RFC 3339 date (`2020-01-01`, midnight UTC) or date-time with an offset (`2020-01-01T09:30:00Z`) as a string, and a date-time with no offset or more than six fractional-second digits is `422`. `null` is no value, and clears one an item holds; a key left out keeps it."
        type: [boolean, number, string, array, "null"]

    IngestResponse:
      type: object
      additionalProperties: false
      required: [rows, created, edited, added, unchanged, over_bound, over_bound_rows, clipped, clamped, mosaica_ids, refused, minted, joined, publication]
      properties:
        rows:
          type: integer
          minimum: 0
          description: Rows the batch carried.
        created:
          type: integer
          minimum: 0
          description: Rows that named no item and created one. `0` on a replay, as are the three counts after it, so a sum over retried batches counts each row once.
        edited:
          type: integer
          minimum: 0
          description: Rows that edited an item they named.
        added:
          type: integer
          minimum: 0
          description: Rows that added an item they named to the batch's view.
        unchanged:
          type: integer
          minimum: 0
          description: Rows that named an item and changed nothing. They write nothing; a batch of only these records its batch id alone, so it is answered as a replay when sent again.
        over_bound:
          type: integer
          minimum: 0
          description: Rows creating an item whose labels index it under more than 4,096 keys. They are stored, not refused. A replay answers the first acceptance's count.
        over_bound_rows:
          type: array
          maxItems: 100
          description: The positions in the batch, from 0, of the first 100 of those rows.
          items:
            type: integer
            minimum: 0
        clipped:
          type: integer
          minimum: 0
          description: Rows whose coordinates lay outside the projection's domain and were stored on the frame's edge. Always `0` on a view whose projection is `none`.
        clamped:
          type: integer
          minimum: 0
          description: Rows whose coordinates lay outside the view's extent and were stored on its edge.
        mosaica_ids:
          type: array
          description: One `mosaica_id` per row in the order sent, as a string of decimal digits, for the item the row created or named, and `null` for a refused row. A replay answers the original ids.
          items:
            type: [string, "null"]
            pattern: "^[0-9]+$"
        refused:
          type: array
          description: The rows the identity rule refused, in the order sent. The batch applied every other row. A strict batch with a refused row is refused instead, so a batch first accepted with `strict=true` lists none; a replay answers the first acceptance's list, whatever the replay's `strict` says.
          items:
            $ref: "#/components/schemas/RefusedRow"
        minted:
          type: integer
          minimum: 0
          description: Artifacts the batch's layer columns created on `open` layers. A created artifact cannot be undone. `0` on a replay.
        joined:
          type: integer
          minimum: 0
          description: Memberships the batch's layer columns added, to artifacts it created and to held ones alike. A row whose one change is placing its item in an artifact changes the artifact, not the item, and is counted in `unchanged`. `0` on a replay.
        replayed:
          const: true
          description: Present only when this body was already accepted from this caller under this batch id. Nothing was written.
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`. A replay names the next cycle, later than the one that published its rows."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    ChangeItem:
      type: object
      additionalProperties: false
      required: [op, match]
      description: "One change and what it names: an item, or an artifact or a layer by its `mosaica_id`."
      properties:
        op:
          type: string
          enum: [delete, suppress, unsuppress]
          description: "`delete` removes the item or the artifact; `suppress` hides it; `unsuppress` lifts a suppression."
        match:
          $ref: "#/components/schemas/AddressRow"

    AddressRow:
      type: object
      description: "One item, named by its `mosaica_id` and the values of fields declared unique, keyed by column. Every value names the item that holds it; a row naming no item, or two, is refused. A key that is neither `mosaica_id` nor a unique field names nothing: it is ignored, as a build ignores it, and the answer lists it in `ignored_columns`. A request none of whose rows carries an identifying key is `422`."
      properties:
        mosaica_id:
          type: [string, "null"]
          pattern: "^[0-9]+$"
          description: The item's `mosaica_id`, as a string of decimal digits; a JSON number is `422`.
      additionalProperties:
        description: A value of the unique field the key names. A keyword is a string, and a number is `422`; an integer or a timestamp is a number or a string of decimal digits, and a timestamp also an RFC 3339 date (`2020-01-01`, midnight UTC) or date-time with an offset (`2020-01-01T09:30:00Z`) as a string. `null` names nothing. Any other value in a unique field's column is `422`; a key that is not a unique field takes any value.

    MemberTable:
      type: object
      description: "Members, one row per member, as columns of equal length keyed by `mosaica_id` and unique field names; a cell is as in an `AddressRow`, and `null` where that column does not name the row's member. `{}` holds no member. Columns of different lengths are `422`. A key that is neither `mosaica_id` nor a unique field is ignored and listed in `ignored_columns`; a request whose tables are left with no identifying column is `422`."
      properties:
        mosaica_id:
          type: array
          items:
            type: [string, "null"]
            pattern: "^[0-9]+$"
      additionalProperties:
        type: array
        items:
          description: A cell as in an `AddressRow`.

    IgnoredColumns:
      type: array
      description: The keys of the request's `match`es or member tables that are neither `mosaica_id` nor a unique field, which named nothing and were ignored, each once.
      items:
        type: string

    RefusedRow:
      type: object
      additionalProperties: false
      required: [row, reason]
      description: A row the identity rule refused.
      properties:
        row:
          type: integer
          minimum: 0
          description: The row's position in the request, from 0.
        reason:
          $ref: "#/components/schemas/RefusalReason"

    RefusedMember:
      type: object
      additionalProperties: false
      required: [artifact, reason]
      description: A member the identity rule refused, which the artifact was published or grown without; or, with no `list` and `row`, an artifact refused whole as `label_not_held`, which wrote nothing.
      properties:
        artifact:
          type: integer
          minimum: 0
          description: The artifact's position in the request, from 0.
        list:
          type: string
          enum: [members, excluding, leaving]
        row:
          type: integer
          minimum: 0
          description: The member's row in that table, from 0.
        reason:
          $ref: "#/components/schemas/RefusalReason"

    RefusalReason:
      type: string
      enum: [names_two_items, unknown_mosaica_id, names_no_item, one_item_twice, one_value_twice, value_held, label_not_held]
      description: "`names_two_items`: the row's values name more than one item. `unknown_mosaica_id`: its `mosaica_id` names no live or suppressed item (an artifact's or a layer's names none), or on `/control/changes` no live or suppressed item, artifact or layer. `names_no_item`: its values name no item, in a request or a row that creates none. `one_item_twice`: an earlier kept row names the same item. `one_value_twice`: an earlier kept row sets the same unique value. `value_held`: an ingest row would create an item, or set a value on the item it names, with a unique value an item outside the writer's terms holds; nothing more is said of the holder. `label_not_held`: an ingest row sets an access label, or a new item takes its view's default, or an artifact sets an access label, that the writer's terms do not satisfy. Rows are decided in row order against the rows kept before them, so a refused row refuses no later row. Where two reasons apply to one row the first in this order is given: `label_not_held` and `unknown_mosaica_id` first, then `names_two_items` and `value_held`, then `names_no_item`, `one_item_twice`, `one_value_twice`."

    ChangesApplied:
      type: object
      additionalProperties: false
      required: [accepted, refused, ignored_columns, publication]
      properties:
        accepted:
          type: integer
          minimum: 0
          description: Changes applied.
        refused:
          type: array
          description: The changes refused, in the order sent. Empty with `strict=true`.
          items:
            $ref: "#/components/schemas/RefusedRow"
        ignored_columns:
          $ref: "#/components/schemas/IgnoredColumns"
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    AttributeDeclaration:
      type: object
      additionalProperties: false
      required: [name, type]
      description: A corpus declaration's `[[attribute]]` block, without the keys that say where a build reads its values.
      properties:
        name:
          type: string
          description: The column's name, unique among attributes and layers.
        title:
          type: [string, "null"]
          description: A human-readable title. Not part of the column's identity.
        type:
          type: string
          enum: [bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, timestamp_us, keyword, text, category]
          description: The value type. `category` stores its vocabulary's codes at the vocabulary's width.
        vocabulary:
          type: [string, "null"]
          description: The vocabulary a `category` draws on, by name. Required for a category and refused on any other type.
        analyser:
          type: [string, "null"]
          description: The analyser that segments a `text` column for search. Absent is `unicode`, the only one. Refused on any other type.
        index:
          type: boolean
          default: false
          description: Build a search structure, so a filter may name the column.
        render:
          type: boolean
          default: false
          description: Serve the column in the viewport's points frame. `true` is refused on this route for a new column.
        unique:
          type: boolean
          default: false
          description: No two live or suppressed items may hold one value, and `eq` and `in` filters answer from the column's index. For `keyword`, integer and `timestamp_us` columns scoped to the entity. A null is no value.
        scope:
          description: "`entity`, one value per item, or `{\"group\": \"<view_group>\"}`, one value per item for each view of that group. Absent is `entity`."
          default: entity
          oneOf:
            - type: string
              const: entity
            - $ref: "#/components/schemas/GroupScope"

    GroupScope:
      type: object
      additionalProperties: false
      required: [group]
      properties:
        group:
          type: string
          description: The view group whose views the values or artifacts are per.

    AttributeDeclared:
      type: object
      additionalProperties: false
      required: [name, existing, publication]
      properties:
        name:
          type: string
        existing:
          type: boolean
          description: "`true` where the name already carried this declaration and nothing was written."
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    ShardsDeclaration:
      type: object
      additionalProperties: false
      required: [size]
      description: A corpus declaration's `[shards]` table.
      properties:
        size:
          type: integer
          minimum: 1
          description: The high water at which the open shard seals.

    ShardsDeclared:
      type: object
      additionalProperties: false
      required: [size, existing, publication]
      properties:
        size:
          type: integer
          minimum: 1
        existing:
          type: boolean
          description: "`true` where the bundle already had this size and nothing was written."
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    VocabularyDeclaration:
      type: object
      additionalProperties: false
      required: [value_set, visibility, width]
      description: A corpus declaration's `[[vocabulary]]` block, without its name, which is in the path, and without the keys that say where a build reads its values.
      properties:
        title:
          type: [string, "null"]
          description: A human-readable title. Not part of the identity.
        value_set:
          type: string
          enum: [closed, open]
          description: "`closed` refuses a key it does not hold at ingest; `open` adds it."
        visibility:
          type: string
          enum: [public, derived]
          description: "`public` serves the values as declared to every viewer. `derived` serves a value only to a viewer who can see an item carrying it."
        width:
          type: string
          enum: [u8, u16, u32]
          description: The code's width, which bounds how many values the vocabulary can hold.
        values:
          type: array
          default: []
          description: The values to add, each with a code the server draws.
          items:
            $ref: "#/components/schemas/VocabularyValue"
        reserved:
          type: array
          default: []
          description: Retired codes, never drawn. Each is from 1 to the width's maximum. Compared as a set.
          items:
            type: integer
            minimum: 1

    VocabularyValue:
      type: object
      additionalProperties: false
      required: [key]
      properties:
        key:
          type: string
          minLength: 1
          description: The value's key, which ingest sends and filters name. Unique in the vocabulary.
        title:
          type: [string, "null"]
          description: The name a client draws. On a key the vocabulary holds, a title replaces the held one.

    VocabularyValues:
      type: object
      additionalProperties: false
      required: [values]
      properties:
        values:
          type: array
          items:
            $ref: "#/components/schemas/VocabularyValue"

    VocabularyDeclared:
      type: object
      additionalProperties: false
      required: [name, existing, added, titles, publication]
      properties:
        name:
          type: string
        existing:
          type: boolean
          description: "`true` where the name already carried this identity."
        added:
          type: integer
          minimum: 0
          description: Keys the request added.
        titles:
          type: integer
          minimum: 0
          description: Held keys whose title the request changed.
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    VocabularyValuesAdded:
      type: object
      additionalProperties: false
      required: [name, publication]
      properties:
        name:
          type: string
        added:
          type: integer
          minimum: 0
          description: Keys the page added. Absent for a caller holding `write` and not `write-all` on a `derived` vocabulary.
        existing:
          type: integer
          minimum: 0
          description: Keys the page named that the vocabulary already held. Absent for a caller holding `write` and not `write-all` on a `derived` vocabulary.
        titles:
          type: integer
          minimum: 0
          description: Held keys whose title the page changed. Absent for a caller holding `write` and not `write-all` on a `derived` vocabulary.
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    DeclaredExtent:
      description: |
        The frame a view's positions are quantised over. It cannot be changed after it is declared.
        It takes the spellings a corpus declaration's `extent` takes, under the same rules. Any
        view takes a box in the coordinates it quantises across, as `x` and `y` or as `min` and
        `max`, taken as written; for a projected view those are the coordinates the projection
        produces, so the `quantisation` `/v1/meta` reports for one view gives another its exact
        frame. A projected view also takes a box in degrees, as `lon` and `lat`, which is widened
        to the smallest aligned square that holds it, the square a build gives the same box.
        `auto`, as the string or as `{"auto": true}`, fits the frame to a view's rows, and a view
        declared here has none, so it is refused.
      oneOf:
        - type: string
        - $ref: "#/components/schemas/Extent"

    Extent:
      type: object
      additionalProperties: false
      description: An `extent` written as an object. Write one of `x` with `y`, `min` with `max`, or, on a projected view, `lon` with `lat`.
      properties:
        x:
          type: array
          minItems: 2
          maxItems: 2
          items: { type: number }
          description: "`[min, max]` on the horizontal axis."
        y:
          type: array
          minItems: 2
          maxItems: 2
          items: { type: number }
          description: "`[min, max]` on the vertical axis."
        min:
          type: number
          description: The low end of both axes.
        max:
          type: number
          description: The high end of both axes.
        lon:
          type: array
          minItems: 2
          maxItems: 2
          items: { type: number }
          description: "`[west, east]` in degrees, within ±180, on a projected view."
        lat:
          type: array
          minItems: 2
          maxItems: 2
          items: { type: number }
          description: "`[south, north]` in degrees, within ±90, on a projected view."
        auto:
          type: boolean
          description: Refused here, as `auto` is.
        margin:
          type: number
          description: Taken only with `auto`, and refused here with it.

    ViewVisibility:
      description: The access labels a viewer must satisfy at least one of to reach the view, as one access expression or a list of them. It cannot be changed after it is declared.
      oneOf:
        - type: string
        - type: array
          items: { type: string }

    PointVisibility:
      type: object
      additionalProperties: false
      properties:
        default:
          type: [string, "null"]
          description: The access label, an access expression, that an ingested row with no label takes. Absent or `null`, such a row is refused.

    Projection:
      type: string
      enum: [none, web_mercator, equirectangular, plate_carree, gall_isographic]
      default: none
      description: What places positions in the view. `none` takes coordinates as written; the others take longitude and latitude in degrees. `plate_carree` is another name for `equirectangular`.

    ViewGroupDeclaration:
      type: object
      additionalProperties: false
      required: [extent]
      description: A corpus declaration's `[[view_group]]` block, without its name, which is in the path, and without its roster and source.
      properties:
        title:
          type: [string, "null"]
        projection:
          $ref: "#/components/schemas/Projection"
        extent:
          $ref: "#/components/schemas/DeclaredExtent"
        visibility:
          description: The labels every view of the group requires; a view's own `visibility` applies as well. Absent or `null` is `public`.
          oneOf:
            - $ref: "#/components/schemas/ViewVisibility"
            - type: "null"
        point_visibility:
          oneOf:
            - $ref: "#/components/schemas/PointVisibility"
            - type: "null"
        members:
          type: [string, "null"]
          description: Another view group whose views this group lays out a second time, under the same keys. That group must own its keys, and a group with `members` declares no `metadata`.
        metadata:
          type: array
          default: []
          description: The typed values each view of the group carries, in the order a client is served them.
          items:
            $ref: "#/components/schemas/MetadataField"

    MetadataField:
      type: object
      additionalProperties: false
      required: [name, type]
      properties:
        name:
          type: string
          description: The name, unique in the group.
        type:
          type: string
          enum: [bool, u8, u16, u32, u64, i8, i16, i32, i64, float, text, timestamp_us, category]
          description: The value's type. A `category` value is a key of its `vocabulary`.
        vocabulary:
          type: [string, "null"]
          description: The vocabulary a `category` draws on, which is required for one and refused for any other type.

    ViewGroupDeclared:
      type: object
      additionalProperties: false
      required: [group, existing, publication]
      properties:
        group:
          type: string
        existing:
          type: boolean
          description: "`true` where the name already carried this declaration and nothing was written."
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    PlainViewDeclaration:
      type: object
      additionalProperties: false
      required: [extent]
      description: A corpus declaration's `[[view]]` block, without its name, which is in the path, and without its source.
      properties:
        title:
          type: [string, "null"]
          description: The name a client draws. Absent is the view's id.
        projection:
          $ref: "#/components/schemas/Projection"
        extent:
          $ref: "#/components/schemas/DeclaredExtent"
        visibility:
          description: The view's visibility. Absent or `null` is `public`.
          oneOf:
            - $ref: "#/components/schemas/ViewVisibility"
            - type: "null"
        point_visibility:
          oneOf:
            - $ref: "#/components/schemas/PointVisibility"
            - type: "null"

    PlainViewDeclared:
      type: object
      additionalProperties: false
      required: [view, existing, publication]
      properties:
        view:
          type: string
        existing:
          type: boolean
          description: "`true` where the name already carried this declaration and nothing was written."
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    ViewRecord:
      type: object
      additionalProperties: false
      description: A view's roster record. It cannot be changed after the view is created.
      properties:
        visibility:
          description: The view's own visibility, applied as well as the group's. Absent or `null` applies the group's alone.
          oneOf:
            - $ref: "#/components/schemas/ViewVisibility"
            - type: "null"
        metadata:
          type: object
          default: {}
          description: One value for each name the group declares, and no other. A `category` is its vocabulary's key as a string, taken as a row's category value is, so a key a `closed` vocabulary does not list is `422`, and one an `open` vocabulary does not hold is added to it. A `timestamp_us` is microseconds since the Unix epoch, as an integer or a string of digits, or an RFC 3339 date (`2020-01-01`, midnight UTC) or date-time with an offset (`2020-01-01T09:30:00Z`) as a string, read as `schema.toml` reads one; a date-time with no offset, or with more than six fractional-second digits, is `422`. An integer is read as a float where the name is declared a float; a float is never read as an integer.
          additionalProperties:
            type: [boolean, number, string]

    ViewCreated:
      type: object
      additionalProperties: false
      required: [view, group, key, existing, publication]
      properties:
        view:
          type: string
          description: The view's id, `<group>:<key>`.
        group:
          type: string
        key:
          type: string
        existing:
          type: boolean
          description: "`true` where a live view already held the key with this record."
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    ViewDropped:
      type: object
      additionalProperties: false
      required: [deleted, publication]
      properties:
        deleted:
          type: integer
          minimum: 0
          description: Items the drop deleted, those it left with a row in no view.
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    LayerDeclaration:
      type: object
      additionalProperties: false
      required: [name, views, membership, visibility, artifact_visibility, require_member_visibility, hierarchy]
      description: A corpus declaration's `[[layer]]` block.
      properties:
        name:
          type: string
          description: The layer's identity, for ever. Not empty and not `all`.
        title:
          type: [string, "null"]
        views:
          type: array
          items: { type: string }
          description: The views the layer is drawn on. A view group's name stands for every view the group holds, and for each view created in it later.
        membership:
          description: "Where artifacts get their members: `enumerated`, a stored set per artifact; `spatial`, the items inside the artifact's shape; `{\"attribute\": \"<column>\"}`, the items carrying each value of an attribute."
          oneOf:
            - type: string
              enum: [enumerated, spatial]
            - type: object
              additionalProperties: false
              required: [attribute]
              properties:
                attribute:
                  type: string
        value_set:
          type: string
          enum: [closed, open]
          default: closed
          description: "`closed` refuses a key no artifact holds when a batch names it; `open` creates the artifact."
        visibility:
          type: [string, "null"]
          description: The access label, an access expression, a viewer must satisfy to know the layer exists. `public` or `null`, every viewer may.
        artifact_visibility:
          type: object
          additionalProperties: false
          required: [default]
          description: Whether artifacts carry access labels of their own, and what one carrying none gets.
          properties:
            field:
              type: [string, "null"]
              description: Present, artifacts carry their own labels, and a publication states each artifact's `access`. Absent or `null`, they carry none, and a label on one is refused.
            default:
              description: "What an artifact with no label of its own gets: `inherited`, the layer's `visibility` alone, or `{\"label\": \"<label>\"}`."
              oneOf:
                - type: string
                  const: inherited
                - type: object
                  additionalProperties: false
                  required: [label]
                  properties:
                    label:
                      type: string
        require_member_visibility:
          description: "How many of an artifact's members a viewer must see for the artifact to exist for them: `{\"count\": n}`, or `{\"fraction\": p}` of its declared members with `p` in (0, 1]. `null` is no such rule. A fraction is refused where the membership is `spatial` or an attribute."
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [count]
              properties:
                count:
                  type: integer
                  minimum: 0
            - type: object
              additionalProperties: false
              required: [fraction]
              properties:
                fraction:
                  type: number
        hierarchy:
          type: object
          required: [kind]
          description: Where the layer's lineage lives. Keys other than these two are ignored.
          properties:
            kind:
              type: string
              enum: [flat, nested, dag, stacked, tiered]
              description: "`flat` has no lineage. `nested` is a tree held in parent edges, and `dag` the same with several parents allowed; neither declares levels. `stacked` is independent analyses, one per level. `tiered` is levels with containment edges from each level to the one below. `stacked` and `tiered` declare at least one level."
            prune_children:
              type: boolean
              default: false
              description: The viewport's default cut. `true` serves only the deepest passing artifact on each branch where a parent and a child both pass; a request may ask for more.
        content:
          type: object
          additionalProperties: false
          description: What artifacts carry beside their members.
          properties:
            computed:
              type: array
              default: []
              description: Properties computed per viewer from the members that viewer can see.
              items:
                type: string
                enum: [centroid, box, hull]
            supplied:
              type: array
              default: []
              description: Content the publisher supplies, in the order each artifact's `values` carry it.
              items:
                $ref: "#/components/schemas/SuppliedContent"
        depends_on:
          type: array
          default: []
          items: { type: string }
          description: Registered layers whose artifacts this layer's artifacts may attach to or name as parents.
        levels:
          type: array
          default: []
          description: The layer's levels, numbered densely from 0. Empty on a `flat`, `nested` or `dag` layer.
          items:
            $ref: "#/components/schemas/LevelDeclaration"
        layout:
          description: How each level's membership is stored for serving. Absent or `null` lets the build and every fold choose. Every form serves the same answers.
          oneOf:
            - type: "null"
            - type: string
              enum: [artifact_major, row_major_label, row_major_list]
        shape:
          description: The kind of shape a `spatial` layer's artifacts carry. Refused on any other membership. A spatial layer without it accepts no artifacts.
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [kind]
              properties:
                kind:
                  type: string
                  enum: [bbox, circle, ellipse, polygon]
        scope:
          description: "`entity`, one artifact set drawn on every view the layer names, or `{\"group\": \"<view_group>\"}`, a different set per view of that group. Absent is `entity`."
          default: entity
          oneOf:
            - type: string
              const: entity
            - $ref: "#/components/schemas/GroupScope"

    SuppliedContent:
      type: object
      additionalProperties: false
      required: [name, type, require_member_visibility]
      properties:
        name:
          type: string
          description: The content's name, unique in the layer.
        type:
          type: string
          description: The content's type, published in `/v1/meta`'s `supplied_content`. `polygon`, `circle` and `ellipse` are authored shapes, read from the value's text; any other type is served as the string sent.
        require_member_visibility:
          type: string
          enum: [all, inherited]
          description: "`all` serves the content only to a viewer who can see every item it was made from, and each content carries `generated_from`. `inherited` serves it wherever the artifact is served, and carries no `generated_from`."

    LevelDeclaration:
      type: object
      required: [level]
      description: One level of a `stacked` or `tiered` layer. Keys other than these three are ignored.
      properties:
        level:
          type: integer
          minimum: 0
        title:
          type: [string, "null"]
        zoom:
          description: "`[min, max]` tile depth: a viewport naming no `levels` is answered at the levels whose range covers its depth. Absent or `null`, the level is served at every depth. A range holding no depth from 0 to 16 is refused."
          oneOf:
            - type: "null"
            - type: array
              minItems: 2
              maxItems: 2
              items: { type: integer, minimum: 0 }

    LayerRegistered:
      type: object
      additionalProperties: false
      required: [name, mosaica_id, existing, publication]
      properties:
        name:
          type: string
        existing:
          type: boolean
          description: "`true` where a layer of this name already held this declaration."
        mosaica_id:
          type: string
          pattern: "^[0-9]+$"
          description: The layer's own identifier, the address `POST /control/changes` suppresses the layer by.
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    PublishRequest:
      type: object
      additionalProperties: false
      required: [artifacts]
      properties:
        level:
          type: integer
          minimum: 0
          default: 0
          description: The level the artifacts go into. Absent is 0, the only level of a layer that declares none.
        default_space:
          type: string
          enum: [view, wgs84]
          description: The space of a shape, and of authored shape content, whose artifact names none. `view` takes the view's own coordinates; `wgs84` takes degrees, projected by each view's own projection and refused on a view whose projection is `none`. Absent is `view`.
        artifacts:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PublishedArtifact"

    PublishedArtifact:
      type: object
      additionalProperties: false
      description: One artifact of a publication. Its members are `members` or `excluding`; an artifact with `attached_to` may carry neither, and is served over its target's members.
      properties:
        key:
          type: [string, "null"]
          description: The publisher's key, unique in the level. Needed by any artifact another names as a parent or target, and by any grown later.
        view:
          type: [string, "null"]
          description: The key of the view the artifact belongs to. Required on a layer scoped to a group, refused on any other.
        members:
          oneOf:
            - $ref: "#/components/schemas/MemberTable"
            - type: "null"
          description: The members. `{}` is a membership that holds nobody.
        excluding:
          oneOf:
            - $ref: "#/components/schemas/MemberTable"
            - type: "null"
          description: The view's items the membership leaves out. The membership is every other item of the view. Refused beside `members`.
        content:
          type: array
          default: []
          description: Ranked supplied content, most specific first.
          items:
            $ref: "#/components/schemas/ContentItem"
        attached_to:
          oneOf:
            - $ref: "#/components/schemas/Attachment"
            - type: "null"
        parent:
          type: array
          default: []
          items: { type: string }
          description: Parent keys. At most one on a `nested` or `tiered` layer, any number on a `dag`. A parent already exists or comes earlier in the batch.
        bbox:
          type: [array, "null"]
          items: { type: number }
          description: "`[min_x, min_y, max_x, max_y]`, on a layer whose `shape` is `bbox`."
        circle:
          type: [array, "null"]
          items: { type: number }
          description: "`[cx, cy, r]`, on a layer whose `shape` is `circle`."
        ellipse:
          type: [array, "null"]
          items: { type: number }
          description: "`[cx, cy, a, b, angle_degrees]`, on a layer whose `shape` is `ellipse`."
        wkt:
          type: [string, "null"]
          description: A WKT polygon or multipolygon, on a layer whose `shape` is `polygon`.
        space:
          type: [string, "null"]
          enum: [view, wgs84, null]
          description: This artifact's space, in place of `default_space`.
        access:
          type: [array, "null"]
          items: { type: string }
          description: The artifact's own access labels, each an access expression; a viewer satisfying any one of them is admitted. `null` or `[]` is no label, which `artifact_visibility.default` answers. Absent states none, which a layer whose `artifact_visibility` names a field refuses. Refused on a layer that names no field.

    ContentItem:
      type: object
      additionalProperties: false
      required: [values]
      properties:
        values:
          type: array
          items: { type: string }
          description: One value per supplied content the layer declares, in the declaration's order.
        generated_from:
          oneOf:
            - $ref: "#/components/schemas/MemberTable"
            - type: "null"
          description: The items this content was made from. Required where the content's `require_member_visibility` is `all`, and refused where it is `inherited`. A member naming no item, or two, refuses the request.

    Attachment:
      type: object
      additionalProperties: false
      required: [layer, key]
      description: The artifact this one is attached to, such as a cluster its label names. The target already exists, on a layer this one's `depends_on` names. The attached artifact is served only where its target is.
      properties:
        layer:
          type: string
        level:
          type: integer
          minimum: 0
          default: 0
        key:
          type: string

    ArtifactsPublished:
      type: object
      additionalProperties: false
      required: [artifacts, created, without_content, filled, joined, refused, ignored_columns, publication]
      properties:
        artifacts:
          type: array
          description: One entry per artifact, in the order sent.
          items:
            type: object
            additionalProperties: false
            required: [key, mosaica_id]
            properties:
              key:
                type: [string, "null"]
              mosaica_id:
                type: [string, "null"]
                pattern: "^[0-9]+$"
                description: The artifact's identifier, the held artifact's own where the key was held; `null` for an artifact refused as `label_not_held`.
        created:
          type: integer
          minimum: 0
          description: Artifacts the batch created.
        without_content:
          type: integer
          minimum: 0
          description: Of those, the ones with no content on a layer that declares some.
        filled:
          type: integer
          minimum: 0
          description: Parts filled on held artifacts.
        joined:
          type: integer
          minimum: 0
          description: Memberships added, to created and held artifacts alike.
        refused:
          type: array
          description: The members the identity rule refused, which the artifacts were published without, and the artifacts refused as `label_not_held`. Empty with `strict=true`.
          items:
            $ref: "#/components/schemas/RefusedMember"
        ignored_columns:
          $ref: "#/components/schemas/IgnoredColumns"
        shapes:
          type: array
          description: Present when the batch carried a shape or authored shape content.
          items:
            $ref: "#/components/schemas/ShapeReport"
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    ShapeReport:
      type: object
      additionalProperties: false
      required: [key, views]
      description: What fitting one shape to each of its layer's views did. A shape is fitted to every view, and reported on those a session holding the caller's terms is served, every one for a caller holding `write-all`.
      properties:
        key:
          type: [string, "null"]
          description: The artifact's key.
        content:
          type: integer
          minimum: 0
          description: The rank of the authored shape content this reports on. Absent for the artifact's own shape.
        views:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [view, clipped, outside, rings_dropped, degrees_looking, vertices_in, vertices_out, parts, rings, interior_tiles, boundary_cells]
            properties:
              view:
                type: string
              clipped:
                type: boolean
                description: Part of the shape lay outside the view's extent and was cut away.
              outside:
                type: boolean
                description: The whole shape lay outside the extent, so it holds nothing.
              rings_dropped:
                type: integer
                minimum: 0
                description: Rings that fell below three distinct vertices on the view's grid.
              degrees_looking:
                type: boolean
                description: Every coordinate lay within ±180 by ±90 while the extent does not. The shape may have been written in degrees for a view that is not.
              vertices_in:
                type: integer
                minimum: 0
              vertices_out:
                type: integer
                minimum: 0
              parts:
                type: integer
                minimum: 0
              rings:
                type: integer
                minimum: 0
              interior_tiles:
                type: integer
                minimum: 0
                description: Grid tiles wholly inside the shape.
              boundary_cells:
                type: integer
                minimum: 0
                description: Grid cells the shape's edge crosses.

    GrowRequest:
      type: object
      additionalProperties: false
      required: [artifacts]
      description: "`level` and `default_space` are `PublishRequest`'s."
      properties:
        level:
          type: integer
          minimum: 0
          default: 0
        default_space:
          type: string
          enum: [view, wgs84]
        artifacts:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/GrowingArtifact"

    GrowingArtifact:
      type: object
      additionalProperties: false
      required: [key]
      description: One artifact of a growth, by key. Every other field is optional, and a row with only a key changes nothing.
      properties:
        key:
          type: string
          description: The key the artifact was published under.
        view:
          type: [string, "null"]
          description: The key of the view the artifact belongs to, as on a publication. Required on a layer scoped to a group, refused on any other.
        rank:
          type: [integer, "null"]
          minimum: 0
          maximum: 65535
          description: Absent, `members` joins the membership. Present, `members` and `leaving` move the generating set of the content at this rank, its position among the artifact's contents, and the row fills no part. A withdrawn rank is `422`.
        members:
          oneOf:
            - $ref: "#/components/schemas/MemberTable"
            - type: "null"
          description: Members joining.
        leaving:
          oneOf:
            - $ref: "#/components/schemas/MemberTable"
            - type: "null"
          description: Members leaving a generating set, applied after the joins. Needs a `rank`.
        parent:
          type: array
          default: []
          items: { type: string }
          description: Parent keys. Filled where the artifact holds none; `409` where it holds others.
        attached_to:
          oneOf:
            - $ref: "#/components/schemas/Attachment"
            - type: "null"
        content:
          type: array
          default: []
          description: Contents by rank, each filled where the artifact lacks it. A rank is a position among the artifact's contents, and a content at a rank holding none, withdrawn or past the last, is a new content at the next free rank. A content here carries no generating set, so on a layer whose content requires every member visible it is `422`, and a `PUT` of the key supplies it with its `generated_from`.
          items:
            type: object
            additionalProperties: false
            required: [rank, values]
            properties:
              rank:
                type: integer
                minimum: 0
                maximum: 65535
              values:
                type: array
                items: { type: string }
        bbox:
          type: [array, "null"]
          items: { type: number }
          description: A shape field, as on a publication. Only a `spatial` layer takes a shape, and a `spatial` layer's artifacts cannot be grown, so a row carrying one is `422`.
        circle:
          type: [array, "null"]
          items: { type: number }
          description: As `bbox`.
        ellipse:
          type: [array, "null"]
          items: { type: number }
          description: As `bbox`.
        wkt:
          type: [string, "null"]
          description: As `bbox`.
        space:
          type: [string, "null"]
          enum: [view, wgs84, null]
          description: This row's space for authored shape content, in place of `default_space`.
        access:
          type: [array, "null"]
          items: { type: string }
          description: The artifact's access labels, filled where it has none.

    MembershipsGrown:
      type: object
      additionalProperties: false
      required: [artifacts, refused, ignored_columns, publication]
      properties:
        artifacts:
          type: array
          description: One entry per row, in the order sent.
          items:
            type: object
            additionalProperties: false
            required: [key, mosaica_id, joined, filled, left]
            properties:
              key:
                type: string
              mosaica_id:
                type: [string, "null"]
                pattern: "^[0-9]+$"
                description: "`null` for a row refused as `label_not_held`, whose counts are 0."
              joined:
                type: integer
                minimum: 0
                description: Joining members the membership did not already hold.
              filled:
                type: integer
                minimum: 0
                description: Parts that were absent and are now held.
              left:
                type: integer
                minimum: 0
                description: Leaving members taken out of a generating set.
              withdrawn:
                type: integer
                minimum: 0
                description: Present where this row emptied the generating set at its rank, and that rank. The content is withdrawn, and its rank is never taken again; a content supplied in its place takes the next free rank.
        refused:
          type: array
          description: The members the identity rule refused, which the artifacts were grown without, and the rows refused as `label_not_held`. Empty with `strict=true`.
          items:
            $ref: "#/components/schemas/RefusedMember"
        ignored_columns:
          $ref: "#/components/schemas/IgnoredColumns"
        shapes:
          type: array
          description: Present when the batch filled authored shape content.
          items:
            $ref: "#/components/schemas/ShapeReport"
        publication:
          type: integer
          minimum: 0
          description: "`PublicationAck.publication`."
        visible:
          type: boolean
          description: "`PublicationAck.visible`."

    SharedCacheStatus:
      type: object
      additionalProperties: false
      description: A cache shared across sessions, in `GET /control/status`.
      required: [entries, bytes, bound_bytes, hits, misses, building_refusals, waits_satisfied, waiters_now, evictions, oversized_admissions]
      properties:
        entries: { type: integer, minimum: 0 }
        bytes: { type: integer, minimum: 0 }
        bound_bytes: { type: integer, minimum: 0 }
        hits: { type: integer, minimum: 0 }
        misses: { type: integer, minimum: 0 }
        building_refusals: { type: integer, minimum: 0 }
        waits_satisfied: { type: integer, minimum: 0 }
        waiters_now:
          type: integer
          minimum: 0
          description: Requests waiting for another's build at this instant.
        evictions: { type: integer, minimum: 0 }
        oversized_admissions:
          type: integer
          minimum: 0
          description: Builds larger than the whole bound, served to their callers and not kept.

    Status:
      type: object
      additionalProperties: false
      description: The `GET /control/status` document. Counters count from the process's start; gauges are read when the request is answered. Byte figures are bytes and times are in the unit their name gives.
      required: [entity_id_high_water, shards, publication, partitions, compute, bulk, artifacts, write_executor, ingest, limits, segments, overlay, layer_spaces, compaction, jobs, fragmentation, row_projection_cache, projection_builds_by_route, fragment_cache, sessions, masked_count_cache, region_cache, filter_cache, derived_cache, suggest_sets, occupancy, heap]
      properties:
        entity_id_high_water:
          type: integer
          minimum: 0
          description: Each shard's high water, the id past every one its allocator has issued, summed over the bundle's shards.
        shards:
          type: object
          additionalProperties: false
          required: [size, records]
          properties:
            size:
              type: integer
              minimum: 1
              description: The high water at which the open shard seals, as `PUT /control/shards` or the build's `[shards]` declared it.
            records:
              type: array
              description: One record per shard the bundle holds, ascending by number.
              items:
                type: object
                additionalProperties: false
                required: [shard, state, high_water, live_rows, pool, retired, deleted, suppressed, segments, last_compaction, compacting]
                properties:
                  shard:
                    type: integer
                    minimum: 0
                  state:
                    type: string
                    enum: [open, sealed]
                    description: An open shard takes new items at its high water; a sealed one only into ids its compactions freed.
                  high_water:
                    type: integer
                    minimum: 0
                    description: One past the highest id the shard's allocator has issued.
                  live_rows:
                    type: integer
                    minimum: 0
                    description: Rows the shard's segments hold, deleted ones not yet compacted included.
                  pool:
                    type: integer
                    minimum: 0
                    description: Freed ids waiting to be issued again, at every tenancy, those held until the log moves past them included.
                  retired:
                    type: integer
                    minimum: 0
                    description: Numbers retired at the last tenancy, which are never issued again.
                  deleted:
                    type: integer
                    minimum: 0
                    description: Deleted items whose rows a compaction of the shard has not yet removed.
                  suppressed:
                    type: integer
                    minimum: 0
                    description: Suppressed items in the shard.
                  segments:
                    type: integer
                    minimum: 0
                    description: The most segments any one view of the shard holds.
                  last_compaction:
                    description: The shard's last compaction since the service started, or `null` where it has had none.
                    oneOf:
                      - type: "null"
                      - type: object
                        additionalProperties: false
                        required: [secs, rss_bytes]
                        properties:
                          secs: { type: integer, minimum: 0 }
                          rss_bytes:
                            type: integer
                            minimum: 0
                            description: The highest resident memory sampled between its passes, so it understates the peak.
                  compacting:
                    type: boolean
                    description: Whether a compaction of the shard is running or waits to publish.
        publication:
          type: integer
          minimum: 0
          description: Publication cycles completed since the write executor started. A write is visible once this reaches the `publication` its answer named.
        partitions:
          type: array
          description: One entry per partition of the bundle.
          items:
            type: object
            additionalProperties: false
            required: [partition, segments_version, watermark, readiness]
            properties:
              partition:
                type: string
              segments_version:
                type: integer
                minimum: 0
                description: Moves when a flush, merge or fold publishes.
              watermark:
                type: integer
                minimum: 0
                description: The entities the published geometry covers, summed over the bundle's shards.
              readiness:
                type: boolean
                description: Whether the write executor is `running`, the same for every partition. It does not reflect a stepped-down partition, which `/readyz` does.
        compute:
          type: object
          additionalProperties: false
          required: [admission, queue, in_flight, waiting, streaming, shed_total]
          description: The compute admission limit the viewport, single-item and single-artifact reads, browse, `/v1/aggregate` and a suggest counting within a view share.
          properties:
            admission:
              type: integer
              minimum: 0
              description: Requests computing at once.
            queue:
              type: integer
              minimum: 0
              description: Requests that may wait for a slot.
            in_flight:
              type: integer
              minimum: 0
            waiting:
              type: integer
              minimum: 0
            streaming:
              type: integer
              minimum: 0
              description: Responses still streaming, which hold a slot but no compute.
            shed_total:
              type: integer
              minimum: 0
              description: Requests this limit answered `429`. A `429` for a request waiting on another's build is not counted.
        bulk:
          type: object
          additionalProperties: false
          required: [admission, queue, in_flight, waiting, shed_total]
          description: The admission limit `POST /v1/items` and `POST /v1/artifacts` run under, `serve.bulk_admission`. A read holds its slot for its whole response.
          properties:
            admission:
              type: integer
              minimum: 0
            queue:
              type: integer
              minimum: 0
              description: Always 0, so a read past the limit is refused at once.
            in_flight:
              type: integer
              minimum: 0
            waiting:
              type: integer
              minimum: 0
            shed_total:
              type: integer
              minimum: 0
        artifacts:
          type: object
          additionalProperties: false
          required: [admission, queue, in_flight, waiting, shed_total]
          description: The admission limit `POST /v1/artifacts/viewport` runs under, `serve.artifact_admission`, as many waiting as running. A response holds its slot until its last tile.
          properties:
            admission:
              type: integer
              minimum: 0
            queue:
              type: integer
              minimum: 0
            in_flight:
              type: integer
              minimum: 0
            waiting:
              type: integer
              minimum: 0
            shed_total:
              type: integer
              minimum: 0
              description: Requests this limit answered `429`.
        write_executor:
          $ref: "#/components/schemas/WriteExecutorStatus"
        ingest:
          type: object
          additionalProperties: false
          required: [admission, in_flight, shed_total]
          description: The admission limit of `POST /control/ingest`, `ingest.ingest_admission`.
          properties:
            admission:
              type: integer
              minimum: 0
            in_flight:
              type: integer
              minimum: 0
            shed_total:
              type: integer
              minimum: 0
              description: Requests this limit answered `429`. A `429` for a full buffer or queue is not counted.
        limits:
          type: object
          additionalProperties: false
          required: [ingest, publish, grow, changes, declarations]
          description: Each write route's caps, so a client can size its requests. A request over one is `422` naming the limit, and is never truncated.
          properties:
            ingest:
              $ref: "#/components/schemas/BatchLimits"
            publish:
              type: object
              additionalProperties: false
              required: [route, max_artifacts_per_request, max_body_bytes, max_shape_vertices, max_excluded_per_request]
              properties:
                route:
                  type: string
                max_artifacts_per_request:
                  type: integer
                  minimum: 0
                max_body_bytes:
                  type: integer
                  minimum: 0
                max_shape_vertices:
                  type: integer
                  minimum: 0
                max_excluded_per_request:
                  type: integer
                  minimum: 0
            grow:
              type: object
              additionalProperties: false
              required: [route, max_members_per_request, max_body_bytes]
              properties:
                route:
                  type: string
                max_members_per_request:
                  type: integer
                  minimum: 0
                  description: Joining and leaving members together.
                max_body_bytes:
                  type: integer
                  minimum: 0
            changes:
              type: object
              additionalProperties: false
              required: [route, max_changes_per_request, max_body_bytes]
              properties:
                route:
                  type: string
                max_changes_per_request:
                  type: integer
                  minimum: 0
                max_body_bytes:
                  type: integer
                  minimum: 0
            declarations:
              type: object
              additionalProperties: false
              required: [route, max_records_per_request, max_body_bytes]
              description: The declaration routes, which the `route` string lists.
              properties:
                route:
                  type: string
                max_records_per_request:
                  type: integer
                  minimum: 0
                  description: Always 1.
                max_body_bytes:
                  type: integer
                  minimum: 0
        segments:
          type: array
          description: The live segment count per partition and view, summed over the bundle's shards, which every viewport pays for. A merge bounds the count, and a compaction resets its shard's to one. The schedule compacts a shard whose count reaches `compaction_max_segments`, so a count past that figure times the number of shards means compactions are being refused.
          items:
            type: object
            additionalProperties: false
            required: [partition, view, count]
            properties:
              partition:
                type: string
              view:
                type: string
              count:
                type: integer
                minimum: 0
        overlay:
          type: object
          additionalProperties: false
          required: [depth, retirable, soft_limit_alarms]
          description: The deletions and suppressions of items in force and not yet folded, summed over the bundle's shards.
          properties:
            depth:
              type: integer
              minimum: 0
              description: Deletions and suppressions together.
            retirable:
              type: integer
              minimum: 0
              description: Deletions alone, which a fold retires and the fold schedule keys on. A suppression never retires.
            soft_limit_alarms:
              type: integer
              minimum: 0
        layer_spaces:
          type: object
          additionalProperties: false
          required: [count, depth, retirable]
          description: The deletions and suppressions of artifacts and layers in force, summed over every layer space.
          properties:
            count:
              type: integer
              minimum: 0
              description: How many layer spaces there are, one per registered layer.
            depth:
              type: integer
              minimum: 0
              description: Deletions and suppressions together, a layer's own suppression included.
            retirable:
              type: integer
              minimum: 0
              description: Deleted artifacts, whose records leave at the compaction of the last shard that lists them. The schedule counts each in the gauge of every shard that lists it, and one no shard lists in the open shard's.
        compaction:
          $ref: "#/components/schemas/CompactionStatus"
        jobs:
          type: array
          description: The record of the flush and of the compaction in flight, each where one is, and empty when neither is. A compaction's counts are over the whole corpus, so the records are served on the control plane only.
          items:
            $ref: "#/components/schemas/Progress"
        fragmentation:
          type: object
          additionalProperties: false
          required: [scope, postings_per_container, run_ratio, postings, runs, containers, rows, tiers, allocation]
          description: How scattered ingested entity ids are across the index, measured over delta tiers as each is flushed; the base bundle is not measured.
          properties:
            scope:
              type: string
              const: delta-tiers
            postings_per_container:
              type: [number, "null"]
              description: Mean entries per container touched. `null` before the first flush publishes.
            run_ratio:
              type: [number, "null"]
              description: Mean run length over the scattered baseline at the same density; `1.0` is fully scattered and larger is better. `null` before the first flush publishes.
            postings:
              type: integer
              minimum: 0
            runs:
              type: integer
              minimum: 0
            containers:
              type: integer
              minimum: 0
            rows:
              type: integer
              minimum: 0
            tiers:
              type: integer
              minimum: 0
              description: Published tiers measured.
            allocation:
              type: object
              additionalProperties: false
              required: [scope, postings, runs, containers, rows, windows]
              description: The same counts taken per commit window, before tiers are formed.
              properties:
                scope:
                  type: string
                  const: commit-window-allocation
                postings:
                  type: integer
                  minimum: 0
                runs:
                  type: integer
                  minimum: 0
                containers:
                  type: integer
                  minimum: 0
                rows:
                  type: integer
                  minimum: 0
                windows:
                  type: integer
                  minimum: 0
        row_projection_cache:
          type: object
          additionalProperties: false
          required: [entries, bytes, bound_bytes, hits, misses, building_refusals, waits_satisfied, waiters_now, evictions, young_evictions, thrashing, oversized_admissions]
          description: The row projections of authorised sets, one per grant, view and shard, each shared by every session with the grant.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            bound_bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            building_refusals:
              type: integer
              minimum: 0
              description: Requests refused with `429` while another built the same projection.
            waits_satisfied:
              type: integer
              minimum: 0
              description: Requests served by waiting for another's build.
            waiters_now:
              type: integer
              minimum: 0
              description: Requests waiting now, across the process.
            evictions: { type: integer, minimum: 0 }
            young_evictions:
              type: integer
              minimum: 0
              description: Entries evicted before they were read again, a sign the bound is smaller than what the grants in use hold at once.
            thrashing:
              type: boolean
              description: "`young_evictions` is above 0."
            oversized_admissions: { type: integer, minimum: 0 }
        projection_builds_by_route:
          type: object
          additionalProperties: false
          required: [whole_domain, walk, split, complement]
          description: How row projections were built, per construction. Every construction gives the same rows; the shares show whether the choice between them fits the corpus.
          properties:
            whole_domain: { type: integer, minimum: 0 }
            walk: { type: integer, minimum: 0 }
            split: { type: integer, minimum: 0 }
            complement: { type: integer, minimum: 0 }
        fragment_cache:
          type: object
          additionalProperties: false
          required: [entries, bytes, bound_bytes, hits, misses, building_refusals, evictions, young_evictions, thrashing, oversized_admissions, rebuilds]
          description: The mask fragments, one per grant and shard, built on a session's first read of the shard and again at each publication that moves it.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            bound_bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            building_refusals: { type: integer, minimum: 0 }
            evictions: { type: integer, minimum: 0 }
            young_evictions: { type: integer, minimum: 0 }
            thrashing: { type: boolean }
            oversized_admissions: { type: integer, minimum: 0 }
            rebuilds:
              type: integer
              minimum: 0
              description: Fragments built again after an eviction, as against for the first time.
        sessions:
          type: object
          additionalProperties: false
          required: [retained, sweeps, swept_total, sweep_at]
          description: Viewer sessions held in memory. Expired ones are swept when `retained` reaches `sweep_at`; an expired session is refused whether or not it has been swept.
          properties:
            retained: { type: integer, minimum: 0 }
            sweeps: { type: integer, minimum: 0 }
            swept_total: { type: integer, minimum: 0 }
            sweep_at: { type: integer, minimum: 0 }
        masked_count_cache:
          type: object
          additionalProperties: false
          required: [entries, bytes, hits, misses, evictions, fills, field_fills, field_mismatches, loads, exact, reserve_spent, not_admitted, labels_bytes, labels_bound_bytes, labels_rows_read, disk_bytes, disk_bound_bytes]
          description: The per-artifact counts of levels served from a row column, and the figures of number and timestamp fields, held per grant over the grant's base rows and corrected per request. A field's figures are joined from those the base stores per set of access labels.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            evictions: { type: integer, minimum: 0 }
            fills:
              type: integer
              minimum: 0
              description: Walks of one shard's base rows under a grant for a level. A growth or a publication between folds does not walk again, and a compaction of one shard walks that shard again and no other.
            field_fills:
              type: integer
              minimum: 0
              description: Walks of a grant's base rows for a field, which a field the base stores no figures of takes, and so does one whose stored figures disagree with the grant's base rows.
            field_mismatches:
              type: integer
              minimum: 0
              description: Field figures joined from the base's stored figures that did not cover the grant's base rows, or a deny that subtracted more than they held, each answered by a walk instead. Above zero, the stored figures and the base disagree.
            loads:
              type: integer
              minimum: 0
              description: A level's counts over every shard, and one shard's walk of a level, read back from the cache directory rather than walked.
            exact:
              type: integer
              minimum: 0
              description: Walks of one request's whole visible set, taken while a level's denied-row labels are read in the background.
            reserve_spent:
              type: integer
              minimum: 0
              description: Boxes worked out from an artifact's rows, or a field's smallest or largest value from the grant's base rows, because a deny reached every spare edge row or value kept on that side.
            not_admitted:
              type: integer
              minimum: 0
              description: Counts larger than the whole bound, served to the request that built them and not kept, and spare edge rows left out so that a level's counts fit the bound. Above zero, the bound is below one grant's level.
            labels_bytes:
              type: integer
              minimum: 0
              description: Bytes of denied rows' labels held to correct the counts.
            labels_bound_bytes:
              type: integer
              minimum: 0
            labels_rows_read:
              type: integer
              minimum: 0
              description: Denied rows whose labels were read from a level's column, at a first read or a deny.
            disk_bytes:
              type: integer
              minimum: 0
              description: Bytes of counts and labels in the engine's cache directory.
            disk_bound_bytes:
              type: integer
              minimum: 0
              description: The bound on disk_bytes, `serve.figures_disk_bytes`.
        region_cache:
          description: The rows each `region` filter leaf selects, shared across sessions per generation.
          allOf:
            - $ref: "#/components/schemas/SharedCacheStatus"
        filter_cache:
          description: The items each filter condition matches (a `match`, a `phrase`, or a range, `eq` or `in` on a column), per shard and set of access attributes, kept until the next flush, coalesce or compaction.
          allOf:
            - $ref: "#/components/schemas/SharedCacheStatus"
        derived_cache:
          type: object
          additionalProperties: false
          required: [entries, bytes, hits, misses, evictions, hit_rate]
          description: Artifact geometry derived per session, such as hulls.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            evictions: { type: integer, minimum: 0 }
            hit_rate:
              type: [number, "null"]
              description: Hits over lookups. `null` before the first lookup.
        suggest_sets:
          type: object
          additionalProperties: false
          required: [entries, bytes, hits, misses, builds, declined, discarded, evictions, in_flight]
          description: The per-session sets of visible category values `/v1/categories/{column}/suggest` answers from.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            builds: { type: integer, minimum: 0 }
            declined:
              type: integer
              minimum: 0
              description: Sessions too broad for a set, over `selection.max_suggest_set_entities`, which are answered by probing instead.
            discarded: { type: integer, minimum: 0 }
            evictions: { type: integer, minimum: 0 }
            in_flight: { type: integer, minimum: 0 }
        occupancy:
          type: object
          additionalProperties: false
          required: [entries, bytes, bound_bytes, hits, misses, evictions, walks]
          description: Which tiles hold visible rows, per session, view, depth and generation.
          properties:
            entries: { type: integer, minimum: 0 }
            bytes: { type: integer, minimum: 0 }
            bound_bytes: { type: integer, minimum: 0 }
            hits: { type: integer, minimum: 0 }
            misses: { type: integer, minimum: 0 }
            evictions: { type: integer, minimum: 0 }
            walks:
              type: integer
              minimum: 0
              description: Occupancy walks. One per session, view and generation is expected; one per request means the cache is not holding.
        heap:
          type: object
          additionalProperties: false
          required: [anon_bytes, file_bytes, resident_bytes, trims, last_trim_returned_bytes, last_trim_micros, trim_baseline_bytes, trim_growth_bytes]
          description: The kernel's view of the process's memory. The gap between `anon_bytes` and the caches above is what the allocator retains.
          properties:
            anon_bytes: { type: integer, minimum: 0 }
            file_bytes: { type: integer, minimum: 0 }
            resident_bytes: { type: integer, minimum: 0 }
            trims:
              type: integer
              minimum: 0
              description: Times the allocator was asked to return memory to the kernel.
            last_trim_returned_bytes: { type: integer, minimum: 0 }
            last_trim_micros: { type: integer, minimum: 0 }
            trim_baseline_bytes: { type: integer, minimum: 0 }
            trim_growth_bytes:
              type: integer
              minimum: 0
              description: The growth over the baseline that asks for the next trim.

    BatchLimits:
      type: object
      additionalProperties: false
      required: [route, max_batch_rows, max_batch_bytes]
      properties:
        route:
          type: string
          description: The route these caps apply to, as `<method> <path>`.
        max_batch_rows:
          type: integer
          minimum: 0
          description: "`ingest.ingest_max_batch_rows`."
        max_batch_bytes:
          type: integer
          minimum: 0
          description: "`ingest.ingest_max_batch_bytes`."

    WriteExecutorStatus:
      type: object
      additionalProperties: false
      description: The write executor, the one thread that applies every write.
      required: [posture, ready, work_submitted, deny_submitted, wal_appends, wal_fsyncs, wal_recoveries, wal, apply_nanos_total, apply_nanos_max, foreign_side_manifests, work_completed, work_depth, work_service_nanos_ewma, flush, coalesces, merges, coalesce_in_flight, merge_in_flight, work_in_flight_nanos, bench_timing, stage_nanos, flush_stages]
      properties:
        posture:
          type: string
          enum: [not-started, running, wal-poisoned, dead]
          description: "`running` is ready. `wal-poisoned` is not ready, and still applies deletions and suppressions."
        ready:
          type: boolean
          description: Whether `posture` is `running`. `/readyz` answers `200` only when this is `true` and no partition serves a stepped-down manifest.
        work_submitted:
          type: integer
          minimum: 0
          description: Writes submitted to the queue that sheds, every write but changes.
        deny_submitted:
          type: integer
          minimum: 0
          description: Changes submitted to the queue that never sheds.
        wal_appends: { type: integer, minimum: 0 }
        wal_fsyncs:
          type: integer
          minimum: 0
          description: "`wal_appends / wal_fsyncs` is the mean writes per commit window."
        wal_recoveries:
          type: integer
          minimum: 0
          description: Write-ahead log faults recovered from. A rise means changes were answered `500` and their callers send them again.
        wal:
          type: object
          additionalProperties: false
          required: [members, bytes, position, pinned_by, pinned_at, pin_span_bytes, samples]
          description: The write-ahead log, sampled at most once per flush period.
          properties:
            members:
              type: integer
              minimum: 0
              description: Files the log is held in. Steady state is two; a rising count is a rotation that is not reclaiming.
            bytes: { type: integer, minimum: 0 }
            position:
              type: integer
              minimum: 0
              description: Record bytes written across every file the log has held. A position, not a size.
            pinned_by:
              type: [string, "null"]
              description: What keeps the log from rotating below `pinned_at`, or `null` when nothing does. A `growth` or `fill` pin clears only at a compaction fold.
            pinned_at:
              type: [integer, "null"]
              minimum: 0
            pin_span_bytes:
              type: integer
              minimum: 0
              description: "`position` less `pinned_at`; `0` when nothing pins the log."
            samples:
              type: integer
              minimum: 0
              description: Samples taken. Two reads with the same value read the same sample.
        apply_nanos_total: { type: integer, minimum: 0 }
        apply_nanos_max: { type: integer, minimum: 0 }
        foreign_side_manifests:
          type: integer
          minimum: 0
          description: Manifests another process wrote over this node's bundle root. Anything above 0 means a second writer.
        work_completed: { type: integer, minimum: 0 }
        work_depth:
          type: integer
          minimum: 0
          description: Writes submitted and not yet answered.
        work_service_nanos_ewma:
          type: integer
          minimum: 0
          description: A moving average of one write's service time. With `work_depth` it gives a `429`'s `Retry-After`.
        flush:
          type: object
          additionalProperties: false
          required: [ticks, flushes, flush_skips, flush_failures, flushable_items, flush_requested, in_flight, buffered_items, overlay_publications, refreshes, refresh_in_flight]
          properties:
            ticks: { type: integer, minimum: 0 }
            flushes: { type: integer, minimum: 0 }
            flush_skips:
              type: integer
              minimum: 0
              description: Ticks skipped because a flush was still running. A rising count means flushes are slower than the tick.
            flush_failures:
              type: integer
              minimum: 0
              description: Flushes that failed and left the buffer for the next tick.
            flushable_items:
              type: integer
              minimum: 0
              description: Rows the next flush would publish; an item in two views counts twice.
            flush_requested:
              type: boolean
              description: A `POST /control/flush` is waiting for the next tick.
            in_flight:
              type: boolean
              description: A flush is running, or finished and not yet published.
            buffered_items:
              type: integer
              minimum: 0
              description: Items buffered, summed over the bundle's shards, the figure `ingest.ingest_buffer_max_items` bounds.
            overlay_publications:
              type: integer
              minimum: 0
              description: Manifests written for deletions and suppressions alone.
            refreshes:
              type: integer
              minimum: 0
              description: Grants' projections refreshed after a flush or merge. A session already served a view is served its grant's old projection until the refresh.
            refresh_in_flight:
              type: boolean
              description: The refresh for the newest flush or merge is still running.
        coalesces: { type: integer, minimum: 0 }
        merges: { type: integer, minimum: 0 }
        coalesce_in_flight: { type: boolean }
        merge_in_flight: { type: boolean }
        work_in_flight_nanos:
          type: integer
          minimum: 0
          description: How long the current write has run; `0` when the executor is idle.
        bench_timing:
          type: boolean
          description: Whether this binary records stage times. Without it every stage time is 0.
        stage_nanos:
          type: object
          description: Time spent in each stage of closing a commit window, by stage name. The names are instrumentation and are not listed here.
          additionalProperties:
            type: integer
            minimum: 0
        flush_stages:
          type: object
          additionalProperties: false
          required: [bench_timing, executions, rows_executed, flushes, rows_published, executor_nanos, pool_nanos]
          description: Flush stage times, on the executor thread and on the pool. Divide pool times by `executions`, and executor times by `flushes`, for a per-flush figure.
          properties:
            bench_timing: { type: boolean }
            executions: { type: integer, minimum: 0 }
            rows_executed: { type: integer, minimum: 0 }
            flushes: { type: integer, minimum: 0 }
            rows_published: { type: integer, minimum: 0 }
            executor_nanos:
              type: object
              description: Nanoseconds by stage name, on the executor thread.
              additionalProperties:
                type: integer
                minimum: 0
            pool_nanos:
              type: object
              description: Nanoseconds by stage name, on the pool.
              additionalProperties:
                type: integer
                minimum: 0

    CompactionStatus:
      type: object
      additionalProperties: false
      description: Compaction folds. Alarm on `fold_failures`, and on `fold_refusals` rising while `folds` does not.
      required: [live_rows, folds, fold_failures, fold_requested, fold_refusals, fold_refusals_by_gate, last_refusal, requests, last_attr_bytes_read, last_attr_bytes_written, passes, last_job]
      properties:
        live_rows:
          type: integer
          minimum: 0
          description: Rows the bundle's segments hold, deleted ones not yet folded included, summed over the bundle's shards.
        folds:
          type: integer
          minimum: 0
          description: Compactions published, one for each shard compacted.
        fold_failures:
          type: integer
          minimum: 0
          description: Compactions discarded. Each leaves a directory nothing reclaims, the size of its shard's files.
        fold_requested:
          type: boolean
          description: True from a `POST /control/compact` until the last compaction it asked for publishes, is discarded or is refused.
        fold_refusals:
          type: integer
          minimum: 0
          description: Folds asked for and not started, the sum of `fold_refusals_by_gate`.
        fold_refusals_by_gate:
          type: object
          additionalProperties: false
          required: [insufficient_disc, insufficient_memory, nothing_to_fold, overlay_diverged, prefix_diverged, stepped_down, wal_poisoned]
          properties:
            insufficient_disc: { type: integer, minimum: 0 }
            insufficient_memory: { type: integer, minimum: 0 }
            nothing_to_fold: { type: integer, minimum: 0 }
            overlay_diverged: { type: integer, minimum: 0 }
            prefix_diverged:
              type: integer
              minimum: 0
              description: Requests refused because a compaction was published on disc and this node could not switch to it.
            stepped_down: { type: integer, minimum: 0 }
            wal_poisoned: { type: integer, minimum: 0 }
        last_refusal:
          description: The latest refusal, or `null` where there has been none.
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required: [gate, need_bytes, had_bytes, at_unix]
              properties:
                gate:
                  type: string
                  description: One of `fold_refusals_by_gate`'s names.
                need_bytes:
                  type: [integer, "null"]
                  minimum: 0
                  description: For a memory or disc refusal, what the fold needs; `null` otherwise.
                had_bytes:
                  type: [integer, "null"]
                  minimum: 0
                  description: For a memory or disc refusal, what the host had; `null` otherwise.
                at_unix:
                  type: integer
                  minimum: 0
                  description: When, in seconds since the Unix epoch.
        requests:
          type: object
          additionalProperties: false
          description: "`POST /control/compact`'s requests since the server started, by the number each `202` carried, numbered as `GET /control/compact/{request}` says. That route says how each was answered."
          required: [accepted, answered]
          properties:
            accepted:
              type: integer
              minimum: 0
              description: The number the latest request was given, or one below this run's first number before the first request.
            answered:
              type: integer
              minimum: 0
              description: The highest request number every shard of which has published, failed, been discarded or been refused, or one below this run's first number before the first answer.
        last_attr_bytes_read: { type: integer, minimum: 0 }
        last_attr_bytes_written: { type: integer, minimum: 0 }
        passes:
          type: array
          description: The last compaction's passes, in order, each named as its step in the compaction's progress record.
          items:
            type: object
            additionalProperties: false
            required: [pass, ms, rss_bytes, anon_bytes, done, total]
            properties:
              pass: { type: string }
              ms: { type: integer, minimum: 0 }
              rss_bytes: { type: integer, minimum: 0 }
              anon_bytes: { type: integer, minimum: 0 }
              done:
                type: [integer, "null"]
                minimum: 0
                description: What the pass counted when it ended, in its step's unit; `null` for a pass that counts nothing.
              total:
                type: [integer, "null"]
                minimum: 0
                description: The total the pass counted towards; `null` for a pass that counts nothing.
        last_job:
          description: The last record of the last compaction to end since the server started, with `state` `finished` or `failed`, and `null` before the first. A failed compaction's record keeps the step it stopped in.
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/Progress"

    Progress:
      type: object
      additionalProperties: false
      description: How far a long-running job has gone. A job is a fixed list of phases, each of which runs one or more steps, and a step that processes a known number of things counts them.
      required: [job, state, phase, step, step_id, elapsed_s, step_elapsed_s, rss_bytes]
      properties:
        job:
          type: string
          enum: [build, ingest, flush, compaction]
        state:
          type: string
          enum: [running, finished, failed]
          description: A failed job's record keeps the step it stopped in.
        phase:
          type: object
          additionalProperties: false
          required: [index, of, name]
          description: The phase's position among the job's phases, counted from 1, and its name.
          properties:
            index: { type: integer, minimum: 1 }
            of: { type: integer, minimum: 1 }
            name: { type: string }
        step:
          type: string
          description: The step's stable name, such as `postings` for a compaction or `rows` for a flush.
        step_id:
          type: integer
          minimum: 1
          description: Which step this is. No other step begun in the same process has it, and it does not change while the step's count or context does, so a reader that sees a different `step_id` from the last record it read knows a step began.
        context:
          type: string
          description: Which part of the step is running, such as a shard, a view or a page. Absent when the step has one part with no name of its own.
        done:
          type: integer
          minimum: 0
          description: How much of the step is done, in `unit`. `done`, `total` and `unit` are absent for a step that counts nothing.
        total:
          type: integer
          minimum: 0
        unit:
          type: string
          enum: [items, rows, pairs, terms, bytes]
        rate:
          type: number
          minimum: 0
          description: Units per second, measured over the last 20 seconds of the step. Absent for a step that counts nothing and for one younger than two seconds.
        eta_s:
          type: number
          minimum: 0
          description: Seconds until `done` reaches `total` at `rate`, for the step only. Absent where `rate` is absent or zero.
        elapsed_s:
          type: number
          minimum: 0
          description: Seconds since the job began.
        step_elapsed_s:
          type: number
          minimum: 0
          description: Seconds since the step began.
        rss_bytes:
          type: integer
          minimum: 0
          description: The resident memory of the process running the job when the record was taken.
