{
  "openapi": "3.0.3",
  "info": {
    "title": "Cloudonym API",
    "description": "Multi-cloud resource naming engine. This release enables Google Cloud (gcp) only; AWS and Azure are in the rule table but not enabled, and a request for them is 400 request.provider_not_enabled with the enabled providers in supported. Generates, corrects and validates resource names, one by one or for a whole project with the naming wizard (/wizard), against each provider's physical constraints. Names from /wizard may carry a tier token (web, was) after the abbreviation and kind token, and attached resources (boot and data disks, reserved addresses) are named after their server; /generate and /names never put a tier in a name. Formulas, region codes, the zones they accept, kind tokens and every rule are in /llms.txt; the resource catalog and region table are in /catalog.json (served from the site root, not under /v1). Cloudonym does not replace the provider's final verdict: it checks only the reserved words and patterns that the official documents state. Successful responses of /generate, /names, /wizard, /validate and /health carry version { engine, rules }; failures and /events do not. Every failure has the ErrorResponse shape; an unknown /v1 path or method returns 404 with code request.not_found. PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
    "version": "0.1.3",
    "license": {
      "name": "Apache-2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "/v1",
      "description": "Same host as this document (Cloudflare Worker)"
    }
  ],
  "paths": {
    "/catalog.json": {
      "servers": [
        {
          "url": "/",
          "description": "Site root — this path is not under /v1"
        }
      ],
      "get": {
        "summary": "Resource catalog, region table and wizard client data (presets, rule inputs, limits)",
        "operationId": "catalog",
        "description": "The resource catalog and region table of the enabled providers, with version { engine, rules }, and what a client needs to build a wizard plan (input kind, tier slot and locations per rule, presets, attachments, limits) — the repository's command-line tool reads it instead of keeping its own copy of the rules. Cache-Control: public, max-age=86400 and Access-Control-Allow-Origin: *. GET counts toward the per-IP rate limit; HEAD does not.",
        "responses": {
          "200": {
            "description": "Catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/generate": {
      "post": {
        "summary": "Generate cloud resource names",
        "description": "Generates names and standard labels for every resource type of the requested providers from five context values. Names that cannot fit even after shortening are returned with valid=false and the reasons; nothing is silently truncated. Every violation, step and note is a Message: branch on code, show message to people. This is the catalogue: rules that need a kind (resource policies) come back valid=false here (name.kind_required); use /names with kind for them.",
        "operationId": "generateNames",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateRequest"
              },
              "example": {
                "context": {
                  "owner": "platform",
                  "system": "billing",
                  "purpose": "trade",
                  "env": "prod",
                  "region": "asia-northeast3"
                },
                "providers": [
                  "gcp"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated names and labels",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenerateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not JSON or not an object, context is invalid (including an owner, system or purpose longer than 64 characters, or an env or region longer than 1024), providers is not a non-empty array of supported providers or names a provider that is not enabled yet (request.provider_not_enabled), or rules_version is not a string. At most the first 50 errors are listed; when there are more, the last entry is request.errors_truncated with params.shown (50) and params.total (the number of errors found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "rules_version was sent and differs from the current rules revision (request.rules_version_mismatch; params.requested and params.current). The API never serves an old rules revision: re-run without rules_version to use the current rules, or keep using the names you already created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body is larger than 32 KB (request.body_too_large)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/names": {
      "post": {
        "summary": "Generate names for the resources you pick",
        "description": "Generates names for the selected resource types of one provider, count names each (sequence 01..99) or one per role, with an optional kind (a value name such as POSTGRES) whose short token goes right after the abbreviation. One request stands for one instance of each parent scope (one resource group, one project, one VPC): two items of the same kind of resource with the same name are each marked valid=false with name.conflict, and conflicts lists each collision once. Items that fail or collide are still returned with the reasons (status 200). Every violation, step and note is a Message: branch on code, show message to people.",
        "operationId": "generateSelectedNames",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NamesRequest"
              },
              "example": {
                "provider": "gcp",
                "context": {
                  "owner": "platform",
                  "system": "billing",
                  "purpose": "trade",
                  "env": "prod",
                  "region": "asia-northeast3-a"
                },
                "resources": [
                  {
                    "type": "compute_instance",
                    "count": 2
                  },
                  {
                    "type": "subnet",
                    "count": 2
                  },
                  {
                    "type": "service_account",
                    "role": "db"
                  },
                  {
                    "type": "project_id"
                  },
                  {
                    "type": "cloud_sql_instance",
                    "count": 2,
                    "kind": "POSTGRES"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Names for the selected resources, in request order then sequence order. data.<provider>.valid is false when any item is invalid or collides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NamesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not JSON or not an object, provider is unknown or not enabled yet (request.provider_not_enabled; supported lists the enabled providers), context is invalid (including an owner, system or purpose longer than 64 characters, or an env or region longer than 1024), rules_version is not a string, or resources is invalid: not an array, empty, more than 200 items, an item that is not an object, an unknown type (supported lists the resource types), a duplicate type, count not an integer from 1 to 99, count above 1 or a role on a rule that takes none, a role not matching ^[a-z0-9]{2,16}$, a kind on a rule that takes none (selection.kind_not_supported), a kind that is not one of the rule's values (selection.unknown_kind; params.supported lists them), no kind on a rule that needs one (selection.kind_required; params.supported), or more than 200 names in total (selection.* codes)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "rules_version was sent and differs from the current rules revision (request.rules_version_mismatch; params.requested and params.current). The API never serves an old rules revision: re-run without rules_version to use the current rules, or keep using the names you already created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body is larger than 32 KB (request.body_too_large)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/validate": {
      "post": {
        "summary": "Validate an existing resource name",
        "description": "Checks a name against the provider's physical constraints (length, pattern, documented reserved words). When the name is invalid and a fix is possible, suggested_name is included. PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
        "operationId": "validateName",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateRequest"
              },
              "example": {
                "provider": "gcp",
                "resource_type": "service_account",
                "name": "platform-billing-trade-prod-ane3-sa-sync"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown provider or resource_type, or a provider that is not enabled yet (request.provider_not_enabled; supported lists the accepted values), name or rules_version is not a string, or a field is longer than 1024 characters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "rules_version was sent and differs from the current rules revision (request.rules_version_mismatch; params.requested and params.current). The API never serves an old rules revision: re-run without rules_version to use the current rules, or keep using the names you already created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body is larger than 32 KB (request.body_too_large)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "post": {
        "summary": "Web UI usage beacon",
        "description": "Counts a UI event. No context values are accepted or stored. Only for the Cloudonym web page: requests without Sec-Fetch-Site: same-origin are rejected with 403, and there is no CORS.",
        "operationId": "recordEvent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventRequest"
              },
              "example": {
                "event": "ui_visit"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown event, or event is longer than 1024 characters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Not sent by the Cloudonym web page (request.same_origin_only): Sec-Fetch-Site is missing or not same-origin. This endpoint accepts only the page's own beacons and sends no CORS headers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body is larger than 32 KB (request.body_too_large)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Health check and version",
        "description": "Always 200 while the Worker is up. Not rate limited.",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/wizard": {
      "post": {
        "summary": "Name a whole project: project-level resources and one or more services",
        "description": "The naming wizard. One request is one GCP project: the project ID (always the first item), project-level resources (project_picks: network, firewall rules, Cloud Router, Cloud NAT) and, per service (purpose), the resources picked from presets or by rule id. Servers keep their sequence number (instances[].seq), so removing server 01 does not rename server 02. A server can carry attached resources (boot disk, data disks, a static external or internal address) whose names are the server name plus a suffix (-boot, -data-01, -ip, -ipi); they are never shortened. Items are returned in order: project level, then each service, each parent followed by its attached resources. Item keys are structured paths (service/shop/compute_instance/-/web/01/data/01). Items that fail or collide are still returned with the reasons (status 200). The response is reproduced by repeating the same wizard request with its rules_version; /names is a separate, flat endpoint.",
        "operationId": "nameProject",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WizardRequest"
              },
              "example": {
                "provider": "gcp",
                "project": {
                  "owner": "platform",
                  "system": "billing",
                  "env": "prod",
                  "region": "asia-northeast3-a"
                },
                "project_picks": [
                  {
                    "preset": "vpc",
                    "instances": [
                      {
                        "id": "a",
                        "seq": "01"
                      }
                    ]
                  },
                  {
                    "preset": "subnet",
                    "instances": [
                      {
                        "id": "b",
                        "seq": "01"
                      }
                    ]
                  },
                  {
                    "preset": "firewall",
                    "tier": "ssh",
                    "instances": [
                      {
                        "id": "c",
                        "seq": "01"
                      }
                    ]
                  },
                  {
                    "preset": "firewall",
                    "tier": "web",
                    "instances": [
                      {
                        "id": "d",
                        "seq": "01"
                      }
                    ]
                  }
                ],
                "services": [
                  {
                    "purpose": "shop",
                    "picks": [
                      {
                        "preset": "web",
                        "instances": [
                          {
                            "id": "e",
                            "seq": "01",
                            "attach": {
                              "boot": true,
                              "data": [
                                "01"
                              ],
                              "ip": true
                            }
                          }
                        ]
                      },
                      {
                        "preset": "db",
                        "kind": "POSTGRES",
                        "instances": [
                          {
                            "id": "f",
                            "seq": "01"
                          }
                        ]
                      }
                    ]
                  },
                  {
                    "purpose": "pay",
                    "picks": [
                      {
                        "preset": "web",
                        "ha": true,
                        "instances": [
                          {
                            "id": "g",
                            "seq": "01",
                            "attach": {
                              "boot": true
                            }
                          },
                          {
                            "id": "h",
                            "seq": "02",
                            "attach": {
                              "boot": true
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Names for the whole project, in structured-key order. data.<provider>.valid is false when any item is invalid or collides",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WizardResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not JSON or not an object, provider is unknown or not enabled yet, project is not an object, a context value or purpose is invalid or too long (field project.owner, services[1].purpose ...), or the wizard plan is invalid (wizard.* and selection.* codes). field is the request path of the offending element, in request names: services[1].picks[0].instances[1].attach.data[0], project_picks[2], services[1]. Errors about the whole plan (wizard.empty, wizard.too_many_services, wizard.too_many_names, selection.not_array for services or project_picks) have no field. At most the first 50 errors are listed; when there are more, the last entry is request.errors_truncated with params.shown (50) and params.total (the number of errors found)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "success": false,
                  "errors": [
                    {
                      "code": "wizard.invalid_data_seq",
                      "field": "services[0].picks[0].instances[0].attach.data[1]",
                      "params": {
                        "index": 0,
                        "value": "00",
                        "pattern": "^(0[1-9]|[1-9][0-9])$"
                      },
                      "message": "services[0].picks[0].instances[0].attach.data[1]: disk number '00' must match ^(0[1-9]|[1-9][0-9])$"
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "rules_version was sent and differs from the current rules revision (request.rules_version_mismatch; params.requested and params.current). The API never serves an old rules revision: re-run without rules_version to use the current rules, or keep using the names you already created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body is larger than 32 KB (request.body_too_large)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this client IP (60 per 60 seconds, shared by POST /v1/*, GET /llms.txt and GET /catalog.json). Wait the number of seconds in Retry-After. errors[0].code is request.rate_limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error (server.internal_error). The stack trace is logged, never returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "GenerateRequest": {
        "type": "object",
        "required": [
          "context"
        ],
        "properties": {
          "context": {
            "type": "object",
            "required": [
              "owner",
              "system",
              "purpose",
              "env",
              "region"
            ],
            "properties": {
              "owner": {
                "type": "string",
                "maxLength": 64,
                "example": "platform"
              },
              "system": {
                "type": "string",
                "maxLength": 64,
                "example": "billing"
              },
              "purpose": {
                "type": "string",
                "maxLength": 64,
                "example": "trade"
              },
              "env": {
                "type": "string",
                "maxLength": 1024,
                "description": "Canonical: prod, stg, dev, sbx, qa, uat, test, perf, dr. Common aliases are accepted and normalised (prd, production, live -> prod; staging -> stg; development -> dev; sandbox -> sbx; quality -> qa; acceptance -> uat; testing, tst -> test; performance, load -> perf; disaster, recovery -> dr).",
                "example": "prod"
              },
              "region": {
                "type": "string",
                "maxLength": 1024,
                "description": "A region or a zone of the provider, by name or by code (asia-northeast3, asia-northeast3-a, ane3, ane3a), or global. Names carry the region code (ane3); a zone name is accepted and resolved to its region, so zonal resources are named like regional ones. Country values and region names of another provider are 400 context.value_unsupported with candidates in supported. The region table is in /llms.txt. Multi-region and dual-region locations (US, EU, ASIA, ASIA1, EUR4, EUR5, EUR7, EUR8, NAM4; by name or code, any case) are taken only by the resource types that support them: storage_bucket takes all nine, bigquery_dataset takes US and EU; names carry the lower-case code (us). A location is accepted only when every requested type that carries a region token takes it, otherwise 400 context.location_unsupported — always on /v1/generate, which names every type.",
                "example": "asia-northeast3-a"
              }
            }
          },
          "providers": {
            "type": "array",
            "description": "Omit for every enabled provider (only gcp for now; aws and azure are planned). Must be non-empty when present. A known provider that is not enabled yet is 400 request.provider_not_enabled.",
            "items": {
              "type": "string",
              "enum": [
                "gcp"
              ]
            },
            "example": [
              "gcp"
            ]
          },
          "rules_version": {
            "type": "string",
            "maxLength": 1024,
            "description": "Optional pin to a rules revision (YYYY-MM-DD.N). If it differs from the current revision the API answers 409 instead of returning names that may differ. Not a string -> 400.",
            "example": "2026-09-29.2"
          }
        }
      },
      "GenerateResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "success",
          "version",
          "data",
          "metadata"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "data": {
            "type": "object",
            "description": "provider -> resource_type -> ResourceItem. The provider set equals the requested providers.",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/components/schemas/ResourceItem"
              }
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/GenerateMetadata"
          }
        }
      },
      "GenerateMetadata": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "generated_labels",
          "label_violations",
          "label_transformed",
          "transformed"
        ],
        "properties": {
          "generated_labels": {
            "type": "object",
            "description": "provider -> standard tags/labels (owner, system, purpose, environment, managed_by)",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          },
          "label_violations": {
            "type": "object",
            "description": "provider -> label values over the provider's limit or with characters the provider rejects (values are never truncated)",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Message"
              }
            }
          },
          "label_transformed": {
            "type": "object",
            "description": "provider -> label values changed to fit the provider's character rules (GCP)",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Message"
              }
            }
          },
          "transformed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Context values normalised from aliases (context.normalized, e.g. env 'production' -> 'prod')"
          }
        }
      },
      "ResourceItem": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "valid",
          "is_truncated",
          "is_transformed",
          "original",
          "violations",
          "steps",
          "rule"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Final name. Empty when a required token became empty after normalisation.",
            "example": "billing-trade-prod-sa-sync"
          },
          "valid": {
            "type": "boolean",
            "description": "PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
            "example": true
          },
          "is_truncated": {
            "type": "boolean",
            "description": "Optional tokens were dropped or tokens were shortened to fit",
            "example": true
          },
          "is_transformed": {
            "type": "boolean",
            "description": "Characters were changed by normalisation or separators were removed",
            "example": false
          },
          "original": {
            "type": "string",
            "description": "Name assembled before normalisation and shortening",
            "example": "billing-trade-prod-sa-sync"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Why the name is invalid (empty when valid)"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Shortening and transformation steps in order (step.token_dropped, step.abbreviated, step.budget_shortened, step.normalized, step.separator_removed)"
          },
          "rule": {
            "$ref": "#/components/schemas/RuleInfo"
          }
        }
      },
      "NamesRequest": {
        "type": "object",
        "required": [
          "provider",
          "context",
          "resources"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "gcp"
            ],
            "maxLength": 1024,
            "example": "gcp"
          },
          "context": {
            "type": "object",
            "required": [
              "owner",
              "system",
              "purpose",
              "env",
              "region"
            ],
            "properties": {
              "owner": {
                "type": "string",
                "maxLength": 64,
                "example": "platform"
              },
              "system": {
                "type": "string",
                "maxLength": 64,
                "example": "billing"
              },
              "purpose": {
                "type": "string",
                "maxLength": 64,
                "example": "trade"
              },
              "env": {
                "type": "string",
                "maxLength": 1024,
                "description": "Canonical: prod, stg, dev, sbx, qa, uat, test, perf, dr. Common aliases are accepted and normalised (prd, production, live -> prod; staging -> stg; development -> dev; sandbox -> sbx; quality -> qa; acceptance -> uat; testing, tst -> test; performance, load -> perf; disaster, recovery -> dr).",
                "example": "prod"
              },
              "region": {
                "type": "string",
                "maxLength": 1024,
                "description": "A region or a zone of the provider, by name or by code (asia-northeast3, asia-northeast3-a, ane3, ane3a), or global. Names carry the region code (ane3); a zone name is accepted and resolved to its region, so zonal resources are named like regional ones. Country values and region names of another provider are 400 context.value_unsupported with candidates in supported. The region table is in /llms.txt. Multi-region and dual-region locations (US, EU, ASIA, ASIA1, EUR4, EUR5, EUR7, EUR8, NAM4; by name or code, any case) are taken only by the resource types that support them: storage_bucket takes all nine, bigquery_dataset takes US and EU; names carry the lower-case code (us). A location is accepted only when every requested type that carries a region token takes it, otherwise 400 context.location_unsupported — always on /v1/generate, which names every type.",
                "example": "asia-northeast3-a"
              }
            }
          },
          "resources": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "description": "The resource types to name, each at most once. The total number of names (sum of count) is at most 200. The limits are for one screen and one share link; they do not bound the response size.",
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "maxLength": 1024,
                  "description": "A resource_type of the provider (see /llms.txt Resource tree)",
                  "example": "virtual_machine"
                },
                "count": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 99,
                  "description": "Only on rules with a sequence number (input count). Default 1",
                  "example": 2
                },
                "role": {
                  "type": "string",
                  "maxLength": 1024,
                  "pattern": "^[a-z0-9]{2,16}$",
                  "description": "Only on role rules (input role). Default sync",
                  "example": "db"
                },
                "kind": {
                  "type": "string",
                  "maxLength": 1024,
                  "description": "Only on rules with a kind table (see /llms.txt Kind tokens): a value name of that rule, such as POSTGRES or snapshot-schedule, not the token. The value that is the provider default for that scope adds no token. Required on resource_policy",
                  "example": "POSTGRES"
                }
              }
            }
          },
          "rules_version": {
            "type": "string",
            "maxLength": 1024,
            "description": "Optional pin to a rules revision (YYYY-MM-DD.N). If it differs from the current revision the API answers 409 instead of returning names that may differ. Not a string -> 400.",
            "example": "2026-09-29.2"
          }
        }
      },
      "NamesResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "success",
          "version",
          "data",
          "metadata"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "data": {
            "type": "object",
            "description": "provider -> NamesProviderResult. Exactly one key: the requested provider.",
            "additionalProperties": {
              "$ref": "#/components/schemas/NamesProviderResult"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/GenerateMetadata"
          }
        }
      },
      "NamesProviderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "valid",
          "items",
          "conflicts"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "true only when every item is valid (after name.conflict is applied)"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NameItem"
            }
          },
          "conflicts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "One name.conflict per collision (params.name, params.keys). The same message is also in the violations of each colliding item"
          }
        }
      },
      "NameItem": {
        "type": "object",
        "additionalProperties": false,
        "description": "One generated name of /names: a ResourceItem with the item key, resource type, sequence or role, kind, placement scope and uniqueness",
        "required": [
          "key",
          "resource_type",
          "scope",
          "uniqueness",
          "name",
          "valid",
          "is_truncated",
          "is_transformed",
          "original",
          "violations",
          "steps",
          "rule"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Unique within the response: resource_type_seq (count rules), resource_type_role (role rules) or resource_type (rules with no input)",
            "example": "virtual_machine_01"
          },
          "resource_type": {
            "type": "string",
            "example": "virtual_machine"
          },
          "seq": {
            "type": "string",
            "pattern": "^[0-9]{2}$",
            "description": "Present on count rules: 01..99",
            "example": "01"
          },
          "role": {
            "type": "string",
            "pattern": "^[a-z0-9]{2,16}$",
            "description": "Present on role rules: the requested role or the default sync",
            "example": "db"
          },
          "kind": {
            "type": "string",
            "description": "Present when the item asked for a kind: the value name as sent",
            "example": "POSTGRES"
          },
          "kind_token": {
            "type": "string",
            "pattern": "^[a-z0-9]{1,5}$",
            "description": "Present when the kind puts a token in the name (absent for the provider default of that scope)",
            "example": "pg"
          },
          "scope": {
            "type": "string",
            "enum": [
              "aws:partition",
              "aws:account",
              "aws:region",
              "aws:ecs_cluster",
              "aws:log_group",
              "aws:vpc",
              "gcp:namespace",
              "gcp:project",
              "gcp:project_global",
              "gcp:project_region",
              "gcp:project_zone",
              "gcp:project_location",
              "gcp:kms_key_ring",
              "gcp:spanner_instance",
              "gcp:workforce_pool",
              "gcp:workload_identity_pool",
              "azure:global",
              "azure:subscription",
              "azure:resource_group",
              "azure:virtual_network",
              "azure:managed_cluster",
              "azure:region",
              "azure:appconfiguration/configurationstores",
              "azure:elasticsan/elasticsans",
              "azure:machinelearningservices/workspaces",
              "azure:synapse/workspaces",
              "azure:web/sites",
              "azure:apimanagement/service",
              "azure:apimanagement/service/apis",
              "azure:apimanagement/service/apis/issues",
              "azure:apimanagement/service/apis/operations",
              "azure:apimanagement/service/groups",
              "azure:apimanagement/service/notifications",
              "azure:apimanagement/service/products",
              "azure:automation/automationaccounts",
              "azure:batch/batchaccounts",
              "azure:botservice/botservices",
              "azure:cache/redis",
              "azure:compute/galleries",
              "azure:containerregistry/registries",
              "azure:containerregistry/registries/buildtasks",
              "azure:customerinsights/hubs",
              "azure:customerinsights/hubs/connectors",
              "azure:datafactory/factories",
              "azure:datafactory/factories/triggers",
              "azure:datalakeanalytics/accounts",
              "azure:datalakestore/accounts",
              "azure:datamigration/services",
              "azure:dataprotection/backupvaults",
              "azure:dbformariadb/servers",
              "azure:dbformysql/servers",
              "azure:dbforpostgresql/servers",
              "azure:devices/iothubs",
              "azure:devices/iothubs/eventhubendpoints",
              "azure:devices/provisioningservices",
              "azure:devtestlab/labs",
              "azure:eventgrid/domains",
              "azure:eventhub/namespaces",
              "azure:eventhub/namespaces/eventhubs",
              "azure:keyvault/vaults",
              "azure:kusto/clusters",
              "azure:kusto/clusters/databases",
              "azure:logic/integrationaccounts",
              "azure:logic/integrationserviceenvironments",
              "azure:media/mediaservices",
              "azure:media/mediaservices/liveevents",
              "azure:mobilenetwork/mobilenetworks",
              "azure:mobilenetwork/packetcorecontrolplanes",
              "azure:mobilenetwork/simgroups",
              "azure:netapp/netappaccounts",
              "azure:network/firewallpolicies",
              "azure:network/loadbalancers",
              "azure:network/networksecuritygroups",
              "azure:network/privatednszones",
              "azure:network/privatelinkservices",
              "azure:network/routefilters",
              "azure:network/routetables",
              "azure:network/vpngateways",
              "azure:networkcloud/edgeclusters",
              "azure:networkcloud/kubernetesclusters",
              "azure:networkcloud/registrationhubs",
              "azure:notificationhubs/namespaces",
              "azure:notificationhubs/namespaces/notificationhubs",
              "azure:recoveryservices/vaults",
              "azure:relay/namespaces",
              "azure:relay/namespaces/hybridconnections",
              "azure:relay/namespaces/wcfrelays",
              "azure:servicebus/namespaces",
              "azure:servicebus/namespaces/queues",
              "azure:servicebus/namespaces/topics",
              "azure:sql/servers",
              "azure:sql/servers/databases",
              "azure:storage/storageaccounts",
              "azure:storage/storageaccounts/blobservices",
              "azure:storage/storageaccounts/fileservices",
              "azure:storagesync/storagesyncservices",
              "azure:streamanalytics/streamingjobs",
              "azure:timeseriesinsights/environments",
              "gcp:artifact_registry_repository",
              "azure:tenant",
              "gcp:cloud_sql_instance",
              "gcp:router"
            ],
            "description": "scope is the placement scope: the parent the resource is created in (a scope id such as azure:resource_group). It says where the name lives, not that the provider enforces uniqueness there; see uniqueness.",
            "example": "azure:resource_group"
          },
          "uniqueness": {
            "type": "string",
            "enum": [
              "csp",
              "none"
            ],
            "description": "uniqueness \"csp\" means the provider requires the name to be unique within its scope; \"none\" (AWS Name tags) means the provider does not enforce uniqueness at all. Cloudonym compares names only with each other inside one request (name.conflict), never with resources that already exist.",
            "example": "csp"
          },
          "name": {
            "type": "string",
            "description": "Final name. Empty when a required token became empty after normalisation.",
            "example": "billing-trade-prod-sa-sync"
          },
          "valid": {
            "type": "boolean",
            "description": "PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
            "example": true
          },
          "is_truncated": {
            "type": "boolean",
            "description": "Optional tokens were dropped or tokens were shortened to fit",
            "example": true
          },
          "is_transformed": {
            "type": "boolean",
            "description": "Characters were changed by normalisation or separators were removed",
            "example": false
          },
          "original": {
            "type": "string",
            "description": "Name assembled before normalisation and shortening",
            "example": "billing-trade-prod-sa-sync"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Why the name is invalid (empty when valid)"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Shortening and transformation steps in order (step.token_dropped, step.abbreviated, step.budget_shortened, step.normalized, step.separator_removed)"
          },
          "rule": {
            "$ref": "#/components/schemas/RuleInfo"
          }
        }
      },
      "RuleInfo": {
        "type": "object",
        "required": [
          "min_length",
          "max_length",
          "pattern",
          "pattern_flags",
          "allowed",
          "doc_url",
          "notes"
        ],
        "properties": {
          "min_length": {
            "type": "integer",
            "example": 6
          },
          "max_length": {
            "type": "integer",
            "example": 30
          },
          "pattern": {
            "type": "string",
            "description": "Provider's physical rule (regular expression source). Rebuild with new RegExp(pattern, pattern_flags).",
            "example": "^[a-z][a-z0-9-]{4,28}[a-z0-9]$"
          },
          "pattern_flags": {
            "type": "string",
            "description": "RegExp flags for pattern ('u' for Unicode classes, else empty)",
            "example": ""
          },
          "allowed": {
            "type": "string",
            "description": "Character set Cloudonym uses when generating (house style)",
            "example": "lower-digit-hyphen"
          },
          "doc_url": {
            "type": "string",
            "description": "Official document the numbers come from"
          },
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Notes on the rule, e.g. where a limit is deliberately conservative or reserved words apply (note.* codes). Always present, may be empty"
          }
        },
        "additionalProperties": false
      },
      "Message": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message"
        ],
        "description": "One reason, step, note or error. code is stable for machines; message is English text for people and may change between releases.",
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^[a-z]+\\.[a-z_0-9]+$",
            "example": "name.too_long"
          },
          "field": {
            "type": "string",
            "description": "Context field (owner ...), token name, label key or request field (provider, providers[1], rules_version ...) the message is about",
            "example": "owner"
          },
          "params": {
            "type": "object",
            "description": "Values used in message. User values (from, to, value) are display strings: cut to 40 characters followed by … in failure responses (params.value, params.requested and the message built from them), non-strings shown as JSON; control, bidirectional and zero-width characters are shown as \\uXXXX text in params and message",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            },
            "example": {
              "actual": 34,
              "max": 30
            }
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "example": "Name is 34 characters long; the maximum is 30"
          }
        }
      },
      "Version": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "engine",
          "rules"
        ],
        "description": "engine = Cloudonym engine version; rules = rules revision (YYYY-MM-DD.N). A different rules revision may give a different name, verdict, suggestion or label for the same input.",
        "properties": {
          "engine": {
            "type": "string",
            "example": "0.1.3"
          },
          "rules": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}\\.\\d+$",
            "example": "2026-09-29.2"
          }
        }
      },
      "ValidateRequest": {
        "type": "object",
        "required": [
          "provider",
          "resource_type",
          "name"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "maxLength": 1024,
            "enum": [
              "gcp"
            ],
            "example": "gcp"
          },
          "resource_type": {
            "type": "string",
            "maxLength": 1024,
            "description": "One of the provider's resource types; an unknown value returns 400 with the supported list",
            "example": "service_account"
          },
          "name": {
            "type": "string",
            "maxLength": 1024,
            "description": "Longer than 1024 characters returns 400. Up to 1024, a name over the rule's max_length is judged by the engine (valid=false with a length violation).",
            "example": "platform-billing-trade-prod-ane3-sa-sync"
          },
          "rules_version": {
            "type": "string",
            "maxLength": 1024,
            "description": "Optional pin to a rules revision (YYYY-MM-DD.N). If it differs from the current revision the API answers 409 instead of returning names that may differ. Not a string -> 400.",
            "example": "2026-09-29.2"
          }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "valid",
          "version",
          "violations"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
            "example": false
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "example": [
              {
                "code": "name.too_long",
                "params": {
                  "actual": 34,
                  "max": 30
                },
                "message": "Name is 34 characters long; the maximum is 30"
              }
            ]
          },
          "suggested_name": {
            "type": "string",
            "description": "Present only when the name is invalid and a fix is possible",
            "example": "billing-trade-prod-ane3-sa-sync"
          }
        }
      },
      "EventRequest": {
        "type": "object",
        "required": [
          "event"
        ],
        "properties": {
          "event": {
            "type": "string",
            "maxLength": 1024,
            "enum": [
              "ui_visit",
              "ui_generate",
              "ui_copy"
            ],
            "example": "ui_visit"
          },
          "target": {
            "type": "string",
            "maxLength": 1024,
            "enum": [
              "share-url",
              "name",
              "all-names",
              "names-json",
              "terraform",
              "tags-json",
              "plan-json"
            ],
            "description": "Which copy button succeeded (plan-json: the wizard's Save plan download, D100). Required when event is ui_copy (missing is 400 request.field_missing, unknown is 400 request.value_unsupported, both with field target); ignored for the other events. The copied text, the item key and the resource type are never sent.",
            "example": "names-json"
          },
          "chip_rules": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Rule ids of the resource kinds picked in the wizard with a preset chip (the serverless rule when serverless is on). Read only on ui_generate and ignored for the other events. Only ids of the rule table are kept; a value that is not an array of strings, or is longer than the number of rules, is dropped without an error. Context values, service names, tiers and roles are never sent.",
            "example": [
              "gcp.compute_instance",
              "gcp.run_service"
            ]
          },
          "custom_rules": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Rule ids of the resource kinds picked in the wizard with Custom. Read only on ui_generate and ignored for the other events. Only ids of the rule table are kept; a value that is not an array of strings, or is longer than the number of rules, is dropped without an error. Context values, service names, tiers and roles are never sent.",
            "example": [
              "gcp.secret"
            ]
          }
        }
      },
      "EventResponse": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          }
        },
        "additionalProperties": false
      },
      "HealthResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "version"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "success",
          "errors"
        ],
        "description": "Every failure (400, 403, 409, 413, 429, 500) has this shape. A /wizard or /generate 400 lists at most the first 50 errors; when there are more, a last entry with code request.errors_truncated and params { shown, total } says how many were left out. Other routes list every error. User values echoed in params.value and params.requested (and the message) are cut to 40 characters followed by …; supported is never cut",
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "minItems": 1,
            "example": [
              {
                "code": "context.value_unsupported",
                "field": "env",
                "params": {
                  "value": "preprod",
                  "supported": [
                    "prod",
                    "stg",
                    "dev",
                    "sbx",
                    "qa",
                    "uat",
                    "test",
                    "perf",
                    "dr"
                  ]
                },
                "message": "env 'preprod' is not supported (prod, stg, dev, sbx, qa, uat, test, perf, dr)"
              }
            ]
          },
          "supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Accepted values, present when a provider, resource_type, resources type, event or providers entry is unknown, or when providers is not a non-empty array (request.providers_not_array, request.providers_empty)"
          }
        }
      },
      "CatalogResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "version",
          "providers"
        ],
        "description": "The resource catalog and region table of the enabled providers, with what a client needs to build a wizard plan: each rule's input kind, tier slot and locations, the wizard presets, the attachment table and the request limits. Generated from catalog/*.json and the engine's tables in the repository, the same data the engine and /llms.txt use.",
        "properties": {
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "providers": {
            "type": "object",
            "description": "Keyed by enabled provider (gcp in this release)",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "meta",
                "rows",
                "regions",
                "presets",
                "attachments",
                "limits"
              ],
              "properties": {
                "meta": {
                  "type": "object",
                  "nullable": true,
                  "description": "Source versions the catalog was checked against (gcloud, Terraform provider, discovery revision)"
                },
                "rows": {
                  "type": "array",
                  "description": "One row per screen row",
                  "items": {
                    "type": "object",
                    "required": [
                      "key",
                      "displayName",
                      "category",
                      "rules",
                      "slug",
                      "entity",
                      "kind",
                      "info",
                      "enterNameAt",
                      "badges",
                      "aliases"
                    ],
                    "properties": {
                      "key": {
                        "type": "string",
                        "example": "gcp.subnet"
                      },
                      "displayName": {
                        "type": "string",
                        "example": "Subnet"
                      },
                      "category": {
                        "type": "string",
                        "example": "networking"
                      },
                      "rules": {
                        "type": "object",
                        "description": "Rule id -> { scope, regionToken (none or region), kinds? { default, allowed }, input, tier, locations? }. A row with several rules asks for the scope; each rule is one resource_type of /v1/names and one rule of a wizard pick",
                        "additionalProperties": {
                          "type": "object",
                          "required": [
                            "scope",
                            "regionToken",
                            "input",
                            "tier"
                          ],
                          "properties": {
                            "scope": {
                              "type": "string"
                            },
                            "regionToken": {
                              "type": "string",
                              "enum": [
                                "none",
                                "region"
                              ]
                            },
                            "kinds": {
                              "type": "object",
                              "properties": {
                                "default": {
                                  "type": "string",
                                  "nullable": true
                                },
                                "allowed": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  }
                                }
                              }
                            },
                            "input": {
                              "type": "string",
                              "enum": [
                                "count",
                                "role",
                                "none"
                              ],
                              "description": "What a pick of this rule takes: count (numbered servers, instances[] in /v1/wizard), role (one role value) or none"
                            },
                            "tier": {
                              "type": "boolean",
                              "description": "Whether the name has a tier slot (a pick may send tier)"
                            },
                            "locations": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Values a wizard pick may send as location (multi-region and dual-region names, or regional for a secret). Absent when the rule takes no location"
                            }
                          }
                        }
                      },
                      "slug": {
                        "type": "object",
                        "required": [
                          "value",
                          "basis"
                        ],
                        "description": "The abbreviation in the name and why it was chosen",
                        "properties": {
                          "value": {
                            "type": "string",
                            "example": "snet"
                          },
                          "basis": {
                            "type": "string"
                          },
                          "previous": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      },
                      "entity": {
                        "type": "string",
                        "nullable": true
                      },
                      "kind": {
                        "type": "object",
                        "nullable": true,
                        "description": "Kind value name -> token (null = no token), the values that add no token and the source",
                        "properties": {
                          "rule": {
                            "type": "string"
                          },
                          "tokens": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "string",
                              "nullable": true
                            }
                          },
                          "omitValues": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "source": {
                            "type": "string"
                          },
                          "defaultBasis": {
                            "type": "string"
                          }
                        }
                      },
                      "info": {
                        "type": "string",
                        "description": "One-line description"
                      },
                      "enterNameAt": {
                        "type": "object",
                        "required": [
                          "gcloud",
                          "terraform",
                          "apiOnly"
                        ],
                        "properties": {
                          "gcloud": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "terraform": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "apiOnly": {
                            "type": "boolean"
                          },
                          "preview": {
                            "type": "boolean"
                          }
                        }
                      },
                      "badges": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "api-only",
                            "preview"
                          ]
                        }
                      },
                      "aliases": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Search terms: Terraform type, gcloud group, product name, earlier abbreviation"
                      }
                    }
                  }
                },
                "regions": {
                  "type": "array",
                  "description": "Regions with their code and zones",
                  "items": {
                    "type": "object",
                    "required": [
                      "region",
                      "code",
                      "zones"
                    ],
                    "properties": {
                      "region": {
                        "type": "string",
                        "example": "asia-northeast3"
                      },
                      "code": {
                        "type": "string",
                        "example": "ane3"
                      },
                      "label": {
                        "type": "string"
                      },
                      "zones": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "required": [
                            "zone",
                            "code"
                          ],
                          "properties": {
                            "zone": {
                              "type": "string",
                              "example": "asia-northeast3-a"
                            },
                            "code": {
                              "type": "string",
                              "example": "ane3a"
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "presets": {
                  "type": "array",
                  "description": "Wizard presets (POST /v1/wizard pick.preset). Empty for a provider without the wizard",
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "id",
                      "label",
                      "rule",
                      "tier",
                      "ha",
                      "serverless",
                      "level"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "example": "db"
                      },
                      "label": {
                        "type": "string",
                        "example": "Database (Cloud SQL)"
                      },
                      "rule": {
                        "type": "string",
                        "example": "gcp.cloud_sql_instance",
                        "description": "The rule the preset names"
                      },
                      "tier": {
                        "type": "string",
                        "nullable": true,
                        "description": "The tier used when the pick sends none"
                      },
                      "tier_choices": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Tiers offered on the screen; the first is the default"
                      },
                      "kind": {
                        "type": "string",
                        "description": "Fixed kind; another kind is wizard.preset_kind_fixed"
                      },
                      "ha": {
                        "type": "object",
                        "nullable": true,
                        "required": [
                          "min_instances"
                        ],
                        "properties": {
                          "min_instances": {
                            "type": "integer"
                          }
                        },
                        "description": "The pick may send ha: true with at least min_instances servers"
                      },
                      "serverless": {
                        "type": "array",
                        "nullable": true,
                        "items": {
                          "type": "string"
                        },
                        "description": "Rules the pick may send as serverless"
                      },
                      "default_attach": {
                        "type": "object",
                        "description": "Attachments the screen turns on for a new server (not added by the API)"
                      },
                      "level": {
                        "type": "string",
                        "enum": [
                          "project",
                          "service"
                        ],
                        "description": "project_picks or a service's picks"
                      },
                      "group": {
                        "type": "string",
                        "description": "Screen group (network, lb)"
                      }
                    }
                  }
                },
                "attachments": {
                  "type": "array",
                  "description": "Resources attached to a server slot (instances[].attach): parent rule, attach key, child rule and whether the child is numbered (data disks)",
                  "items": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "parent",
                      "attach",
                      "child",
                      "seq"
                    ],
                    "properties": {
                      "parent": {
                        "type": "string",
                        "example": "gcp.compute_instance"
                      },
                      "attach": {
                        "type": "string",
                        "enum": [
                          "boot",
                          "data",
                          "ip",
                          "ipi"
                        ]
                      },
                      "child": {
                        "type": "string",
                        "example": "gcp.disk"
                      },
                      "seq": {
                        "type": "boolean"
                      }
                    }
                  }
                },
                "limits": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "Request limits and value patterns, for a client to check before sending. The API's answer is the verdict",
                  "required": [
                    "max_services",
                    "max_picks_per_service",
                    "max_names",
                    "max_count",
                    "max_data_disks",
                    "max_body_bytes",
                    "seq_pattern",
                    "slot_id_pattern",
                    "tier_pattern",
                    "role_pattern"
                  ],
                  "properties": {
                    "max_services": {
                      "type": "integer",
                      "example": 20
                    },
                    "max_picks_per_service": {
                      "type": "integer",
                      "example": 40
                    },
                    "max_names": {
                      "type": "integer",
                      "example": 200
                    },
                    "max_count": {
                      "type": "integer",
                      "example": 99
                    },
                    "max_data_disks": {
                      "type": "integer",
                      "example": 9
                    },
                    "max_body_bytes": {
                      "type": "integer",
                      "example": 32768
                    },
                    "seq_pattern": {
                      "type": "string"
                    },
                    "slot_id_pattern": {
                      "type": "string"
                    },
                    "tier_pattern": {
                      "type": "string"
                    },
                    "role_pattern": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "WizardRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "provider",
          "project"
        ],
        "description": "One project. project_picks and services may each be empty or omitted, but not both (wizard.empty)",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "gcp"
            ],
            "maxLength": 1024,
            "example": "gcp"
          },
          "project": {
            "type": "object",
            "required": [
              "owner",
              "system",
              "env",
              "region"
            ],
            "description": "The project context: the four values without purpose. Each service adds its purpose",
            "properties": {
              "owner": {
                "type": "string",
                "maxLength": 64,
                "example": "platform"
              },
              "system": {
                "type": "string",
                "maxLength": 64,
                "example": "billing"
              },
              "env": {
                "type": "string",
                "maxLength": 1024,
                "description": "Canonical: prod, stg, dev, sbx, qa, uat, test, perf, dr. Common aliases are accepted and normalised (prd, production, live -> prod; staging -> stg; development -> dev; sandbox -> sbx; quality -> qa; acceptance -> uat; testing, tst -> test; performance, load -> perf; disaster, recovery -> dr).",
                "example": "prod"
              },
              "region": {
                "type": "string",
                "maxLength": 1024,
                "description": "A region or a zone of the provider, by name or by code (asia-northeast3, asia-northeast3-a, ane3, ane3a), or global. Names carry the region code (ane3); a zone name is accepted and resolved to its region, so zonal resources are named like regional ones. Country values and region names of another provider are 400 context.value_unsupported with candidates in supported. The region table is in /llms.txt. The project region is a single region: a multi-region or dual-region location here is 400 context.location_unsupported; give it per pick in location instead.",
                "example": "asia-northeast3-a"
              }
            }
          },
          "project_id_suffix": {
            "type": "string",
            "pattern": "^[a-z0-9]{2,16}$",
            "maxLength": 1024,
            "description": "Optional suffix appended to the project ID (platform-billing-prod-team1), so a predictable ID can still be unique. Same rules as a tier: 2-16 lowercase letters or digits, not a reserved word (400 wizard.invalid_project_id_suffix, wizard.project_id_suffix_reserved). It is never shortened; the item key stays project/-/project_id/-/-/-. Project IDs are unique across Google Cloud and cannot be reused; Cloudonym does not check whether an ID is free.",
            "example": "team1"
          },
          "project_picks": {
            "type": "array",
            "maxItems": 40,
            "description": "Project-level resources (no purpose in the name): rules placed in the project, its global or regional scope, such as vpc, subnet and firewall. At most 40",
            "items": {
              "$ref": "#/components/schemas/WizardPick"
            }
          },
          "services": {
            "type": "array",
            "maxItems": 20,
            "description": "At most 20 services. purpose is the service name and namespace; after cleaning it must be non-empty, unique and not project",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "purpose",
                "picks"
              ],
              "properties": {
                "purpose": {
                  "type": "string",
                  "maxLength": 64,
                  "example": "shop"
                },
                "picks": {
                  "type": "array",
                  "maxItems": 40,
                  "description": "At most 40 picks per service",
                  "items": {
                    "$ref": "#/components/schemas/WizardPick"
                  }
                }
              }
            }
          },
          "rules_version": {
            "type": "string",
            "maxLength": 1024,
            "description": "Optional pin to a rules revision (YYYY-MM-DD.N). If it differs from the current revision the API answers 409 instead of returning names that may differ. Not a string -> 400.",
            "example": "2026-09-29.2"
          },
          "environments": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "prod",
                "stg",
                "dev",
                "sbx",
                "qa",
                "uat",
                "test",
                "perf",
                "dr"
              ]
            },
            "description": "Ignored by the API. A plan file saved from the wizard (Save plan, D100) is this request body plus this list when the plan has two or more environments; project.env is the first. The API names project.env only, so send one request per environment with project.env set to each value.",
            "example": [
              "prod",
              "stg"
            ]
          }
        }
      },
      "WizardPick": {
        "type": "object",
        "additionalProperties": false,
        "description": "One resource line: a preset or a rule. The same (rule, tier, role) may appear once per service (wizard.duplicate_pick)",
        "properties": {
          "preset": {
            "type": "string",
            "nullable": true,
            "maxLength": 1024,
            "description": "A preset id: web, was, bastion, file, db, object, run, sa, secret, gke, wip, ip, ig, bs, hc, url, cert, thps, fwd, urlredir, thp, fwdredir, neg, bb, vpc, subnet, firewall, router, nat, psa. null or omitted for a custom pick by rule. The presets are listed in /llms.txt Wizard presets",
            "example": "web"
          },
          "rule": {
            "type": "string",
            "maxLength": 1024,
            "description": "A rule id (gcp.run_service) when preset is null. gcp.project_id is always included and cannot be picked (wizard.reserved_pick)",
            "example": "gcp.run_service"
          },
          "tier": {
            "type": "string",
            "maxLength": 1024,
            "pattern": "^[a-z0-9]{2,16}$",
            "description": "The tier token placed after the abbreviation and kind token, before the sequence (billing-shop-prod-ane3-gce-web-01). On rules with a sequence number, and on Google Cloud rules with neither a sequence number nor a role, where it goes right after the abbreviation (platform-billing-shop-prod-ane3-gcs-uploads); never on role rules. Overrides the preset tier. Abbreviations, kind tokens, environment values and region codes are reserved (wizard.tier_reserved)",
            "example": "web"
          },
          "role": {
            "type": "string",
            "maxLength": 1024,
            "description": "Only on role rules, as on /names",
            "example": "db"
          },
          "kind": {
            "type": "string",
            "maxLength": 1024,
            "description": "A kind value name, as on /names",
            "example": "POSTGRES"
          },
          "ha": {
            "type": "boolean",
            "nullable": true,
            "description": "High availability: only on the web and was presets as virtual machines; needs at least 2 servers (wizard.count_below_min)",
            "example": true
          },
          "serverless": {
            "type": "string",
            "nullable": true,
            "maxLength": 1024,
            "description": "Run the web or was preset on a serverless rule instead of a virtual machine: gcp.cloud_function or gcp.run_service",
            "example": "gcp.run_service"
          },
          "location": {
            "type": "string",
            "maxLength": 1024,
            "description": "A multi-region or dual-region location for this pick, by name or code (US, us, ASIA1). Only on rules that take one: storage_bucket (US, EU, ASIA, ASIA1, EUR4, EUR5, EUR7, EUR8, NAM4) and bigquery_dataset (US, EU). The name carries its code in the region slot (…-prod-us-gcs). Omitted: the project region. On secret picks the only value is regional (D79): omitted, the secret is global and its name has no region token; regional puts the project region in its name (…-prod-ane3-secret-01), and is 400 wizard.location_needs_region when the project region is global. Anything else is 400 wizard.invalid_location with the accepted locations in params.supported.",
            "example": "US"
          },
          "instances": {
            "type": "array",
            "minItems": 1,
            "maxItems": 99,
            "description": "One slot per server (rules with a sequence number only). Required on those rules",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "seq"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "maxLength": 1024,
                  "pattern": "^[a-z0-9]{1,8}$",
                  "description": "Slot id, unique within the pick (used by share links)",
                  "example": "a"
                },
                "seq": {
                  "type": "string",
                  "maxLength": 1024,
                  "pattern": "^(0[1-9]|[1-9][0-9])$",
                  "description": "The sequence number in the name, unique within the pick",
                  "example": "01"
                },
                "attach": {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "Attached resources (virtual machines only): boot disk, data disks, static external (ip) and internal (ipi) address",
                  "properties": {
                    "boot": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "minItems": 1,
                      "maxItems": 9,
                      "items": {
                        "type": "string",
                        "maxLength": 1024,
                        "pattern": "^(0[1-9]|[1-9][0-9])$"
                      },
                      "example": [
                        "01"
                      ]
                    },
                    "ip": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "ipi": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "WizardItem": {
        "type": "object",
        "additionalProperties": false,
        "description": "One generated name of /wizard: a NameItem with a structured key, the namespace, and for attached resources the parent key and suffix kind",
        "required": [
          "key",
          "namespace",
          "resource_type",
          "scope",
          "uniqueness",
          "name",
          "valid",
          "is_truncated",
          "is_transformed",
          "original",
          "violations",
          "steps",
          "rule"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Structured key, unique within the response: <level>/<service>/<resource_type>/<kind|->/<tier|->/<seq|role|-> for a parent, where kind is the kind token in the name or - (project/-/vpc_network/-/-/01, project/-/subnet/proxy/-/01, service/shop/compute_instance/-/web/01), followed by /<attach>/<seq|-> for an attached resource (service/shop/compute_instance/-/web/01/data/01)",
            "example": "service/shop/compute_instance/-/web/01"
          },
          "namespace": {
            "type": "string",
            "description": "project for project-level items, otherwise the cleaned purpose of the service",
            "example": "shop"
          },
          "level": {
            "type": "string",
            "enum": [
              "project"
            ],
            "description": "Present on project-level items"
          },
          "parent": {
            "type": "string",
            "description": "Present on attached resources: the key of the parent item",
            "example": "service/shop/compute_instance/-/web/01"
          },
          "attach": {
            "type": "string",
            "enum": [
              "boot",
              "data",
              "ip",
              "ipi"
            ],
            "description": "Present on attached resources: the suffix kind"
          },
          "resource_type": {
            "type": "string",
            "example": "virtual_machine"
          },
          "tier": {
            "type": "string",
            "pattern": "^[a-z0-9]{2,16}$",
            "description": "Present when the item carries a tier token",
            "example": "web"
          },
          "seq": {
            "type": "string",
            "pattern": "^[0-9]{2}$",
            "description": "Present on count rules: 01..99",
            "example": "01"
          },
          "role": {
            "type": "string",
            "pattern": "^[a-z0-9]{2,16}$",
            "description": "Present on role rules: the requested role or the default sync",
            "example": "db"
          },
          "kind": {
            "type": "string",
            "description": "Present when the item asked for a kind: the value name as sent",
            "example": "POSTGRES"
          },
          "kind_token": {
            "type": "string",
            "pattern": "^[a-z0-9]{1,5}$",
            "description": "Present when the kind puts a token in the name (absent for the provider default of that scope)",
            "example": "pg"
          },
          "scope": {
            "type": "string",
            "enum": [
              "aws:partition",
              "aws:account",
              "aws:region",
              "aws:ecs_cluster",
              "aws:log_group",
              "aws:vpc",
              "gcp:namespace",
              "gcp:project",
              "gcp:project_global",
              "gcp:project_region",
              "gcp:project_zone",
              "gcp:project_location",
              "gcp:kms_key_ring",
              "gcp:spanner_instance",
              "gcp:workforce_pool",
              "gcp:workload_identity_pool",
              "azure:global",
              "azure:subscription",
              "azure:resource_group",
              "azure:virtual_network",
              "azure:managed_cluster",
              "azure:region",
              "azure:appconfiguration/configurationstores",
              "azure:elasticsan/elasticsans",
              "azure:machinelearningservices/workspaces",
              "azure:synapse/workspaces",
              "azure:web/sites",
              "azure:apimanagement/service",
              "azure:apimanagement/service/apis",
              "azure:apimanagement/service/apis/issues",
              "azure:apimanagement/service/apis/operations",
              "azure:apimanagement/service/groups",
              "azure:apimanagement/service/notifications",
              "azure:apimanagement/service/products",
              "azure:automation/automationaccounts",
              "azure:batch/batchaccounts",
              "azure:botservice/botservices",
              "azure:cache/redis",
              "azure:compute/galleries",
              "azure:containerregistry/registries",
              "azure:containerregistry/registries/buildtasks",
              "azure:customerinsights/hubs",
              "azure:customerinsights/hubs/connectors",
              "azure:datafactory/factories",
              "azure:datafactory/factories/triggers",
              "azure:datalakeanalytics/accounts",
              "azure:datalakestore/accounts",
              "azure:datamigration/services",
              "azure:dataprotection/backupvaults",
              "azure:dbformariadb/servers",
              "azure:dbformysql/servers",
              "azure:dbforpostgresql/servers",
              "azure:devices/iothubs",
              "azure:devices/iothubs/eventhubendpoints",
              "azure:devices/provisioningservices",
              "azure:devtestlab/labs",
              "azure:eventgrid/domains",
              "azure:eventhub/namespaces",
              "azure:eventhub/namespaces/eventhubs",
              "azure:keyvault/vaults",
              "azure:kusto/clusters",
              "azure:kusto/clusters/databases",
              "azure:logic/integrationaccounts",
              "azure:logic/integrationserviceenvironments",
              "azure:media/mediaservices",
              "azure:media/mediaservices/liveevents",
              "azure:mobilenetwork/mobilenetworks",
              "azure:mobilenetwork/packetcorecontrolplanes",
              "azure:mobilenetwork/simgroups",
              "azure:netapp/netappaccounts",
              "azure:network/firewallpolicies",
              "azure:network/loadbalancers",
              "azure:network/networksecuritygroups",
              "azure:network/privatednszones",
              "azure:network/privatelinkservices",
              "azure:network/routefilters",
              "azure:network/routetables",
              "azure:network/vpngateways",
              "azure:networkcloud/edgeclusters",
              "azure:networkcloud/kubernetesclusters",
              "azure:networkcloud/registrationhubs",
              "azure:notificationhubs/namespaces",
              "azure:notificationhubs/namespaces/notificationhubs",
              "azure:recoveryservices/vaults",
              "azure:relay/namespaces",
              "azure:relay/namespaces/hybridconnections",
              "azure:relay/namespaces/wcfrelays",
              "azure:servicebus/namespaces",
              "azure:servicebus/namespaces/queues",
              "azure:servicebus/namespaces/topics",
              "azure:sql/servers",
              "azure:sql/servers/databases",
              "azure:storage/storageaccounts",
              "azure:storage/storageaccounts/blobservices",
              "azure:storage/storageaccounts/fileservices",
              "azure:storagesync/storagesyncservices",
              "azure:streamanalytics/streamingjobs",
              "azure:timeseriesinsights/environments",
              "gcp:artifact_registry_repository",
              "azure:tenant",
              "gcp:cloud_sql_instance",
              "gcp:router"
            ],
            "description": "scope is the placement scope: the parent the resource is created in (a scope id such as azure:resource_group). It says where the name lives, not that the provider enforces uniqueness there; see uniqueness.",
            "example": "azure:resource_group"
          },
          "uniqueness": {
            "type": "string",
            "enum": [
              "csp",
              "none"
            ],
            "description": "uniqueness \"csp\" means the provider requires the name to be unique within its scope; \"none\" (AWS Name tags) means the provider does not enforce uniqueness at all. Cloudonym compares names only with each other inside one request (name.conflict), never with resources that already exist.",
            "example": "csp"
          },
          "name": {
            "type": "string",
            "description": "Final name. Empty when a required token became empty after normalisation.",
            "example": "billing-trade-prod-sa-sync"
          },
          "valid": {
            "type": "boolean",
            "description": "PASS / `valid: true` means the name satisfies the documented naming checks of this rules revision (length, pattern, reserved words). It does not mean the name is available or that you may create it — uniqueness, permissions, quotas and organisation policy are not checked. Generated names are not guaranteed unique: shortening can map two different contexts to the same name.",
            "example": true
          },
          "is_truncated": {
            "type": "boolean",
            "description": "Optional tokens were dropped or tokens were shortened to fit",
            "example": true
          },
          "is_transformed": {
            "type": "boolean",
            "description": "Characters were changed by normalisation or separators were removed",
            "example": false
          },
          "original": {
            "type": "string",
            "description": "Name assembled before normalisation and shortening",
            "example": "billing-trade-prod-sa-sync"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Why the name is invalid (empty when valid)"
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Shortening and transformation steps in order (step.token_dropped, step.abbreviated, step.budget_shortened, step.normalized, step.separator_removed)"
          },
          "rule": {
            "$ref": "#/components/schemas/RuleInfo"
          }
        }
      },
      "WizardProviderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "valid",
          "items",
          "conflicts"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "true only when every item is valid (after name.conflict is applied)"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WizardItem"
            }
          },
          "conflicts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "One name.conflict per collision across the whole project (params.name, params.keys). The same message is also in the violations of each colliding item"
          }
        }
      },
      "WizardMetadata": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "generated_labels",
          "label_violations",
          "label_transformed",
          "transformed"
        ],
        "properties": {
          "generated_labels": {
            "type": "object",
            "description": "provider -> namespace -> standard labels. project has owner, system, environment and managed_by; each service adds purpose",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          },
          "label_violations": {
            "type": "object",
            "description": "provider -> label values over the limit or with rejected characters. field is <namespace>.<label key> (project.owner, shop.purpose)",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Message"
              }
            }
          },
          "label_transformed": {
            "type": "object",
            "description": "provider -> label values changed to fit the character rules. field is <namespace>.<label key>",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Message"
              }
            }
          },
          "transformed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "Context values normalised from aliases (context.normalized, e.g. env 'production' -> 'prod')"
          }
        }
      },
      "WizardResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "success",
          "version",
          "data",
          "metadata"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "version": {
            "$ref": "#/components/schemas/Version"
          },
          "data": {
            "type": "object",
            "description": "provider -> WizardProviderResult. Exactly one key: the requested provider.",
            "additionalProperties": {
              "$ref": "#/components/schemas/WizardProviderResult"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/WizardMetadata"
          }
        }
      }
    }
  }
}
