From f41a9bfa47cfbe88c5981c9d01d806c888c79140 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Tue, 15 Sep 2026 12:07:01 -0700 Subject: [PATCH] Pick up service windows from ld main, and render every authored response example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-vendors specs/hosted-v1.yaml from ld main (3c82f7d8b2d). dolthub-v2.yaml is unchanged there, so v2 is untouched. GET /deployments/{owner}/{deployment}/service-windows listDeploymentServiceWindows plus ServiceWindow and DayOfWeek. Deployment goes from 16 endpoints to 17 and models.md from 37 schemas to 39. Deployment-tagged, so no new page or nav entry. In the overview it takes a table row, and joins the endpoints that return their list whole rather than paging it. The renderer needed one fix to document it honestly. This endpoint authors two response examples — a configured window, and the nil-UUID default a deployment reports until one is set — and only one was rendered. Worse, the one chosen was the default: the picker prefers the example keyed `default`, which here names the default *window* rather than the representative case. The page's only example was therefore the degenerate one, nil UUID and all. Responses with more than one authored example now render all of them under a single heading, each captioned with its summary, in spec order. `default` is only a key in this position and does not mean "the representative one", so spec order is what decides, which puts the configured window first. A response with one example, or an unnamed one, renders exactly as before. This is the response half of the gap noted in #208, which did the request half. It reaches two endpoints: service windows, and log retrieval, where the second example is an empty page — the case that endpoint's own stopping rule turns on. v2 authors one example per response and is unaffected. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/lib/openapi-docs.mjs | 31 +++- .../content/products/hosted/api/v1/README.md | 3 +- .../products/hosted/api/v1/deployment.md | 86 ++++++++- .../content/products/hosted/api/v1/models.md | 30 ++++ specs/hosted-v1.yaml | 168 ++++++++++++++++++ 5 files changed, 314 insertions(+), 4 deletions(-) diff --git a/scripts/lib/openapi-docs.mjs b/scripts/lib/openapi-docs.mjs index 7ac3046..a8bc19a 100644 --- a/scripts/lib/openapi-docs.mjs +++ b/scripts/lib/openapi-docs.mjs @@ -221,8 +221,22 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { const resp = rawResp.$ref ? resolveRef(rawResp.$ref) : rawResp; const media = jsonBody(resp); - // An example authored on the response wins outright — it shows the whole - // envelope, including any `meta`, exactly as the API returns it. + // Examples authored on the response win outright — they show the whole + // envelope, including any `meta`, exactly as the API returns it. Where a + // spec authors several, each is one the others cannot stand in for: a + // configured resource beside its unset default, a full page beside an + // empty one. They render in spec order, since `default` is only a key + // here and does not mean "the representative one". + const named = media?.examples; + if (named && typeof named === "object") { + const authoredAll = Object.entries(named).filter( + ([, entry]) => entry && "value" in entry + ); + if (authoredAll.length > 1) { + return exampleBlocks(successCode, authoredAll); + } + } + const authored = mediaTypeExample(media); if (authored !== undefined) { return exampleBlock(successCode, authored); @@ -266,6 +280,19 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { return `\n**Example response \`${code}\`**\n\n\`\`\`json\n${JSON.stringify(body, null, 2)}\n\`\`\`\n`; } + // Several authored examples under one heading, each captioned with its + // summary so the reader can see which case it is. + function exampleBlocks(code, entries) { + const blocks = entries + .map(([key, entry]) => { + const label = escapeMarkdown(entry.summary ?? key); + const body = JSON.stringify(entry.value, null, 2); + return `\n_${label}_\n\n\`\`\`json\n${body}\n\`\`\`\n`; + }) + .join(""); + return `\n**Example responses \`${code}\`**\n${blocks}`; + } + // ------------------------------------------------------------------------- // Types and constraints // ------------------------------------------------------------------------- diff --git a/site/dolt/src/content/products/hosted/api/v1/README.md b/site/dolt/src/content/products/hosted/api/v1/README.md index 12fbaba..e6de340 100644 --- a/site/dolt/src/content/products/hosted/api/v1/README.md +++ b/site/dolt/src/content/products/hosted/api/v1/README.md @@ -53,6 +53,7 @@ See [Authentication](/products/hosted/api/v1/authentication) for how to create a | **PATCH** | `/api/v1/deployments/{owner}/{deployment}/config` | [Change some of a deployment's configuration overrides](/products/hosted/api/v1/deployment#patchDeploymentConfig) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/logs` | [Read a deployment's logs](/products/hosted/api/v1/deployment#getDeploymentLogs) | | **PATCH** | `/api/v1/deployments/{owner}/{deployment}/expose` | [Expose or stop exposing the remotesapi or MCP endpoint](/products/hosted/api/v1/deployment#exposeDeploymentService) | +| **GET** | `/api/v1/deployments/{owner}/{deployment}/service-windows` | [List a deployment's service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics` | [List a deployment's metrics](/products/hosted/api/v1/deployment#listDeploymentMetrics) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics/{metric}` | [Read one of a deployment's metrics](/products/hosted/api/v1/deployment#getDeploymentMetric) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/backups` | [List a deployment's backups](/products/hosted/api/v1/deployment#listDeploymentBackups) | @@ -88,7 +89,7 @@ List endpoints put the pagination cursor in `meta`: When `meta.next_page_token` is present, pass it back as the `page_token` query parameter to fetch the next page. On the last page `meta` is omitted entirely, so checking whether the token is present is all a client needs — it is never returned present but empty. Page size is fixed and not caller-controlled, so a full page is not itself a sign that another one follows. -Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing. +Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics) and [service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing. Each endpoint's parameters say which of the three it is. diff --git a/site/dolt/src/content/products/hosted/api/v1/deployment.md b/site/dolt/src/content/products/hosted/api/v1/deployment.md index af847b1..7bb98de 100644 --- a/site/dolt/src/content/products/hosted/api/v1/deployment.md +++ b/site/dolt/src/content/products/hosted/api/v1/deployment.md @@ -753,7 +753,9 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l | `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | | `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) | -**Example response `200`** +**Example responses `200`** + +_Two lines with more history available._ ```json { @@ -773,6 +775,14 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l } ``` +_A window with nothing in it._ + +```json +{ + "data": [] +} +``` + --- ## Expose or stop exposing a deployment's remotesapi or MCP endpoint {#exposeDeploymentService} @@ -850,6 +860,80 @@ _Stop serving the remotesapi endpoint._ --- +## List a deployment's service windows {#listDeploymentServiceWindows} +GET /api/v1/deployments/{owner}/{deployment}/service-windows + +Returns the weekly windows in which Hosted may restart the deployment's instances to apply maintenance. + +A window covers whole hours in UTC on one day of the week. `start_hour_utc` is inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning 07:00 UTC. + +Every deployment has at least one. Until one is set, the list holds a single window with `is_default` set: Sunday 07:00 to 08:00 UTC. + + +**Parameters** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `owner` | path | string | yes | The user or organization that owns the deployment. 3–32 characters of letters, digits, hyphens, and underscores. | +| `deployment` | path | string | yes | The deployment name, unique within the owner. 3–32 characters of letters, digits, hyphens, and underscores. | + +**Example request** + +```sh +curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/service-windows' \ + -H 'Authorization: Bearer YOUR_TOKEN' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `200` | The deployment's service windows. | [`ServiceWindow[]`](/products/hosted/api/v1/models#model-servicewindow) | +| `400` | The request was malformed or failed input validation. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `401` | Authentication credentials were missing or invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `404` | The requested resource does not exist. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `405` | The HTTP method is not supported for this resource. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `422` | The request was well-formed but semantically invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) | + +**Example responses `200`** + +_A window set for early Tuesday morning UTC._ + +```json +{ + "data": [ + { + "id": "7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d", + "day_of_week": "tuesday", + "start_hour_utc": 3, + "end_hour_utc": 5, + "is_default": false + } + ] +} +``` + +_A deployment with no window configured._ + +```json +{ + "data": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "day_of_week": "sunday", + "start_hour_utc": 7, + "end_hour_utc": 8, + "is_default": true + } + ] +} +``` + +--- + ## Disable a deployment {#disableDeployment} POST /api/v1/deployments/{owner}/{deployment}/disable diff --git a/site/dolt/src/content/products/hosted/api/v1/models.md b/site/dolt/src/content/products/hosted/api/v1/models.md index 8b547e2..186ecb5 100644 --- a/site/dolt/src/content/products/hosted/api/v1/models.md +++ b/site/dolt/src/content/products/hosted/api/v1/models.md @@ -251,6 +251,36 @@ One metric a deployment collects. Read it with `GET /api/v1/deployments/{owner}/ --- +## DayOfWeek {#model-dayofweek} +The day of the week a service window falls on, in UTC. + +**Enum values** + +| Value | +|-------| +| `sunday` | +| `monday` | +| `tuesday` | +| `wednesday` | +| `thursday` | +| `friday` | +| `saturday` | + +--- + +## ServiceWindow {#model-servicewindow} +One weekly window in which Hosted may restart the deployment's instances for maintenance. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | `string` | yes | The window's identifier, unique within the deployment. A default window has no identifier of its own and reports the nil UUID, `00000000-0000-0000-0000-000000000000`. | +| `day_of_week` | [`DayOfWeek`](/products/hosted/api/v1/models#model-dayofweek) | yes | The day of the week a service window falls on, in UTC. | +| `start_hour_utc` | `integer` | yes | The first hour of the window, in UTC. Inclusive. | +| `end_hour_utc` | `integer` | yes | The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers 03:00 until 05:00. | +| `is_default` | `boolean` | yes | Whether this is the default window Hosted falls back to rather than one that was configured. `true` means no maintenance window has been set for this deployment. | + +--- + ## DeploymentInstance {#model-deploymentinstance} One instance backing a deployment. A deployment has a primary and, when it has read replicas, one instance per replica. diff --git a/specs/hosted-v1.yaml b/specs/hosted-v1.yaml index f2dc57c..0a96a30 100644 --- a/specs/hosted-v1.yaml +++ b/specs/hosted-v1.yaml @@ -1328,6 +1328,103 @@ paths: "503": $ref: "#/components/responses/ServiceUnavailable" + /api/v1/deployments/{owner}/{deployment}/service-windows: + get: + operationId: listDeploymentServiceWindows + summary: List a deployment's service windows. + description: >- + Returns the weekly windows in which Hosted may restart the deployment's instances to + apply maintenance. + + + A window covers whole hours in UTC on one day of the week. `start_hour_utc` is + inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning + 07:00 UTC. + + + Every deployment has at least one. Until one is set, the list holds a single window + with `is_default` set: Sunday 07:00 to 08:00 UTC. + tags: + - Deployment + security: + - apiToken: [] + parameters: + - name: owner + in: path + required: true + description: >- + The user or organization that owns the deployment. 3–32 characters of letters, + digits, hyphens, and underscores. + schema: + type: string + pattern: "^[-a-zA-Z0-9_]{3,32}$" + example: acme + - name: deployment + in: path + required: true + description: >- + The deployment name, unique within the owner. 3–32 characters of letters, + digits, hyphens, and underscores. + schema: + type: string + pattern: "^[-a-zA-Z0-9_]{3,32}$" + example: analytics + responses: + "200": + description: The deployment's service windows. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + type: array + description: The deployment's service windows. + items: + $ref: "#/components/schemas/ServiceWindow" + examples: + configured: + summary: A window set for early Tuesday morning UTC. + value: + data: + - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: tuesday + start_hour_utc: 3 + end_hour_utc: 5 + is_default: false + default: + summary: A deployment with no window configured. + value: + data: + - id: 00000000-0000-0000-0000-000000000000 + day_of_week: sunday + start_hour_utc: 7 + end_hour_utc: 8 + is_default: true + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "404": + $ref: "#/components/responses/NotFound" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "422": + $ref: "#/components/responses/UnprocessableEntity" + "500": + $ref: "#/components/responses/InternalServerError" + "503": + $ref: "#/components/responses/ServiceUnavailable" + /api/v1/deployments/{owner}/{deployment}/disable: post: operationId: disableDeployment @@ -2882,6 +2979,77 @@ components: - id: cpu display_name: CPU Utilization + DayOfWeek: + type: string + title: DayOfWeek + description: The day of the week a service window falls on, in UTC. + enum: + - sunday + - monday + - tuesday + - wednesday + - thursday + - friday + - saturday + examples: + - tuesday + + ServiceWindow: + type: object + title: ServiceWindow + description: >- + One weekly window in which Hosted may restart the deployment's instances for + maintenance. + required: + - id + - day_of_week + - start_hour_utc + - end_hour_utc + - is_default + properties: + id: + type: string + format: uuid + description: >- + The window's identifier, unique within the deployment. A default window has no + identifier of its own and reports the nil UUID, + `00000000-0000-0000-0000-000000000000`. + examples: + - 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: + $ref: "#/components/schemas/DayOfWeek" + start_hour_utc: + type: integer + format: int32 + minimum: 0 + maximum: 23 + description: The first hour of the window, in UTC. Inclusive. + examples: + - 3 + end_hour_utc: + type: integer + format: int32 + minimum: 0 + maximum: 23 + description: >- + The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers + 03:00 until 05:00. + examples: + - 5 + is_default: + type: boolean + description: >- + Whether this is the default window Hosted falls back to rather than one that was + configured. `true` means no maintenance window has been set for this deployment. + examples: + - false + examples: + - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d + day_of_week: tuesday + start_hour_utc: 3 + end_hour_utc: 5 + is_default: false + DeploymentInstance: type: object title: DeploymentInstance