"""Wire types. These are deliberately NOT the domain models. `Instance` carries `team`, `namespace` and `release_name` — placement details a tenant has no business seeing and no business setting. The response model is the allow-list that keeps them off the wire, which is why it is written out by hand instead of derived from `Instance`. """ from __future__ import annotations from uuid import UUID from pydantic import BaseModel, ConfigDict, Field from svcforge_core.domain.states import InstanceState class CreateInstanceRequest(BaseModel): """What a tenant may ask for. `service_type` and `size` are plain strings, not enums: the catalog is data loaded at runtime, so baking its keys into a type would mean a redeploy to add a service type, and a 422 (schema) where the spec wants a 404 (unknown resource). They are validated against the catalog in the handler. """ model_config = ConfigDict( extra="forbid", json_schema_extra={"examples": [{"service_type": "redis", "size": "small", "ttl_days": 7}]}, ) service_type: str = Field( min_length=1, description=( "A service type in the catalog, e.g. `elasticsearch`, `redis`, `postgres`. " "Unknown values return 404." ), ) size: str = Field( description=( "A size the catalog defines for that service type, e.g. `small`. Unknown values return 422." ), ) ttl_days: int | None = Field( default=None, ge=1, le=30, description="Delete the instance automatically after this many days. Omit for no expiry.", ) class InstanceResponse(BaseModel): """What a tenant gets back. A subset of Instance, on purpose.""" model_config = ConfigDict( from_attributes=True, json_schema_extra={ "examples": [ { "id": "0f8b7d3e-1c2a-4f5b-9e6d-7a8b9c0d1e2f", "state": "ready", "service_type": "redis", "size": "small", "endpoint": "http://acme-redis-0f8b7d3e.tenant-acme.svc.cluster.local", "chart_version": "20.6.2", "error": None, } ] }, ) id: UUID = Field(description="Poll `GET /v1/instances/{id}` with this to watch the state change.") state: InstanceState = Field(description="Lifecycle state. Only `ready` carries a usable endpoint.") service_type: str size: str endpoint: str | None = Field(description="In-cluster DNS name. Null until the instance is `ready`.") chart_version: str = Field( description="The chart version actually deployed, written only after helm succeeds." ) error: str | None = Field(description="Why the last attempt failed. Null unless `state` is `failed`.") class ErrorBody(BaseModel): """Every non-2xx body. `code` is for machines, `message` is for humans.""" model_config = ConfigDict( json_schema_extra={ "examples": [{"code": "unknown_service_type", "message": "no such service_type: mongodb"}] } ) code: str = Field(description="Stable machine-readable identifier for the failure.") message: str = Field(description="Human-readable detail. Do not parse this.")