From 30c2010c09a93dba3705f4d693b8401a0b94d41f Mon Sep 17 00:00:00 2001 From: Brad Ison Date: Tue, 11 Aug 2026 11:36:18 +0200 Subject: [PATCH] [management] Document the Agent Network settings ETag in the API spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declares what the handlers already do: the settings reads and writes return an ETag, and PUT and DELETE honour an If-Match against it. The 412 that PUT can now return is documented alongside, and DELETE's existing 412 grows a note that a precondition failure shares the status with its two state guards and is told apart by the message rather than the code. Response headers generate nothing under a models-only codegen config, so those entries are for human and third-party consumers. The If-Match parameters do generate — hence the two Params structs in types.gen.go, which nothing calls yet but which the REST client can take up. --- shared/management/http/api/openapi.yml | 55 +++++++++++++++++++++++-- shared/management/http/api/types.gen.go | 22 ++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index 7728961a2..e88867faf 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -6438,6 +6438,15 @@ components: schema: type: string example: cot7r4n3l3vh3qj4qveg + ETag: + description: | + Strong entity-tag identifying the returned representation. Send it back + in `If-Match` on a subsequent write to make that write conditional, so + a change made between the read and the write is refused with `412` + rather than silently overwritten. + schema: + type: string + example: '"9f86d081884c7d65"' securitySchemes: BearerAuth: type: http @@ -13733,6 +13742,9 @@ paths: responses: '200': description: Agent Network settings for the account + headers: + ETag: + $ref: '#/components/headers/ETag' content: application/json: schema: @@ -13760,6 +13772,9 @@ paths: responses: '200': description: The freshly bootstrapped Agent Network settings + headers: + ETag: + $ref: '#/components/headers/ETag' content: application/json: schema: @@ -13778,11 +13793,25 @@ paths: "$ref": "#/components/responses/internal_error" put: summary: Update Agent Network settings - description: Updates the account-level Agent Network settings; the request carries every field, replacing the mutable ones (collection toggles and retention). Returns 404 when the account has no settings row yet — bootstrap it with POST first. The endpoint and proxy address are assigned at bootstrap and immutable; the request must carry them unchanged, and a request carrying different values is rejected. + description: Updates the account-level Agent Network settings; the request carries every field, replacing the mutable ones (collection toggles and retention). Returns 404 when the account has no settings row yet — bootstrap it with POST first. The endpoint and proxy address are assigned at bootstrap and immutable; the request must carry them unchanged, and a request carrying different values is rejected. Supply `If-Match` to make the update conditional; without it the update is unconditional and the last write wins. tags: [ Agent Network ] security: - BearerAuth: [ ] - TokenAuth: [ ] + parameters: + - name: If-Match + in: header + required: false + description: | + Makes the update conditional on the settings not having changed since + they were read. Send the `ETag` from an earlier `GET`, `POST` or `PUT`, + or `*` to require only that a settings row exists. The precondition is + evaluated against the stored row inside the update's own transaction, + so two clients starting from the same `ETag` cannot both succeed. + Omitting the header leaves the update unconditional. + schema: + type: string + example: '"9f86d081884c7d65"' requestBody: description: Settings update request content: @@ -13792,6 +13821,9 @@ paths: responses: '200': description: Updated Agent Network settings + headers: + ETag: + $ref: '#/components/headers/ETag' content: application/json: schema: @@ -13804,17 +13836,34 @@ paths: "$ref": "#/components/responses/forbidden" '404': "$ref": "#/components/responses/not_found" + '412': + description: The `If-Match` precondition failed — the settings changed since they were read. The stored settings are unmodified; read them again and retry. + content: { } '422': "$ref": "#/components/responses/validation_failed" '500': "$ref": "#/components/responses/internal_error" delete: summary: Delete Agent Network settings - description: Deletes the account's Agent Network settings row, releasing the endpoint. Guarded — the delete is refused with 412 while any Agent Network provider exists for the account or while a proxy is actively serving the endpoint. Bootstrapping again after a delete allocates a new endpoint; the released hostname is not reserved. + description: Deletes the account's Agent Network settings row, releasing the endpoint. Guarded — the delete is refused with 412 while any Agent Network provider exists for the account or while a proxy is actively serving the endpoint. Bootstrapping again after a delete allocates a new endpoint; the released hostname is not reserved. Supply `If-Match` to make the delete conditional, which is worth doing here even more than on update — the other two guards are about state rather than staleness, so nothing else stops a client from deleting a row that was replaced since it read one. tags: [ Agent Network ] security: - BearerAuth: [ ] - TokenAuth: [ ] + parameters: + - name: If-Match + in: header + required: false + description: | + Makes the delete conditional on the settings not having changed since + they were read. Send the `ETag` from an earlier `GET`, `POST` or `PUT`, + or `*` to require only that a settings row exists. The precondition is + evaluated inside the delete's own transaction, ahead of the provider + and serving-proxy guards. Omitting the header leaves the delete + unconditional. + schema: + type: string + example: '"9f86d081884c7d65"' responses: '200': description: Settings deleted @@ -13825,7 +13874,7 @@ paths: '404': "$ref": "#/components/responses/not_found" '412': - description: Delete refused — Agent Network providers still exist for the account, or a proxy is actively serving the endpoint + description: Delete refused — the `If-Match` precondition failed, or Agent Network providers still exist for the account, or a proxy is actively serving the endpoint. The stored settings are unmodified in every case; the response message distinguishes them. content: { } '500': "$ref": "#/components/responses/internal_error" diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index e5d32bfc4..1a3a59e14 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -5939,6 +5939,28 @@ type GetApiAgentNetworkAccessLogsParamsSortBy string // GetApiAgentNetworkAccessLogsParamsSortOrder defines parameters for GetApiAgentNetworkAccessLogs. type GetApiAgentNetworkAccessLogsParamsSortOrder string +// DeleteApiAgentNetworkSettingsParams defines parameters for DeleteApiAgentNetworkSettings. +type DeleteApiAgentNetworkSettingsParams struct { + // IfMatch Makes the delete conditional on the settings not having changed since + // they were read. Send the `ETag` from an earlier `GET`, `POST` or `PUT`, + // or `*` to require only that a settings row exists. The precondition is + // evaluated inside the delete's own transaction, ahead of the provider + // and serving-proxy guards. Omitting the header leaves the delete + // unconditional. + IfMatch *string `json:"If-Match,omitempty"` +} + +// PutApiAgentNetworkSettingsParams defines parameters for PutApiAgentNetworkSettings. +type PutApiAgentNetworkSettingsParams struct { + // IfMatch Makes the update conditional on the settings not having changed since + // they were read. Send the `ETag` from an earlier `GET`, `POST` or `PUT`, + // or `*` to require only that a settings row exists. The precondition is + // evaluated against the stored row inside the update's own transaction, + // so two clients starting from the same `ETag` cannot both succeed. + // Omitting the header leaves the update unconditional. + IfMatch *string `json:"If-Match,omitempty"` +} + // GetApiAgentNetworkUsageOverviewParams defines parameters for GetApiAgentNetworkUsageOverview. type GetApiAgentNetworkUsageOverviewParams struct { // Granularity Time bucket width. Defaults to day.