diff --git a/public/openapi.json b/public/openapi.json index 39bf23d..a90690a 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -753,7 +753,7 @@ "AdminService" ], "summary": "GetCosts", - "description": "A month of the bill shared out to the workspaces that caused it: the runners' by what each\n workspace held of them, the volumes' bucket by what its volumes kept. What nobody caused is\n said apart and never spread: the runners' capacity nobody held, the bucket's bytes no\n workspace keeps, and the platform's own cost by service (internal/controlplane/domain/finops).", + "description": "A month of the bill shared out to the workspaces that caused it: the runners' by what each\n workspace held of them, the volumes' bucket by what its volumes kept. What nobody caused is\n said apart and never spread: the runners' capacity nobody held, the bucket's bytes no\n workspace keeps, and the platform's own cost by role and service; and, from those, what the\n installation costs a day with nobody working (internal/controlplane/domain/finops).", "operationId": "spin.v1.AdminService.GetCosts", "parameters": [ { @@ -995,14 +995,14 @@ } } }, - "/spin.v1.AdminService/GetInstallationSettings": { + "/spin.v1.AdminService/GetMonitoring": { "post": { "tags": [ "AdminService" ], - "summary": "GetInstallationSettings", - "description": "Get installation settings", - "operationId": "spin.v1.AdminService.GetInstallationSettings", + "summary": "GetMonitoring", + "description": "The Grafana the control plane serves over the installation's metrics, and the release's\n dashboards in it.", + "operationId": "spin.v1.AdminService.GetMonitoring", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1027,7 +1027,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetInstallationSettingsRequest" + "$ref": "#/components/schemas/spin.v1.GetMonitoringRequest" } } }, @@ -1049,7 +1049,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetInstallationSettingsResponse" + "$ref": "#/components/schemas/spin.v1.GetMonitoringResponse" } } } @@ -1057,14 +1057,14 @@ } } }, - "/spin.v1.AdminService/GetMonitoring": { + "/spin.v1.AdminService/GetPlatformImages": { "post": { "tags": [ "AdminService" ], - "summary": "GetMonitoring", - "description": "The Grafana the control plane serves over the installation's metrics, and the release's\n dashboards in it.", - "operationId": "spin.v1.AdminService.GetMonitoring", + "summary": "GetPlatformImages", + "description": "Whether the base image and the supervisor this control plane's image carries are\n published.", + "operationId": "spin.v1.AdminService.GetPlatformImages", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1089,7 +1089,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetMonitoringRequest" + "$ref": "#/components/schemas/spin.v1.GetPlatformImagesRequest" } } }, @@ -1111,7 +1111,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetMonitoringResponse" + "$ref": "#/components/schemas/spin.v1.GetPlatformImagesResponse" } } } @@ -1119,14 +1119,14 @@ } } }, - "/spin.v1.AdminService/GetPlatformImages": { + "/spin.v1.AdminService/GetRunbook": { "post": { "tags": [ "AdminService" ], - "summary": "GetPlatformImages", - "description": "Whether the base image and the supervisor this control plane's image carries are\n published.", - "operationId": "spin.v1.AdminService.GetPlatformImages", + "summary": "GetRunbook", + "description": "One runbook, its markdown and where its file is at the pinned commit. NOT_FOUND for an id\n this release's runbooks do not have.", + "operationId": "spin.v1.AdminService.GetRunbook", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1151,7 +1151,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetPlatformImagesRequest" + "$ref": "#/components/schemas/spin.v1.GetRunbookRequest" } } }, @@ -1173,7 +1173,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetPlatformImagesResponse" + "$ref": "#/components/schemas/spin.v1.GetRunbookResponse" } } } @@ -1181,14 +1181,14 @@ } } }, - "/spin.v1.AdminService/GetRegistrationMode": { + "/spin.v1.AdminService/GetSettings": { "post": { "tags": [ "AdminService" ], - "summary": "GetRegistrationMode", - "description": "Get registration mode (open, whitelist, closed)", - "operationId": "spin.v1.AdminService.GetRegistrationMode", + "summary": "GetSettings", + "description": "Every setting of the installation as it is in force, and what each is: its type, its\n default, its bounds, what it reaches when saved and who writes it\n (internal/controlplane/settings, the one registry the file, the dashboard and the CLI are\n made from).", + "operationId": "spin.v1.AdminService.GetSettings", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1213,7 +1213,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetRegistrationModeRequest" + "$ref": "#/components/schemas/spin.v1.GetSettingsRequest" } } }, @@ -1235,7 +1235,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetRegistrationModeResponse" + "$ref": "#/components/schemas/spin.v1.GetSettingsResponse" } } } @@ -1243,14 +1243,14 @@ } } }, - "/spin.v1.AdminService/GetRunbook": { + "/spin.v1.AdminService/GetStorageSettings": { "post": { "tags": [ "AdminService" ], - "summary": "GetRunbook", - "description": "One runbook, its markdown and where its file is at the pinned commit. NOT_FOUND for an id\n this release's runbooks do not have.", - "operationId": "spin.v1.AdminService.GetRunbook", + "summary": "GetStorageSettings", + "description": "Where the installation's object store is. The secret is never read back.", + "operationId": "spin.v1.AdminService.GetStorageSettings", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1275,7 +1275,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetRunbookRequest" + "$ref": "#/components/schemas/spin.v1.GetStorageSettingsRequest" } } }, @@ -1297,7 +1297,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetRunbookResponse" + "$ref": "#/components/schemas/spin.v1.GetStorageSettingsResponse" } } } @@ -1305,14 +1305,14 @@ } } }, - "/spin.v1.AdminService/GetSecuritySettings": { + "/spin.v1.AdminService/GetStorageUsage": { "post": { "tags": [ "AdminService" ], - "summary": "GetSecuritySettings", - "description": "Get security settings", - "operationId": "spin.v1.AdminService.GetSecuritySettings", + "summary": "GetStorageUsage", + "description": "What the store holds, by what each object is for: every volume's objects and the layers its\n commits name, with the workspace, image or extension release it is. As the storage-gc job\n last read it - daily, or when an admin runs it - never the bucket itself.", + "operationId": "spin.v1.AdminService.GetStorageUsage", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1337,7 +1337,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetSecuritySettingsRequest" + "$ref": "#/components/schemas/spin.v1.GetStorageUsageRequest" } } }, @@ -1359,7 +1359,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetSecuritySettingsResponse" + "$ref": "#/components/schemas/spin.v1.GetStorageUsageResponse" } } } @@ -1367,14 +1367,14 @@ } } }, - "/spin.v1.AdminService/GetStorageSettings": { + "/spin.v1.AdminService/GetTelemetryStatus": { "post": { "tags": [ "AdminService" ], - "summary": "GetStorageSettings", - "description": "Where the installation's object store is. The secret is never read back.", - "operationId": "spin.v1.AdminService.GetStorageSettings", + "summary": "GetTelemetryStatus", + "description": "What the collector has sent and failed to send over its last minute, as the watch on its\n machine read it from the collector itself.", + "operationId": "spin.v1.AdminService.GetTelemetryStatus", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1399,7 +1399,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetStorageSettingsRequest" + "$ref": "#/components/schemas/spin.v1.GetTelemetryStatusRequest" } } }, @@ -1421,7 +1421,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetStorageSettingsResponse" + "$ref": "#/components/schemas/spin.v1.GetTelemetryStatusResponse" } } } @@ -1429,14 +1429,14 @@ } } }, - "/spin.v1.AdminService/GetStorageUsage": { + "/spin.v1.AdminService/GetThirdPartyTerms": { "post": { "tags": [ "AdminService" ], - "summary": "GetStorageUsage", - "description": "What the store holds, by what each object is for: every volume's objects and the layers its\n commits name, with the workspace, image or extension release it is. As the storage-gc job\n last read it - daily, or when an admin runs it - never the bucket itself.", - "operationId": "spin.v1.AdminService.GetStorageUsage", + "summary": "GetThirdPartyTerms", + "description": "The software this installation downloads from its makers, the terms it is used under, and\n which of it an administrator accepted the terms for: who, when, and which text. Nothing is\n downloaded of an item whose terms are not accepted (internal/controlplane/thirdparty).", + "operationId": "spin.v1.AdminService.GetThirdPartyTerms", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1461,7 +1461,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetStorageUsageRequest" + "$ref": "#/components/schemas/spin.v1.GetThirdPartyTermsRequest" } } }, @@ -1483,7 +1483,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetStorageUsageResponse" + "$ref": "#/components/schemas/spin.v1.GetThirdPartyTermsResponse" } } } @@ -1491,14 +1491,14 @@ } } }, - "/spin.v1.AdminService/GetTelemetryStatus": { + "/spin.v1.AdminService/GetUserWorkspaceEgress": { "post": { "tags": [ "AdminService" ], - "summary": "GetTelemetryStatus", - "description": "What the collector has sent and failed to send over its last minute, as the watch on its\n machine read it from the collector itself.", - "operationId": "spin.v1.AdminService.GetTelemetryStatus", + "summary": "GetUserWorkspaceEgress", + "description": "Where a workspace sends, as its owner's GetWorkspaceEgress answers it: the host's counters by\n destination, nothing of what was carried. Audited, as a reading of somebody else's.", + "operationId": "spin.v1.AdminService.GetUserWorkspaceEgress", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1523,7 +1523,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetTelemetryStatusRequest" + "$ref": "#/components/schemas/spin.v1.GetUserWorkspaceEgressRequest" } } }, @@ -1545,7 +1545,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetTelemetryStatusResponse" + "$ref": "#/components/schemas/spin.v1.GetUserWorkspaceEgressResponse" } } } @@ -1553,14 +1553,14 @@ } } }, - "/spin.v1.AdminService/GetThirdPartyTerms": { + "/spin.v1.AdminService/GetVSCodeServers": { "post": { "tags": [ "AdminService" ], - "summary": "GetThirdPartyTerms", - "description": "The software this installation downloads from its makers, the terms it is used under, and\n which of it an administrator accepted the terms for: who, when, and which text. Nothing is\n downloaded of an item whose terms are not accepted (internal/controlplane/thirdparty).", - "operationId": "spin.v1.AdminService.GetThirdPartyTerms", + "summary": "GetVSCodeServers", + "description": "The VS Code servers workspaces are given (the vscode-server extension): each release, and\n the publish this control plane ran since it started, if it ran one.", + "operationId": "spin.v1.AdminService.GetVSCodeServers", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1585,7 +1585,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetThirdPartyTermsRequest" + "$ref": "#/components/schemas/spin.v1.GetVSCodeServersRequest" } } }, @@ -1607,7 +1607,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetThirdPartyTermsResponse" + "$ref": "#/components/schemas/spin.v1.GetVSCodeServersResponse" } } } @@ -1615,14 +1615,14 @@ } } }, - "/spin.v1.AdminService/GetVSCodeServers": { + "/spin.v1.AdminService/GetWorkspaceNames": { "post": { "tags": [ "AdminService" ], - "summary": "GetVSCodeServers", - "description": "The VS Code servers workspaces are given (the vscode-server extension): each release, and\n the publish this control plane ran since it started, if it ran one.", - "operationId": "spin.v1.AdminService.GetVSCodeServers", + "summary": "GetWorkspaceNames", + "description": "The names of the workspaces ids are, whoever owns them and whatever state they are in, a\n deleted one among them: a record of the platform's logs names a workspace by its id, and\n ListWorkspaces is the caller's own. An id the installation no longer holds a row for is\n left out of the answer.", + "operationId": "spin.v1.AdminService.GetWorkspaceNames", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1647,7 +1647,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetVSCodeServersRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceNamesRequest" } } }, @@ -1669,7 +1669,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetVSCodeServersResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceNamesResponse" } } } @@ -1677,14 +1677,14 @@ } } }, - "/spin.v1.AdminService/GetWorkspaceNames": { + "/spin.v1.AdminService/ListAlerts": { "post": { "tags": [ "AdminService" ], - "summary": "GetWorkspaceNames", - "description": "The names of the workspaces ids are, whoever owns them and whatever state they are in, a\n deleted one among them: a record of the platform's logs names a workspace by its id, and\n ListWorkspaces is the caller's own. An id the installation no longer holds a row for is\n left out of the answer.", - "operationId": "spin.v1.AdminService.GetWorkspaceNames", + "summary": "ListAlerts", + "description": "What the release's alert rules said in the installation's Grafana, newest first: each\n firing and each resolution the control plane heard, with its runbook and how many\n administrators it reached.", + "operationId": "spin.v1.AdminService.ListAlerts", "parameters": [ { "name": "Connect-Protocol-Version", @@ -1709,7 +1709,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceNamesRequest" + "$ref": "#/components/schemas/spin.v1.ListAlertsRequest" } } }, @@ -1731,7 +1731,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceNamesResponse" + "$ref": "#/components/schemas/spin.v1.ListAlertsResponse" } } } @@ -1986,14 +1986,14 @@ } } }, - "/spin.v1.AdminService/ListJobRuns": { + "/spin.v1.AdminService/ListHostProblems": { "post": { "tags": [ "AdminService" ], - "summary": "ListJobRuns", - "description": "One job's runs, newest first.", - "operationId": "spin.v1.AdminService.ListJobRuns", + "summary": "ListHostProblems", + "description": "What is wrong with the fleet's hosts, one problem per host that is not connected or fails a\n check of its own: the same problems GetInstallationHealth's HOSTS part lists, for the pages\n that read one host or the fleet and nothing else of the installation.", + "operationId": "spin.v1.AdminService.ListHostProblems", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2018,7 +2018,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListJobRunsRequest" + "$ref": "#/components/schemas/spin.v1.ListHostProblemsRequest" } } }, @@ -2040,7 +2040,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListJobRunsResponse" + "$ref": "#/components/schemas/spin.v1.ListHostProblemsResponse" } } } @@ -2048,14 +2048,14 @@ } } }, - "/spin.v1.AdminService/ListJobs": { + "/spin.v1.AdminService/ListJobRuns": { "post": { "tags": [ "AdminService" ], - "summary": "ListJobs", - "description": "The control plane's periodic work - backups, cleanups, the object store's garbage\n collection - with how often each runs and how its last run went.", - "operationId": "spin.v1.AdminService.ListJobs", + "summary": "ListJobRuns", + "description": "One job's runs, newest first.", + "operationId": "spin.v1.AdminService.ListJobRuns", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2080,7 +2080,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListJobsRequest" + "$ref": "#/components/schemas/spin.v1.ListJobRunsRequest" } } }, @@ -2102,7 +2102,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListJobsResponse" + "$ref": "#/components/schemas/spin.v1.ListJobRunsResponse" } } } @@ -2110,14 +2110,14 @@ } } }, - "/spin.v1.AdminService/ListRekeys": { + "/spin.v1.AdminService/ListJobs": { "post": { "tags": [ "AdminService" ], - "summary": "ListRekeys", - "description": "The disks being re-keyed, and the ones a revoked host's keys marked for it.", - "operationId": "spin.v1.AdminService.ListRekeys", + "summary": "ListJobs", + "description": "The control plane's periodic work - backups, cleanups, the object store's garbage\n collection - with how often each runs and how its last run went.", + "operationId": "spin.v1.AdminService.ListJobs", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2142,7 +2142,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListRekeysRequest" + "$ref": "#/components/schemas/spin.v1.ListJobsRequest" } } }, @@ -2164,7 +2164,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListRekeysResponse" + "$ref": "#/components/schemas/spin.v1.ListJobsResponse" } } } @@ -2172,14 +2172,14 @@ } } }, - "/spin.v1.AdminService/ListResourcePresets": { + "/spin.v1.AdminService/ListRefusedVolumes": { "post": { "tags": [ "AdminService" ], - "summary": "ListResourcePresets", - "description": "List resource presets", - "operationId": "spin.v1.AdminService.ListResourcePresets", + "summary": "ListRefusedVolumes", + "description": "The disks a host refuses to serve, each with the workspace it is the disk of and whose that\n is. Audited.", + "operationId": "spin.v1.AdminService.ListRefusedVolumes", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2204,7 +2204,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListResourcePresetsRequest" + "$ref": "#/components/schemas/spin.v1.ListRefusedVolumesRequest" } } }, @@ -2226,7 +2226,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListResourcePresetsResponse" + "$ref": "#/components/schemas/spin.v1.ListRefusedVolumesResponse" } } } @@ -2234,14 +2234,14 @@ } } }, - "/spin.v1.AdminService/ListRunbooks": { + "/spin.v1.AdminService/ListRekeys": { "post": { "tags": [ "AdminService" ], - "summary": "ListRunbooks", - "description": "What an operator does when an alert fires, a job fails or an owner is told something went\n wrong: spin-stack/runbooks at the commit this release pins, served so it is read in the app.", - "operationId": "spin.v1.AdminService.ListRunbooks", + "summary": "ListRekeys", + "description": "The disks being re-keyed, and the ones a revoked host's keys marked for it.", + "operationId": "spin.v1.AdminService.ListRekeys", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2266,7 +2266,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListRunbooksRequest" + "$ref": "#/components/schemas/spin.v1.ListRekeysRequest" } } }, @@ -2288,7 +2288,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListRunbooksResponse" + "$ref": "#/components/schemas/spin.v1.ListRekeysResponse" } } } @@ -2296,14 +2296,14 @@ } } }, - "/spin.v1.AdminService/ListUserAPIKeys": { + "/spin.v1.AdminService/ListResourcePresets": { "post": { "tags": [ "AdminService" ], - "summary": "ListUserAPIKeys", - "description": "List another user's API keys. The secret is never stored, so this is what an admin can\n know about a key: what it is called, when it was made, when it was last used.", - "operationId": "spin.v1.AdminService.ListUserAPIKeys", + "summary": "ListResourcePresets", + "description": "List resource presets", + "operationId": "spin.v1.AdminService.ListResourcePresets", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2328,7 +2328,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListUserAPIKeysRequest" + "$ref": "#/components/schemas/spin.v1.ListResourcePresetsRequest" } } }, @@ -2350,7 +2350,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListUserAPIKeysResponse" + "$ref": "#/components/schemas/spin.v1.ListResourcePresetsResponse" } } } @@ -2358,14 +2358,14 @@ } } }, - "/spin.v1.AdminService/ListUsers": { + "/spin.v1.AdminService/ListRunbooks": { "post": { "tags": [ "AdminService" ], - "summary": "ListUsers", - "description": "List all users", - "operationId": "spin.v1.AdminService.ListUsers", + "summary": "ListRunbooks", + "description": "What an operator does when an alert fires, a job fails or an owner is told something went\n wrong: spin-stack/runbooks at the commit this release pins, served so it is read in the app.", + "operationId": "spin.v1.AdminService.ListRunbooks", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2390,7 +2390,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListUsersRequest" + "$ref": "#/components/schemas/spin.v1.ListRunbooksRequest" } } }, @@ -2412,7 +2412,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListUsersResponse" + "$ref": "#/components/schemas/spin.v1.ListRunbooksResponse" } } } @@ -2420,14 +2420,14 @@ } } }, - "/spin.v1.AdminService/PublishVSCodeServers": { + "/spin.v1.AdminService/ListTimeline": { "post": { "tags": [ "AdminService" ], - "summary": "PublishVSCodeServers", - "description": "Publish the servers of the newest stable VS Code releases, or of the commits named, as a\n new release. Answers once the publish has begun - it downloads a gigabyte - and\n GetVSCodeServers says how it goes.", - "operationId": "spin.v1.AdminService.PublishVSCodeServers", + "summary": "ListTimeline", + "description": "What happened, newest first, between two instants, to one workspace, host, team or project, or\n to the whole installation: read from the audit log, the workspaces' events, the jobs' failed\n runs and the projects' prebuilds as they are, never copied. Sign-ins and what is done inside a\n workspace (a session, a command) are the audit log's alone: they outnumber everything else.", + "operationId": "spin.v1.AdminService.ListTimeline", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2452,7 +2452,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.PublishVSCodeServersRequest" + "$ref": "#/components/schemas/spin.v1.ListTimelineRequest" } } }, @@ -2474,7 +2474,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.PublishVSCodeServersResponse" + "$ref": "#/components/schemas/spin.v1.ListTimelineResponse" } } } @@ -2482,14 +2482,14 @@ } } }, - "/spin.v1.AdminService/RekeyVolume": { + "/spin.v1.AdminService/ListUserAPIKeys": { "post": { "tags": [ "AdminService" ], - "summary": "RekeyVolume", - "description": "Re-key a workspace's disk: copy it whole under a key of its own, point the workspace at the\n copy, and delete the disk - which shreds the key assumed taken and every snapshot of the disk,\n since those are what that key opens. The workspace must be stopped or quarantined. Refused for\n a disk something descends from. Audited.", - "operationId": "spin.v1.AdminService.RekeyVolume", + "summary": "ListUserAPIKeys", + "description": "List another user's API keys. The secret is never stored, so this is what an admin can\n know about a key: what it is called, when it was made, when it was last used.", + "operationId": "spin.v1.AdminService.ListUserAPIKeys", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2514,7 +2514,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RekeyVolumeRequest" + "$ref": "#/components/schemas/spin.v1.ListUserAPIKeysRequest" } } }, @@ -2536,7 +2536,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RekeyVolumeResponse" + "$ref": "#/components/schemas/spin.v1.ListUserAPIKeysResponse" } } } @@ -2544,14 +2544,14 @@ } } }, - "/spin.v1.AdminService/RemoveAllowedEmail": { + "/spin.v1.AdminService/ListUsers": { "post": { "tags": [ "AdminService" ], - "summary": "RemoveAllowedEmail", - "description": "Remove allowed email/domain", - "operationId": "spin.v1.AdminService.RemoveAllowedEmail", + "summary": "ListUsers", + "description": "List all users", + "operationId": "spin.v1.AdminService.ListUsers", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2576,7 +2576,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RemoveAllowedEmailRequest" + "$ref": "#/components/schemas/spin.v1.ListUsersRequest" } } }, @@ -2598,7 +2598,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RemoveAllowedEmailResponse" + "$ref": "#/components/schemas/spin.v1.ListUsersResponse" } } } @@ -2606,14 +2606,14 @@ } } }, - "/spin.v1.AdminService/RemoveStoredVolume": { + "/spin.v1.AdminService/PublishVSCodeServers": { "post": { "tags": [ "AdminService" ], - "summary": "RemoveStoredVolume", - "description": "Remove an image, an extension release, an unclaimed volume or an orphan from the store: its\n row, then its volume, whose key is shredded; storage-gc collects its layers on its next run.\n Refused for one that is kept (StoredVolume.kept_because), one placed on a host, and a\n workspace's disk. Audited.", - "operationId": "spin.v1.AdminService.RemoveStoredVolume", + "summary": "PublishVSCodeServers", + "description": "Publish the servers of the newest stable VS Code releases, or of the commits named, as a\n new release. Answers once the publish has begun - it downloads a gigabyte - and\n GetVSCodeServers says how it goes.", + "operationId": "spin.v1.AdminService.PublishVSCodeServers", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2638,7 +2638,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RemoveStoredVolumeRequest" + "$ref": "#/components/schemas/spin.v1.PublishVSCodeServersRequest" } } }, @@ -2660,7 +2660,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RemoveStoredVolumeResponse" + "$ref": "#/components/schemas/spin.v1.PublishVSCodeServersResponse" } } } @@ -2668,14 +2668,14 @@ } } }, - "/spin.v1.AdminService/RetryPlatformImages": { + "/spin.v1.AdminService/RecoverUserWorkspace": { "post": { "tags": [ "AdminService" ], - "summary": "RetryPlatformImages", - "description": "Look again now rather than when the next retry is due.", - "operationId": "spin.v1.AdminService.RetryPlatformImages", + "summary": "RecoverUserWorkspace", + "description": "Move a workspace off a host that is gone, as its owner's RecoverWorkspace does, and with the\n same question first: without accept_data_loss it answers what the move would give up and\n changes nothing. The work given up is its owner's, so a reason is required, kept on the audit\n entry, and written with who asked on the workspace's history, which its owner reads\n (WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN). Refused for a workspace held in quarantine.", + "operationId": "spin.v1.AdminService.RecoverUserWorkspace", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2700,7 +2700,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RetryPlatformImagesRequest" + "$ref": "#/components/schemas/spin.v1.RecoverUserWorkspaceRequest" } } }, @@ -2722,7 +2722,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RetryPlatformImagesResponse" + "$ref": "#/components/schemas/spin.v1.RecoverUserWorkspaceResponse" } } } @@ -2730,14 +2730,14 @@ } } }, - "/spin.v1.AdminService/RevokeThirdPartyTerms": { + "/spin.v1.AdminService/RekeyVolume": { "post": { "tags": [ "AdminService" ], - "summary": "RevokeThirdPartyTerms", - "description": "Take the acceptance back for the items named: nothing more of them is downloaded. What was\n downloaded already stays until an administrator removes it.", - "operationId": "spin.v1.AdminService.RevokeThirdPartyTerms", + "summary": "RekeyVolume", + "description": "Re-key a workspace's disk: copy it whole under a key of its own, point the workspace at the\n copy, and delete the disk - which shreds the key assumed taken and every snapshot of the disk,\n since those are what that key opens. The workspace must be stopped or quarantined. Refused for\n a disk something descends from. Audited.", + "operationId": "spin.v1.AdminService.RekeyVolume", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2762,7 +2762,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeThirdPartyTermsRequest" + "$ref": "#/components/schemas/spin.v1.RekeyVolumeRequest" } } }, @@ -2784,7 +2784,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeThirdPartyTermsResponse" + "$ref": "#/components/schemas/spin.v1.RekeyVolumeResponse" } } } @@ -2792,14 +2792,14 @@ } } }, - "/spin.v1.AdminService/RevokeUserAPIKey": { + "/spin.v1.AdminService/RemoveAllowedEmail": { "post": { "tags": [ "AdminService" ], - "summary": "RevokeUserAPIKey", - "description": "Revoke another user's API key - the offboarding an admin cannot do by asking the user,\n who may already be gone. It is audited with the admin who did it.", - "operationId": "spin.v1.AdminService.RevokeUserAPIKey", + "summary": "RemoveAllowedEmail", + "description": "Remove allowed email/domain", + "operationId": "spin.v1.AdminService.RemoveAllowedEmail", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2824,7 +2824,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeUserAPIKeyRequest" + "$ref": "#/components/schemas/spin.v1.RemoveAllowedEmailRequest" } } }, @@ -2846,7 +2846,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeUserAPIKeyResponse" + "$ref": "#/components/schemas/spin.v1.RemoveAllowedEmailResponse" } } } @@ -2854,14 +2854,14 @@ } } }, - "/spin.v1.AdminService/RunJobNow": { + "/spin.v1.AdminService/RemoveStoredVolume": { "post": { "tags": [ "AdminService" ], - "summary": "RunJobNow", - "description": "Run a job now rather than when it is next due. Refused while it is running: a second run\n alongside the first would do the same work twice. Audited with the admin who asked.", - "operationId": "spin.v1.AdminService.RunJobNow", + "summary": "RemoveStoredVolume", + "description": "Remove an image, an extension release, an unclaimed volume or an orphan from the store: its\n row, then its volume, whose key is shredded; storage-gc collects its layers on its next run.\n Refused for one that is kept (StoredVolume.kept_because), one placed on a host, and a\n workspace's disk. Audited.", + "operationId": "spin.v1.AdminService.RemoveStoredVolume", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2886,7 +2886,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RunJobNowRequest" + "$ref": "#/components/schemas/spin.v1.RemoveStoredVolumeRequest" } } }, @@ -2908,7 +2908,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RunJobNowResponse" + "$ref": "#/components/schemas/spin.v1.RemoveStoredVolumeResponse" } } } @@ -2916,14 +2916,14 @@ } } }, - "/spin.v1.AdminService/SetRegistrationMode": { + "/spin.v1.AdminService/RetryPlatformImages": { "post": { "tags": [ "AdminService" ], - "summary": "SetRegistrationMode", - "description": "Set registration mode", - "operationId": "spin.v1.AdminService.SetRegistrationMode", + "summary": "RetryPlatformImages", + "description": "Look again now rather than when the next retry is due.", + "operationId": "spin.v1.AdminService.RetryPlatformImages", "parameters": [ { "name": "Connect-Protocol-Version", @@ -2948,7 +2948,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.SetRegistrationModeRequest" + "$ref": "#/components/schemas/spin.v1.RetryPlatformImagesRequest" } } }, @@ -2970,7 +2970,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.SetRegistrationModeResponse" + "$ref": "#/components/schemas/spin.v1.RetryPlatformImagesResponse" } } } @@ -2978,14 +2978,14 @@ } } }, - "/spin.v1.AdminService/UntagImage": { + "/spin.v1.AdminService/RevokeThirdPartyTerms": { "post": { "tags": [ "AdminService" ], - "summary": "UntagImage", - "description": "Take a ref off what it names: no new workspace gets those bytes by it, the disks cloned from\n them go on reading them, and a later publish under the ref tags the new bytes. Audited.", - "operationId": "spin.v1.AdminService.UntagImage", + "summary": "RevokeThirdPartyTerms", + "description": "Take the acceptance back for the items named: nothing more of them is downloaded. What was\n downloaded already stays until an administrator removes it.", + "operationId": "spin.v1.AdminService.RevokeThirdPartyTerms", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3010,7 +3010,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UntagImageRequest" + "$ref": "#/components/schemas/spin.v1.RevokeThirdPartyTermsRequest" } } }, @@ -3032,7 +3032,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UntagImageResponse" + "$ref": "#/components/schemas/spin.v1.RevokeThirdPartyTermsResponse" } } } @@ -3040,14 +3040,14 @@ } } }, - "/spin.v1.AdminService/UpdateEmailSettings": { + "/spin.v1.AdminService/RevokeUserAPIKey": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateEmailSettings", - "description": "Set the SMTP server, checked against it - connected to and signed in to - before it is\n written. An empty host turns email off.", - "operationId": "spin.v1.AdminService.UpdateEmailSettings", + "summary": "RevokeUserAPIKey", + "description": "Revoke another user's API key - the offboarding an admin cannot do by asking the user,\n who may already be gone. It is audited with the admin who did it.", + "operationId": "spin.v1.AdminService.RevokeUserAPIKey", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3072,7 +3072,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateEmailSettingsRequest" + "$ref": "#/components/schemas/spin.v1.RevokeUserAPIKeyRequest" } } }, @@ -3094,7 +3094,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateEmailSettingsResponse" + "$ref": "#/components/schemas/spin.v1.RevokeUserAPIKeyResponse" } } } @@ -3102,13 +3102,14 @@ } } }, - "/spin.v1.AdminService/UpdateHostClass": { + "/spin.v1.AdminService/RunJobNow": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateHostClass", - "operationId": "spin.v1.AdminService.UpdateHostClass", + "summary": "RunJobNow", + "description": "Run a job now rather than when it is next due. Refused while it is running: a second run\n alongside the first would do the same work twice. Audited with the admin who asked.", + "operationId": "spin.v1.AdminService.RunJobNow", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3133,7 +3134,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateHostClassRequest" + "$ref": "#/components/schemas/spin.v1.RunJobNowRequest" } } }, @@ -3155,7 +3156,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateHostClassResponse" + "$ref": "#/components/schemas/spin.v1.RunJobNowResponse" } } } @@ -3163,14 +3164,14 @@ } } }, - "/spin.v1.AdminService/UpdateInstallationSettings": { + "/spin.v1.AdminService/SendTestAlert": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateInstallationSettings", - "description": "Update installation settings", - "operationId": "spin.v1.AdminService.UpdateInstallationSettings", + "summary": "SendTestAlert", + "description": "Has Grafana test its contact point, which sends a test alert the whole way an alert goes:\n Grafana, the control plane, an email to every administrator who has not opted out.\n FAILED_PRECONDITION where this control plane serves no Grafana, or Grafana could not\n reach the control plane.", + "operationId": "spin.v1.AdminService.SendTestAlert", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3195,7 +3196,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateInstallationSettingsRequest" + "$ref": "#/components/schemas/spin.v1.SendTestAlertRequest" } } }, @@ -3217,7 +3218,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateInstallationSettingsResponse" + "$ref": "#/components/schemas/spin.v1.SendTestAlertResponse" } } } @@ -3225,13 +3226,14 @@ } } }, - "/spin.v1.AdminService/UpdateNetworkPolicy": { + "/spin.v1.AdminService/SetSettings": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateNetworkPolicy", - "operationId": "spin.v1.AdminService.UpdateNetworkPolicy", + "summary": "SetSettings", + "description": "Writes the settings named, each checked by its definition and all of them together before\n any is written: a refused one writes nothing.", + "operationId": "spin.v1.AdminService.SetSettings", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3256,7 +3258,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateNetworkPolicyRequest" + "$ref": "#/components/schemas/spin.v1.SetSettingsRequest" } } }, @@ -3278,7 +3280,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateNetworkPolicyResponse" + "$ref": "#/components/schemas/spin.v1.SetSettingsResponse" } } } @@ -3286,14 +3288,14 @@ } } }, - "/spin.v1.AdminService/UpdateResourcePreset": { + "/spin.v1.AdminService/StopUserWorkspace": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateResourcePreset", - "description": "Update a resource preset", - "operationId": "spin.v1.AdminService.UpdateResourcePreset", + "summary": "StopUserWorkspace", + "description": "Stop a workspace, as its owner's StopWorkspace does: one whose stop could not publish its\n disk runs on and holds its host until somebody stops it. Audited.", + "operationId": "spin.v1.AdminService.StopUserWorkspace", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3318,7 +3320,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateResourcePresetRequest" + "$ref": "#/components/schemas/spin.v1.StopUserWorkspaceRequest" } } }, @@ -3340,7 +3342,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateResourcePresetResponse" + "$ref": "#/components/schemas/spin.v1.StopUserWorkspaceResponse" } } } @@ -3348,14 +3350,14 @@ } } }, - "/spin.v1.AdminService/UpdateSecuritySettings": { + "/spin.v1.AdminService/UntagImage": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateSecuritySettings", - "description": "Update security settings", - "operationId": "spin.v1.AdminService.UpdateSecuritySettings", + "summary": "UntagImage", + "description": "Take a ref off what it names: no new workspace gets those bytes by it, the disks cloned from\n them go on reading them, and a later publish under the ref tags the new bytes. Audited.", + "operationId": "spin.v1.AdminService.UntagImage", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3380,7 +3382,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateSecuritySettingsRequest" + "$ref": "#/components/schemas/spin.v1.UntagImageRequest" } } }, @@ -3402,7 +3404,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateSecuritySettingsResponse" + "$ref": "#/components/schemas/spin.v1.UntagImageResponse" } } } @@ -3410,14 +3412,14 @@ } } }, - "/spin.v1.AdminService/UpdateStorageSettings": { + "/spin.v1.AdminService/UpdateEmailSettings": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateStorageSettings", - "description": "Set where the object store is, checked against it before it is written; every control\n plane restarts within seconds to open it. Another store while volumes exist is refused.", - "operationId": "spin.v1.AdminService.UpdateStorageSettings", + "summary": "UpdateEmailSettings", + "description": "Set the SMTP server, checked against it - connected to and signed in to - before it is\n written. An empty host turns email off.", + "operationId": "spin.v1.AdminService.UpdateEmailSettings", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3442,7 +3444,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateStorageSettingsRequest" + "$ref": "#/components/schemas/spin.v1.UpdateEmailSettingsRequest" } } }, @@ -3464,7 +3466,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateStorageSettingsResponse" + "$ref": "#/components/schemas/spin.v1.UpdateEmailSettingsResponse" } } } @@ -3472,14 +3474,13 @@ } } }, - "/spin.v1.AdminService/UpdateUser": { + "/spin.v1.AdminService/UpdateHostClass": { "post": { "tags": [ "AdminService" ], - "summary": "UpdateUser", - "description": "Update user (can toggle is_instance_admin, disable user)", - "operationId": "spin.v1.AdminService.UpdateUser", + "summary": "UpdateHostClass", + "operationId": "spin.v1.AdminService.UpdateHostClass", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3504,7 +3505,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateUserRequest" + "$ref": "#/components/schemas/spin.v1.UpdateHostClassRequest" } } }, @@ -3526,7 +3527,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.UpdateUserResponse" + "$ref": "#/components/schemas/spin.v1.UpdateHostClassResponse" } } } @@ -3534,13 +3535,13 @@ } } }, - "/spin.v1.AuthService/ApproveWorkspaceSignIn": { + "/spin.v1.AdminService/UpdateNetworkPolicy": { "post": { "tags": [ - "AuthService" + "AdminService" ], - "summary": "ApproveWorkspaceSignIn", - "operationId": "spin.v1.AuthService.ApproveWorkspaceSignIn", + "summary": "UpdateNetworkPolicy", + "operationId": "spin.v1.AdminService.UpdateNetworkPolicy", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3565,7 +3566,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ApproveWorkspaceSignInRequest" + "$ref": "#/components/schemas/spin.v1.UpdateNetworkPolicyRequest" } } }, @@ -3587,7 +3588,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ApproveWorkspaceSignInResponse" + "$ref": "#/components/schemas/spin.v1.UpdateNetworkPolicyResponse" } } } @@ -3595,14 +3596,14 @@ } } }, - "/spin.v1.AuthService/BeginWorkspaceSignIn": { + "/spin.v1.AdminService/UpdateResourcePreset": { "post": { "tags": [ - "AuthService" + "AdminService" ], - "summary": "BeginWorkspaceSignIn", - "description": "A sign-in on a workspace's host, done by the dashboard. The proxy begins it and sends the\n browser to the dashboard, which approves it for whoever is signed in there and sends the\n browser back with a one-time code; the proxy completes it with that code and the binding\n it gave the same browser. The identity provider is only ever told the dashboard's callback.", - "operationId": "spin.v1.AuthService.BeginWorkspaceSignIn", + "summary": "UpdateResourcePreset", + "description": "Update a resource preset", + "operationId": "spin.v1.AdminService.UpdateResourcePreset", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3627,7 +3628,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.BeginWorkspaceSignInRequest" + "$ref": "#/components/schemas/spin.v1.UpdateResourcePresetRequest" } } }, @@ -3649,7 +3650,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.BeginWorkspaceSignInResponse" + "$ref": "#/components/schemas/spin.v1.UpdateResourcePresetResponse" } } } @@ -3657,14 +3658,14 @@ } } }, - "/spin.v1.AuthService/CheckDeviceLogin": { + "/spin.v1.AdminService/UpdateStorageSettings": { "post": { "tags": [ - "AuthService" + "AdminService" ], - "summary": "CheckDeviceLogin", - "description": "Check device login status (CLI polls this)\n Returns session token once user has completed login and entered the code", - "operationId": "spin.v1.AuthService.CheckDeviceLogin", + "summary": "UpdateStorageSettings", + "description": "Set where the object store is, checked against it before it is written; every control\n plane restarts within seconds to open it. Another store while volumes exist is refused.", + "operationId": "spin.v1.AdminService.UpdateStorageSettings", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3689,7 +3690,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CheckDeviceLoginRequest" + "$ref": "#/components/schemas/spin.v1.UpdateStorageSettingsRequest" } } }, @@ -3711,7 +3712,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CheckDeviceLoginResponse" + "$ref": "#/components/schemas/spin.v1.UpdateStorageSettingsResponse" } } } @@ -3719,14 +3720,14 @@ } } }, - "/spin.v1.AuthService/CompleteLogin": { + "/spin.v1.AdminService/UpdateUser": { "post": { "tags": [ - "AuthService" + "AdminService" ], - "summary": "CompleteLogin", - "description": "Complete login (callback from IdP)", - "operationId": "spin.v1.AuthService.CompleteLogin", + "summary": "UpdateUser", + "description": "Update user (can toggle is_instance_admin, disable user)", + "operationId": "spin.v1.AdminService.UpdateUser", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3751,7 +3752,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CompleteLoginRequest" + "$ref": "#/components/schemas/spin.v1.UpdateUserRequest" } } }, @@ -3773,7 +3774,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CompleteLoginResponse" + "$ref": "#/components/schemas/spin.v1.UpdateUserResponse" } } } @@ -3781,13 +3782,13 @@ } } }, - "/spin.v1.AuthService/CompleteWorkspaceSignIn": { + "/spin.v1.AuthService/ApproveWorkspaceSignIn": { "post": { "tags": [ "AuthService" ], - "summary": "CompleteWorkspaceSignIn", - "operationId": "spin.v1.AuthService.CompleteWorkspaceSignIn", + "summary": "ApproveWorkspaceSignIn", + "operationId": "spin.v1.AuthService.ApproveWorkspaceSignIn", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3812,7 +3813,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CompleteWorkspaceSignInRequest" + "$ref": "#/components/schemas/spin.v1.ApproveWorkspaceSignInRequest" } } }, @@ -3834,7 +3835,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CompleteWorkspaceSignInResponse" + "$ref": "#/components/schemas/spin.v1.ApproveWorkspaceSignInResponse" } } } @@ -3842,14 +3843,14 @@ } } }, - "/spin.v1.AuthService/ConfirmDeviceLogin": { + "/spin.v1.AuthService/BeginWorkspaceSignIn": { "post": { "tags": [ "AuthService" ], - "summary": "ConfirmDeviceLogin", - "description": "Complete device login (dashboard calls this after user enters code)\n Requires authentication - links the device code to the current user", - "operationId": "spin.v1.AuthService.ConfirmDeviceLogin", + "summary": "BeginWorkspaceSignIn", + "description": "A sign-in on a workspace's host, done by the dashboard. The proxy begins it and sends the\n browser to the dashboard, which approves it for whoever is signed in there and sends the\n browser back with a one-time code; the proxy completes it with that code and the binding\n it gave the same browser. The identity provider is only ever told the dashboard's callback.", + "operationId": "spin.v1.AuthService.BeginWorkspaceSignIn", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3874,7 +3875,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ConfirmDeviceLoginRequest" + "$ref": "#/components/schemas/spin.v1.BeginWorkspaceSignInRequest" } } }, @@ -3896,7 +3897,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ConfirmDeviceLoginResponse" + "$ref": "#/components/schemas/spin.v1.BeginWorkspaceSignInResponse" } } } @@ -3904,14 +3905,14 @@ } } }, - "/spin.v1.AuthService/CreateAPIKey": { + "/spin.v1.AuthService/CheckDeviceLogin": { "post": { "tags": [ "AuthService" ], - "summary": "CreateAPIKey", - "description": "Create API key", - "operationId": "spin.v1.AuthService.CreateAPIKey", + "summary": "CheckDeviceLogin", + "description": "Check device login status (CLI polls this)\n Returns session token once user has completed login and entered the code", + "operationId": "spin.v1.AuthService.CheckDeviceLogin", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3936,7 +3937,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CreateAPIKeyRequest" + "$ref": "#/components/schemas/spin.v1.CheckDeviceLoginRequest" } } }, @@ -3958,7 +3959,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.CreateAPIKeyResponse" + "$ref": "#/components/schemas/spin.v1.CheckDeviceLoginResponse" } } } @@ -3966,14 +3967,14 @@ } } }, - "/spin.v1.AuthService/ExchangeWorkloadToken": { + "/spin.v1.AuthService/CompleteLogin": { "post": { "tags": [ "AuthService" ], - "summary": "ExchangeWorkloadToken", - "description": "Exchanges a workload's JWT-SVID, for the control plane's audience (identity.ControlPlaneAudience),\n for a key delegated from the key that made its workspace: what an agent in a workspace drives\n others with. Verified by go-spiffe against the trust domain's bundle; the key holds no more than\n its parent (delegation.Within), lasts no longer than the token's workload needs, and goes with the\n workspace.", - "operationId": "spin.v1.AuthService.ExchangeWorkloadToken", + "summary": "CompleteLogin", + "description": "Complete login (callback from IdP)", + "operationId": "spin.v1.AuthService.CompleteLogin", "parameters": [ { "name": "Connect-Protocol-Version", @@ -3998,7 +3999,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ExchangeWorkloadTokenRequest" + "$ref": "#/components/schemas/spin.v1.CompleteLoginRequest" } } }, @@ -4020,7 +4021,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ExchangeWorkloadTokenResponse" + "$ref": "#/components/schemas/spin.v1.CompleteLoginResponse" } } } @@ -4028,14 +4029,13 @@ } } }, - "/spin.v1.AuthService/GetCurrentUser": { + "/spin.v1.AuthService/CompleteWorkspaceSignIn": { "post": { "tags": [ "AuthService" ], - "summary": "GetCurrentUser", - "description": "Get current user info", - "operationId": "spin.v1.AuthService.GetCurrentUser", + "summary": "CompleteWorkspaceSignIn", + "operationId": "spin.v1.AuthService.CompleteWorkspaceSignIn", "parameters": [ { "name": "Connect-Protocol-Version", @@ -4060,7 +4060,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetCurrentUserRequest" + "$ref": "#/components/schemas/spin.v1.CompleteWorkspaceSignInRequest" } } }, @@ -4082,7 +4082,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetCurrentUserResponse" + "$ref": "#/components/schemas/spin.v1.CompleteWorkspaceSignInResponse" } } } @@ -4090,14 +4090,14 @@ } } }, - "/spin.v1.AuthService/ListAPIKeys": { + "/spin.v1.AuthService/ConfirmDeviceLogin": { "post": { "tags": [ "AuthService" ], - "summary": "ListAPIKeys", - "description": "List API keys", - "operationId": "spin.v1.AuthService.ListAPIKeys", + "summary": "ConfirmDeviceLogin", + "description": "Complete device login (dashboard calls this after user enters code)\n Requires authentication - links the device code to the current user", + "operationId": "spin.v1.AuthService.ConfirmDeviceLogin", "parameters": [ { "name": "Connect-Protocol-Version", @@ -4122,7 +4122,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListAPIKeysRequest" + "$ref": "#/components/schemas/spin.v1.ConfirmDeviceLoginRequest" } } }, @@ -4144,7 +4144,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListAPIKeysResponse" + "$ref": "#/components/schemas/spin.v1.ConfirmDeviceLoginResponse" } } } @@ -4152,14 +4152,14 @@ } } }, - "/spin.v1.AuthService/ListIdentityProviders": { + "/spin.v1.AuthService/CreateAPIKey": { "post": { "tags": [ "AuthService" ], - "summary": "ListIdentityProviders", - "description": "List available identity providers (for login page)\n Always includes built-in GitHub provider; additional OIDC providers from database", - "operationId": "spin.v1.AuthService.ListIdentityProviders", + "summary": "CreateAPIKey", + "description": "Create API key", + "operationId": "spin.v1.AuthService.CreateAPIKey", "parameters": [ { "name": "Connect-Protocol-Version", @@ -4184,7 +4184,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListIdentityProvidersRequest" + "$ref": "#/components/schemas/spin.v1.CreateAPIKeyRequest" } } }, @@ -4206,7 +4206,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListIdentityProvidersResponse" + "$ref": "#/components/schemas/spin.v1.CreateAPIKeyResponse" } } } @@ -4214,14 +4214,14 @@ } } }, - "/spin.v1.AuthService/Logout": { + "/spin.v1.AuthService/ExchangeWorkloadToken": { "post": { "tags": [ "AuthService" ], - "summary": "Logout", - "description": "Logout (invalidate session)", - "operationId": "spin.v1.AuthService.Logout", + "summary": "ExchangeWorkloadToken", + "description": "Exchanges a workload's JWT-SVID, for the control plane's audience (identity.ControlPlaneAudience),\n for a key delegated from the key that made its workspace: what an agent in a workspace drives\n others with. Verified by go-spiffe against the trust domain's bundle; the key holds no more than\n its parent (delegation.Within), lasts no longer than the token's workload needs, and goes with the\n workspace.", + "operationId": "spin.v1.AuthService.ExchangeWorkloadToken", "parameters": [ { "name": "Connect-Protocol-Version", @@ -4246,7 +4246,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.LogoutRequest" + "$ref": "#/components/schemas/spin.v1.ExchangeWorkloadTokenRequest" } } }, @@ -4268,7 +4268,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.LogoutResponse" + "$ref": "#/components/schemas/spin.v1.ExchangeWorkloadTokenResponse" } } } @@ -4276,14 +4276,14 @@ } } }, - "/spin.v1.AuthService/RevokeAPIKey": { + "/spin.v1.AuthService/GetCurrentUser": { "post": { "tags": [ "AuthService" ], - "summary": "RevokeAPIKey", - "description": "Revoke API key", - "operationId": "spin.v1.AuthService.RevokeAPIKey", + "summary": "GetCurrentUser", + "description": "Get current user info", + "operationId": "spin.v1.AuthService.GetCurrentUser", "parameters": [ { "name": "Connect-Protocol-Version", @@ -4308,7 +4308,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeAPIKeyRequest" + "$ref": "#/components/schemas/spin.v1.GetCurrentUserRequest" } } }, @@ -4330,7 +4330,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.RevokeAPIKeyResponse" + "$ref": "#/components/schemas/spin.v1.GetCurrentUserResponse" } } } @@ -4338,14 +4338,262 @@ } } }, - "/spin.v1.AuthService/SearchUsers": { + "/spin.v1.AuthService/ListAPIKeys": { "post": { "tags": [ "AuthService" ], - "summary": "SearchUsers", - "description": "Search users by name or email (for team member selection, workspace sharing)", - "operationId": "spin.v1.AuthService.SearchUsers", + "summary": "ListAPIKeys", + "description": "List API keys", + "operationId": "spin.v1.AuthService.ListAPIKeys", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListAPIKeysRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListAPIKeysResponse" + } + } + } + } + } + } + }, + "/spin.v1.AuthService/ListIdentityProviders": { + "post": { + "tags": [ + "AuthService" + ], + "summary": "ListIdentityProviders", + "description": "List available identity providers (for login page)\n Always includes built-in GitHub provider; additional OIDC providers from database", + "operationId": "spin.v1.AuthService.ListIdentityProviders", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListIdentityProvidersRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListIdentityProvidersResponse" + } + } + } + } + } + } + }, + "/spin.v1.AuthService/Logout": { + "post": { + "tags": [ + "AuthService" + ], + "summary": "Logout", + "description": "Logout (invalidate session)", + "operationId": "spin.v1.AuthService.Logout", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.LogoutRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.LogoutResponse" + } + } + } + } + } + } + }, + "/spin.v1.AuthService/RevokeAPIKey": { + "post": { + "tags": [ + "AuthService" + ], + "summary": "RevokeAPIKey", + "description": "Revoke API key", + "operationId": "spin.v1.AuthService.RevokeAPIKey", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.RevokeAPIKeyRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.RevokeAPIKeyResponse" + } + } + } + } + } + } + }, + "/spin.v1.AuthService/SearchUsers": { + "post": { + "tags": [ + "AuthService" + ], + "summary": "SearchUsers", + "description": "Search users by name or email (for team member selection, workspace sharing)", + "operationId": "spin.v1.AuthService.SearchUsers", "parameters": [ { "name": "Connect-Protocol-Version", @@ -5828,7 +6076,7 @@ "HostService" ], "summary": "RemoveHost", - "description": "Takes a machine of an autoscaling group out of the fleet (admin only), as Host.removal says:\n given back to its group (GIVE_BACK), or forgotten (FORGET). A host whose removal is REVOKE is\n refused: RevokeRunner is its removal.", + "description": "Takes a machine of an autoscaling group out of the fleet (admin only), as Host.removal says:\n given back to its group (GIVE_BACK), or forgotten (FORGET). A host whose removal is REVOKE is\n refused: RevokeRunner is its removal. Asked with taken, the host leaves as one believed\n compromised: its credentials revoked and every team's disk whose key it was given marked for\n re-keying (ListRekeys), as RevokeRunner does, and its machine given back all the same.", "operationId": "spin.v1.HostService.RemoveHost", "parameters": [ { @@ -6565,14 +6813,14 @@ } } }, - "/spin.v1.LogService/ListLogIndexes": { + "/spin.v1.LogService/KeptTraces": { "post": { "tags": [ "LogService" ], - "summary": "ListLogIndexes", - "description": "ListLogIndexes is every index the store keeps, as the store itself reports it: its fields,\n how long it keeps what it holds and how much it holds. An administrator's: how much each\n index holds is how busy the installation and its workspaces are.", - "operationId": "spin.v1.LogService.ListLogIndexes", + "summary": "KeptTraces", + "description": "KeptTraces is which of the trace ids a page shows the store holds: those, and only those,\n are links. A trace id is written where its span began, before the collector decides whether\n to keep the trace, and it stays after the store's retention has deleted it.", + "operationId": "spin.v1.LogService.KeptTraces", "parameters": [ { "name": "Connect-Protocol-Version", @@ -6597,7 +6845,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListLogIndexesRequest" + "$ref": "#/components/schemas/spin.v1.KeptTracesRequest" } } }, @@ -6619,7 +6867,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.ListLogIndexesResponse" + "$ref": "#/components/schemas/spin.v1.KeptTracesResponse" } } } @@ -6627,7 +6875,69 @@ } } }, - "/spin.v1.LogService/PlatformLogFieldValues": { + "/spin.v1.LogService/ListLogIndexes": { + "post": { + "tags": [ + "LogService" + ], + "summary": "ListLogIndexes", + "description": "ListLogIndexes is every index the store keeps, as the store itself reports it: its fields,\n how long it keeps what it holds and how much it holds. An administrator's: how much each\n index holds is how busy the installation and its workspaces are.", + "operationId": "spin.v1.LogService.ListLogIndexes", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListLogIndexesRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListLogIndexesResponse" + } + } + } + } + } + } + }, + "/spin.v1.LogService/PlatformLogFieldValues": { "post": { "tags": [ "LogService" @@ -7553,6 +7863,68 @@ } } }, + "/spin.v1.ProjectService/GetProjectCosts": { + "post": { + "tags": [ + "ProjectService" + ], + "summary": "GetProjectCosts", + "description": "What a project cost in the month in progress, to its admins and nobody else, as\n ListProjectWorkspaces: everybody's workspaces of it summed, its prebuilds' builds included,\n and a month of what its prebuilds keep. A sum, never by workspace: one workspace's cost says\n how much its owner works, which a project's admin who is not their team's is not told. Read\n from the bill as the installation last read it, never from the cloud.", + "operationId": "spin.v1.ProjectService.GetProjectCosts", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GetProjectCostsRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GetProjectCostsResponse" + } + } + } + } + } + } + }, "/spin.v1.ProjectService/ListPrebuilds": { "post": { "tags": [ @@ -8362,7 +8734,7 @@ "SettingsService" ], "summary": "GetNotificationPreferences", - "description": "Which of those the caller is emailed about.", + "description": "Which of those the caller is emailed about, and for an administrator which of the\n installation's events besides.", "operationId": "spin.v1.SettingsService.GetNotificationPreferences", "parameters": [ { @@ -9961,6 +10333,68 @@ } } }, + "/spin.v1.TeamService/GetTeamCosts": { + "post": { + "tags": [ + "TeamService" + ], + "summary": "GetTeamCosts", + "description": "What the team's workspaces cost in the month in progress, each and summed, for the team's\n admins: a member reads their own workspace's (WorkspaceService.GetWorkspaceCost) and nobody\n else's. A workspace is the team's whose it was on the last day it cost anything; a team below\n this one is not counted in it.", + "operationId": "spin.v1.TeamService.GetTeamCosts", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GetTeamCostsRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GetTeamCostsResponse" + } + } + } + } + } + } + }, "/spin.v1.TeamService/ListIdentityProviders": { "post": { "tags": [ @@ -11445,14 +11879,14 @@ } } }, - "/spin.v1.WorkspaceService/GetWorkspaceEgress": { + "/spin.v1.WorkspaceService/GetWorkspaceCost": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GetWorkspaceEgress", - "description": "What a running workspace has actually reached beyond the node.", - "operationId": "spin.v1.WorkspaceService.GetWorkspaceEgress", + "summary": "GetWorkspaceCost", + "description": "What the workspace cost in the month in progress and what it costs while it runs, for whoever\n reads the workspace itself: its owner, and a guest its owner let in. Nobody else is answered -\n one workspace's cost says how much another person works. Read from the bill as the\n installation last read it, never from the cloud.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceCost", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11477,7 +11911,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceEgressRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceCostRequest" } } }, @@ -11499,7 +11933,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceEgressResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceCostResponse" } } } @@ -11507,14 +11941,14 @@ } } }, - "/spin.v1.WorkspaceService/GetWorkspaceHistory": { + "/spin.v1.WorkspaceService/GetWorkspaceDeletion": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GetWorkspaceHistory", - "description": "Get workspace history (snapshots/state changes timeline)", - "operationId": "spin.v1.WorkspaceService.GetWorkspaceHistory", + "summary": "GetWorkspaceDeletion", + "description": "What deleting a workspace would do, asked before it is done: what goes to the trash and for\n how long, what is erased at once, and what outlives it. It changes nothing.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceDeletion", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11539,7 +11973,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceHistoryRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceDeletionRequest" } } }, @@ -11561,7 +11995,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceHistoryResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceDeletionResponse" } } } @@ -11569,14 +12003,14 @@ } } }, - "/spin.v1.WorkspaceService/GetWorkspaceIdentity": { + "/spin.v1.WorkspaceService/GetWorkspaceEgress": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GetWorkspaceIdentity", - "description": "Who the workspace is outside spin: the identity its tokens and signatures carry, the issuer a\n relying party trusts it by, and what it was cloned from.", - "operationId": "spin.v1.WorkspaceService.GetWorkspaceIdentity", + "summary": "GetWorkspaceEgress", + "description": "What a running workspace has actually reached beyond the node.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceEgress", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11601,7 +12035,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceIdentityRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceEgressRequest" } } }, @@ -11623,7 +12057,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceIdentityResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceEgressResponse" } } } @@ -11631,14 +12065,14 @@ } } }, - "/spin.v1.WorkspaceService/GetWorkspaceStatus": { + "/spin.v1.WorkspaceService/GetWorkspaceHistory": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GetWorkspaceStatus", - "description": "What every party believes about one workspace, and where they disagree.", - "operationId": "spin.v1.WorkspaceService.GetWorkspaceStatus", + "summary": "GetWorkspaceHistory", + "description": "Get workspace history (snapshots/state changes timeline)", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceHistory", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11663,7 +12097,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceStatusRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceHistoryRequest" } } }, @@ -11685,7 +12119,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceStatusResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceHistoryResponse" } } } @@ -11693,14 +12127,14 @@ } } }, - "/spin.v1.WorkspaceService/GetWorkspaceUsage": { + "/spin.v1.WorkspaceService/GetWorkspaceIdentity": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GetWorkspaceUsage", - "description": "What the workspace's machine used of what it held, over the last hour at most, as its host\n sampled it: a running machine's up to now, a stopped or failed one's up to when it ended.\n The samples live on that host, so the series begins where the machine began there, and goes\n with a restart of the host, a move, the workspace's next start or its removal.", - "operationId": "spin.v1.WorkspaceService.GetWorkspaceUsage", + "summary": "GetWorkspaceIdentity", + "description": "Who the workspace is outside spin: the identity its tokens and signatures carry, the issuer a\n relying party trusts it by, and what it was cloned from.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceIdentity", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11725,7 +12159,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceUsageRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceIdentityRequest" } } }, @@ -11747,7 +12181,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GetWorkspaceUsageResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceIdentityResponse" } } } @@ -11755,13 +12189,14 @@ } } }, - "/spin.v1.WorkspaceService/GrantPortAccess": { + "/spin.v1.WorkspaceService/GetWorkspaceStatus": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GrantPortAccess", - "operationId": "spin.v1.WorkspaceService.GrantPortAccess", + "summary": "GetWorkspaceStatus", + "description": "What every party believes about one workspace, and where they disagree.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceStatus", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11786,7 +12221,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GrantPortAccessRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceStatusRequest" } } }, @@ -11808,7 +12243,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GrantPortAccessResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceStatusResponse" } } } @@ -11816,14 +12251,14 @@ } } }, - "/spin.v1.WorkspaceService/GrantWorkspaceResources": { + "/spin.v1.WorkspaceService/GetWorkspaceUsage": { "post": { "tags": [ "WorkspaceService" ], - "summary": "GrantWorkspaceResources", - "description": "Give a workspace more cores and memory of its own, up to its preset's ceiling: its host\n reserves them and its machine is grown while it runs. The grant lasts until it is released\n or the workspace is deleted.", - "operationId": "spin.v1.WorkspaceService.GrantWorkspaceResources", + "summary": "GetWorkspaceUsage", + "description": "What the workspace's machine used of what it held, over the last hour at most, as its host\n sampled it: a running machine's up to now, a stopped or failed one's up to when it ended.\n The samples live on that host, so the series begins where the machine began there, and goes\n with a restart of the host, a move, the workspace's next start or its removal.", + "operationId": "spin.v1.WorkspaceService.GetWorkspaceUsage", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11848,7 +12283,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GrantWorkspaceResourcesRequest" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceUsageRequest" } } }, @@ -11870,7 +12305,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.GrantWorkspaceResourcesResponse" + "$ref": "#/components/schemas/spin.v1.GetWorkspaceUsageResponse" } } } @@ -11878,14 +12313,13 @@ } } }, - "/spin.v1.WorkspaceService/KeepWorkspace": { + "/spin.v1.WorkspaceService/GrantPortAccess": { "post": { "tags": [ "WorkspaceService" ], - "summary": "KeepWorkspace", - "description": "Keep a running workspace awake for a while: neither the idle suspension nor the running cap\n fires before kept_until, and the question asked before one (suspends_at) is withdrawn. An\n answer with an end, never permanent: at most a day at a time (MaxKeepMinutes).", - "operationId": "spin.v1.WorkspaceService.KeepWorkspace", + "summary": "GrantPortAccess", + "operationId": "spin.v1.WorkspaceService.GrantPortAccess", "parameters": [ { "name": "Connect-Protocol-Version", @@ -11910,7 +12344,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.KeepWorkspaceRequest" + "$ref": "#/components/schemas/spin.v1.GrantPortAccessRequest" } } }, @@ -11932,7 +12366,131 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/spin.v1.KeepWorkspaceResponse" + "$ref": "#/components/schemas/spin.v1.GrantPortAccessResponse" + } + } + } + } + } + } + }, + "/spin.v1.WorkspaceService/GrantWorkspaceResources": { + "post": { + "tags": [ + "WorkspaceService" + ], + "summary": "GrantWorkspaceResources", + "description": "Give a workspace more cores and memory of its own, up to its preset's ceiling: its host\n reserves them and its machine is grown while it runs. The grant lasts until it is released\n or the workspace is deleted.", + "operationId": "spin.v1.WorkspaceService.GrantWorkspaceResources", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GrantWorkspaceResourcesRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.GrantWorkspaceResourcesResponse" + } + } + } + } + } + } + }, + "/spin.v1.WorkspaceService/KeepWorkspace": { + "post": { + "tags": [ + "WorkspaceService" + ], + "summary": "KeepWorkspace", + "description": "Keep a running workspace awake for a while: neither the idle suspension nor the running cap\n fires before kept_until, and the question asked before one (suspends_at) is withdrawn. An\n answer with an end, never permanent: at most a day at a time (MaxKeepMinutes).", + "operationId": "spin.v1.WorkspaceService.KeepWorkspace", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.KeepWorkspaceRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.KeepWorkspaceResponse" } } } @@ -12311,6 +12869,68 @@ } } }, + "/spin.v1.WorkspaceService/ListOwnAgentThreads": { + "post": { + "tags": [ + "WorkspaceService" + ], + "summary": "ListOwnAgentThreads", + "description": "The caller's own chats across every workspace of theirs, each workspace's as its supervisor\n last reported them: what the dashboard's chat mode lists in one rail. Selected by the caller's\n id, never one from the request, and a key bound to a team or projects lists theirs alone.", + "operationId": "spin.v1.WorkspaceService.ListOwnAgentThreads", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListOwnAgentThreadsRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListOwnAgentThreadsResponse" + } + } + } + } + } + } + }, "/spin.v1.WorkspaceService/ListSnapshots": { "post": { "tags": [ @@ -12497,6 +13117,68 @@ } } }, + "/spin.v1.WorkspaceService/ListWorkspaceTimeline": { + "post": { + "tags": [ + "WorkspaceService" + ], + "summary": "ListWorkspaceTimeline", + "description": "What happened to a workspace, newest first, between two instants: its own events and what\n the audit log records of it and its snapshots, each with who did it - for its owner, read as\n GetWorkspaceHistory is. AdminService.ListTimeline is the same reading of any workspace, a host,\n a team, a project or the whole installation.", + "operationId": "spin.v1.WorkspaceService.ListWorkspaceTimeline", + "parameters": [ + { + "name": "Connect-Protocol-Version", + "in": "header", + "description": "Define the version of the Connect protocol", + "schema": { + "$ref": "#/components/schemas/connect-protocol-version" + }, + "example": 1 + }, + { + "name": "Connect-Timeout-Ms", + "in": "header", + "description": "Define the timeout, in ms", + "schema": { + "$ref": "#/components/schemas/connect-timeout-header" + }, + "example": 1000 + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListWorkspaceTimelineRequest" + } + } + }, + "required": true + }, + "responses": { + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/spin.v1.ListWorkspaceTimelineResponse" + } + } + } + } + } + } + }, "/spin.v1.WorkspaceService/ListWorkspaces": { "post": { "tags": [ @@ -13950,10 +14632,6 @@ "format": "duration", "description": "A signed duration in seconds, with up to nine fractional digits and an `s` suffix (for example, `3s` or `-0.001s`)." }, - "google.protobuf.FieldMask": { - "type": "string", - "description": "Comma-separated field paths in lowerCamelCase (for example, `user.displayName,photo`)." - }, "google.protobuf.ListValue": { "type": "array", "items": { @@ -14088,7 +14766,7 @@ ], "title": "max_workspaces", "format": "int32", - "description": "What the workspaces its tree made may hold at once; unset for no bound past its person's." + "description": "How many of the workspaces its tree made - this key and every key delegated from it - may be\n on a runner at once, and how many cores those may hold: bounds on what runs, unset for none\n past its person's. A workspace counts while it is being made or started, runs, is stopping or\n waits for a runner. One that is stopped, suspended or in the trash counts for nothing,\n whatever disk it keeps, and is counted again when it is started: a start past the bound is\n refused as a create is, naming the workspaces that hold it." }, "maxCores": { "type": [ @@ -14305,30 +14983,53 @@ "title": "AddWorkspaceGuestResponse", "additionalProperties": false }, - "spin.v1.AgentThread": { + "spin.v1.AffectedWorkspace": { "type": "object", "properties": { "id": { "type": "string", "title": "id" }, - "title": { - "type": "string", - "title": "title" - }, - "project": { - "type": "string", - "title": "project", - "description": "project is the directory the thread works in." - }, - "model": { + "name": { "type": "string", - "title": "model" + "title": "name" }, - "updatedAt": { + "state": { "allOf": [ { - "$ref": "#/components/schemas/google.protobuf.Timestamp" + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "state" + } + }, + "title": "AffectedWorkspace", + "additionalProperties": false + }, + "spin.v1.AgentThread": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id" + }, + "title": { + "type": "string", + "title": "title" + }, + "project": { + "type": "string", + "title": "project", + "description": "project is the directory the thread works in." + }, + "model": { + "type": "string", + "title": "model" + }, + "updatedAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" } ], "title": "updated_at" @@ -14377,6 +15078,102 @@ "additionalProperties": false, "description": "AgentVolume is one agent home of the caller's." }, + "spin.v1.Alert": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id", + "description": "The installation event's id." + }, + "event": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.InstallationEventType" + } + ], + "title": "event" + }, + "name": { + "type": "string", + "title": "name", + "description": "The rule's title (Grafana's alertname)." + }, + "summary": { + "type": "string", + "title": "summary", + "description": "The rule's summary, rendered for what fired; empty where it has none." + }, + "description": { + "type": "string", + "title": "description" + }, + "severity": { + "type": "string", + "title": "severity", + "description": "critical, warning; empty where the rule says none." + }, + "runbookUrl": { + "type": "string", + "title": "runbook_url", + "description": "Where the dashboard's runbook for it is, as the rule names it: /admin/runbooks/\u003cid\u003e." + }, + "labels": { + "type": "object", + "title": "labels", + "additionalProperties": { + "type": "string", + "title": "value" + }, + "description": "What else the alert's series is labelled with: which host, which volume." + }, + "startsAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "starts_at", + "description": "When it began firing, and for a resolution when it stopped." + }, + "endsAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "ends_at" + }, + "createdAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "created_at", + "description": "When the control plane heard it." + }, + "sent": { + "type": "integer", + "title": "sent", + "format": "int32", + "description": "How many administrators it was emailed to, is waiting for, and failed every attempt for." + }, + "pending": { + "type": "integer", + "title": "pending", + "format": "int32" + }, + "failed": { + "type": "integer", + "title": "failed", + "format": "int32" + } + }, + "title": "Alert", + "additionalProperties": false, + "description": "Alert is one firing or one resolution of an alert rule, as Grafana sent it." + }, "spin.v1.AllowedEmail": { "type": "object", "properties": { @@ -15372,6 +16169,135 @@ "additionalProperties": false, "description": "Empty on success" }, + "spin.v1.CostEstimate": { + "type": "object", + "properties": { + "day": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "day", + "description": "The billed day the rate is from: its first instant, UTC." + }, + "runningHourMicros": { + "type": [ + "string", + "null" + ], + "title": "running_hour_micros", + "format": "int64", + "description": "An hour of its machine running, for the cores and memory it holds; unset where that day billed\n no runner." + }, + "stoppedMonthMicros": { + "type": [ + "string", + "null" + ], + "title": "stopped_month_micros", + "format": "int64", + "description": "A month of its disk kept while it is stopped, for the bytes it kept that day; unset where that\n day billed no volume, or the workspace was made since." + } + }, + "title": "CostEstimate", + "additionalProperties": false, + "description": "What a workspace would cost at what the last billed day charged. An estimate: that day is over\n and the next may be charged otherwise, so it is never said without the day it is from." + }, + "spin.v1.CostMonth": { + "type": "object", + "properties": { + "month": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "month", + "description": "Its first instant, UTC." + }, + "readAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "read_at", + "description": "The reading its costs are from." + }, + "currency": { + "type": "string", + "title": "currency", + "description": "ISO 4217." + }, + "totalMicros": { + "type": "string", + "title": "total_micros", + "format": "int64", + "description": "Every cost of its days; idle and unstored are parts of it." + }, + "idleMicros": { + "type": "string", + "title": "idle_micros", + "format": "int64", + "description": "What the runners cost that no workspace held." + }, + "unstoredMicros": { + "type": "string", + "title": "unstored_micros", + "format": "int64", + "description": "What the volumes' bucket cost for bytes no workspace keeps." + }, + "budgetLimitMicros": { + "type": [ + "string", + "null" + ], + "title": "budget_limit_micros", + "format": "int64", + "description": "The installation's budget as the month's last reading in it said; unset where it had none." + } + }, + "title": "CostMonth", + "additionalProperties": false, + "description": "One month of the trend on Admin → Costs, in millionths of the bill's currency. An estimate of\n what nobody caused, from the reading it names: a month's last days are revised by the cloud\n into the next, and read again until they settle." + }, + "spin.v1.CostShare": { + "type": "object", + "properties": { + "computeMicros": { + "type": "string", + "title": "compute_micros", + "format": "int64", + "description": "Of the runners: what was held of them." + }, + "storageMicros": { + "type": "string", + "title": "storage_micros", + "format": "int64", + "description": "Of the volumes' bucket: what the volumes kept." + }, + "sentBytes": { + "type": "string", + "title": "sent_bytes", + "format": "int64", + "description": "What the traffic came to in the month: the machine's network out and back, and its sessions\n through the relay, both ways. Counted, not costed." + }, + "receivedBytes": { + "type": "string", + "title": "received_bytes", + "format": "int64" + }, + "forwardedBytes": { + "type": "string", + "title": "forwarded_bytes", + "format": "int64" + } + }, + "title": "CostShare", + "additionalProperties": false, + "description": "What was caused in a month of the cloud's bill, as the installation shares it out\n (AdminService.GetCosts): by one workspace, or by several summed. In millionths of the bill's\n currency. A share is of days already billed, never of this hour: the bill is read from the\n cloud a few times a day." + }, "spin.v1.CpuUsagePoint": { "type": "object", "properties": { @@ -15443,7 +16369,7 @@ "teamId": { "type": "string", "title": "team_id", - "description": "What it reaches and what its tree may hold: as APIKey's. Asked by a key holding keys:delegate,\n it is that key's child, and holds no more than it (delegation.Within)." + "description": "What it reaches and what its tree may have running at once: as APIKey's, where what is counted\n is said. Asked by a key holding keys:delegate, it is that key's child, and holds no more than it\n (delegation.Within)." }, "projectIds": { "type": "array", @@ -15955,7 +16881,7 @@ "preset": { "type": "string", "title": "preset", - "description": "The preset it is sized by, by name (AdminService.ListResourcePresets): its cores, memory,\n disk and how it grows. Empty is the project's preset when project_id names one, otherwise\n the installation's default preset, or with none marked 1 core, 512 MB and 10 GB free. A\n disabled preset is refused." + "description": "The preset it is sized by, by name (AdminService.ListResourcePresets): its cores, memory,\n disk and how it grows. Empty is the project's preset when project_id names one, otherwise\n the installation's default preset, or with none marked 1 core, 2048 MB and 10 GB free. A\n disabled preset is refused." }, "env": { "type": "object", @@ -16362,6 +17288,70 @@ "additionalProperties": false, "description": "DeletedWorkspace is a workspace in the trash." }, + "spin.v1.DeletionEffect": { + "type": "object", + "properties": { + "fate": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.DeletionFate" + } + ], + "title": "fate" + }, + "what": { + "type": "string", + "title": "what", + "description": "What it is: \"Its disk\", \"2 snapshots\"." + }, + "detail": { + "type": "string", + "title": "detail", + "description": "What happens to it and what follows from that, one sentence." + } + }, + "title": "DeletionEffect", + "additionalProperties": false, + "description": "DeletionEffect is one thing a workspace has and what a delete does to it, in words its owner\n reads before confirming." + }, + "spin.v1.DeletionFate": { + "type": "string", + "title": "DeletionFate", + "enum": [ + "DELETION_FATE_UNSPECIFIED", + "DELETION_FATE_ERASED", + "DELETION_FATE_TRASHED", + "DELETION_FATE_KEPT" + ], + "description": "DeletionFate is what a delete does to one thing a workspace has.\n\n- `DELETION_FATE_ERASED`: Gone when the delete is done, and not undeleted.\n- `DELETION_FATE_TRASHED`: In the trash for GetWorkspaceDeletionResponse.trash_days, restored by an undelete, erased\n when they are up.\n- `DELETION_FATE_KEPT`: Outlives the workspace, the trash's days included.", + "x-enum-descriptions": [ + "", + "Gone when the delete is done, and not undeleted.", + "In the trash for GetWorkspaceDeletionResponse.trash_days, restored by an undelete, erased\n when they are up.", + "Outlives the workspace, the trash's days included." + ] + }, + "spin.v1.DepartureFate": { + "type": "string", + "title": "DepartureFate", + "enum": [ + "DEPARTURE_FATE_UNSPECIFIED", + "DEPARTURE_FATE_MOVES", + "DEPARTURE_FATE_STAYS", + "DEPARTURE_FATE_LOSES_PLACE", + "DEPARTURE_FATE_HOLDS", + "DEPARTURE_FATE_STOPS" + ], + "description": "DepartureFate is what taking a host out does to one workspace on it.\n\n- `DEPARTURE_FATE_MOVES`: Suspended with its memory and resumed on another runner with room.\n- `DEPARTURE_FATE_STAYS`: Keeps its place on this runner: a drain moves only what runs.\n- `DEPARTURE_FATE_LOSES_PLACE`: Suspended at a published snapshot: its disk is let go of here, and its next start is on\n another runner, from what it published.\n- `DEPARTURE_FATE_HOLDS`: It keeps the host: the removal is refused while it is here. A group's machine is held by a\n disk only here, until it is stopped, which publishes it, or moved; a revoked host by any\n workspace that names it.\n- `DEPARTURE_FATE_STOPS`: Its disk is only here and is left here: it runs again only once recovered onto another\n runner, giving up what this one had not published.", + "x-enum-descriptions": [ + "", + "Suspended with its memory and resumed on another runner with room.", + "Keeps its place on this runner: a drain moves only what runs.", + "Suspended at a published snapshot: its disk is let go of here, and its next start is on\n another runner, from what it published.", + "It keeps the host: the removal is refused while it is here. A group's machine is held by a\n disk only here, until it is stopped, which publishes it, or moved; a revoked host by any\n workspace that names it.", + "Its disk is only here and is left here: it runs again only once recovered onto another\n runner, giving up what this one had not published." + ] + }, "spin.v1.DeviceLoginStatus": { "type": "string", "title": "DeviceLoginStatus", @@ -16816,12 +17806,6 @@ ], "title": "released_at" }, - "workspaces": { - "type": "string", - "title": "workspaces", - "format": "int64", - "description": "How many workspaces run it; one that nothing runs and is not the newest is collectable." - }, "newest": { "type": "boolean", "title": "newest", @@ -16835,12 +17819,12 @@ "removable": { "type": "boolean", "title": "removable", - "description": "Not the newest and nothing runs it: RemoveStoredVolume removes it, and so does the\n collect-releases job on its own." + "description": "Not the newest, and open in no machine and no suspended memory: RemoveStoredVolume removes\n it, and so does the collect-releases job on its own. A release only stopped workspaces last\n ran is removable, since their next start is given the newest." }, "keptBecause": { "type": "string", "title": "kept_because", - "description": "Why it stays, in words, where it may not go." + "description": "Why it stays, in words, where it may not go: how many machines and suspended memories have\n it open, or that it is the newest." } }, "title": "ExtensionRelease", @@ -16861,7 +17845,8 @@ "workspaces": { "type": "string", "title": "workspaces", - "format": "int64" + "format": "int64", + "description": "The machines that have it open and the suspended memories that do: a stopped workspace is\n given the newest at its next start, and is on none." }, "newest": { "type": "boolean", @@ -17093,6 +18078,20 @@ } ], "title": "give_back_refused_at" + }, + "capacityRefused": { + "type": "boolean", + "title": "capacity_refused", + "description": "launch_failure is the cloud having no machine of the kind the class asks for - no spot\n capacity, or the spot quota spent - which the class's spot setting, or its fallback, is what\n changes; false for any other failure, and while there is none." + }, + "onDemandUntil": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "on_demand_until", + "description": "Set while a spot class's group starts on-demand machines because the cloud refused it spot\n (HostClass.fallback_on_demand): until this, and spot again after. Its class still says spot." } }, "title": "FleetClass", @@ -17416,11 +18415,37 @@ "platform": { "type": "array", "items": { - "$ref": "#/components/schemas/spin.v1.ServiceCost" + "$ref": "#/components/schemas/spin.v1.PlatformCost" }, "title": "platform", - "description": "Every other cost - the control plane, the proxy, keys, logs - by service, costliest first." - } + "description": "Every other cost - the control plane, the proxy, keys, logs - by role and service, costliest\n first." + }, + "standby": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.StandbyCost" + } + ], + "title": "standby", + "description": "What the installation costs a day with nobody working; unset where no day before the\n reading billed anything." + }, + "readAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "read_at", + "description": "When the month's bill was last read; unset where it never was, and then nothing is answered." + }, + "trend": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.CostMonth" + }, + "title": "trend", + "description": "The year up to this month, oldest first, this month last: each month the bill was read in,\n shared out as this one is. A month never read is not among them." + } }, "title": "GetCostsResponse", "additionalProperties": false @@ -17621,6 +18646,15 @@ } ], "title": "host" + }, + "departure": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.HostDeparture" + } + ], + "title": "departure", + "description": "What taking the host out would do to each workspace on it, as the server would do it: asked\n of one host, because it reads every workspace there." } }, "title": "GetHostResponse", @@ -17687,54 +18721,6 @@ "title": "GetInstallationHealthResponse", "additionalProperties": false }, - "spin.v1.GetInstallationSettingsRequest": { - "type": "object", - "title": "GetInstallationSettingsRequest", - "additionalProperties": false - }, - "spin.v1.GetInstallationSettingsResponse": { - "type": "object", - "properties": { - "settings": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.InstallationSettings" - } - ], - "title": "settings" - }, - "reaches": { - "type": "object", - "title": "reaches", - "additionalProperties": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.SettingReach" - } - ], - "title": "value" - }, - "description": "What each setting touches when saved, by its update_mask path: every path\n UpdateInstallationSettings accepts, and no other." - }, - "runningWorkspaces": { - "type": "integer", - "title": "running_workspaces", - "format": "int32", - "description": "How many workspaces are running now: what a RUNNING_WORKSPACES setting reaches, and what a\n NEW_WORKSPACES one leaves as it is." - }, - "connectedRunners": { - "type": [ - "integer", - "null" - ], - "title": "connected_runners", - "format": "int32", - "description": "How many runners hold their stream to this control plane now: what a NEXT_HEARTBEAT setting\n reaches. Unset where this control plane cannot tell." - } - }, - "title": "GetInstallationSettingsResponse", - "additionalProperties": false - }, "spin.v1.GetMonitoringRequest": { "type": "object", "title": "GetMonitoringRequest", @@ -17770,7 +18756,7 @@ "$ref": "#/components/schemas/spin.v1.NotificationPreference" }, "title": "preferences", - "description": "One per event a person may be told of, in the order they are shown." + "description": "One per event a person may be told of, in the order they are shown: an administrator's\n include the installation's." }, "emailConfigured": { "type": "boolean", @@ -17855,6 +18841,64 @@ "title": "GetPrebuildResponse", "additionalProperties": false }, + "spin.v1.GetProjectCostsRequest": { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "title": "project_id" + } + }, + "title": "GetProjectCostsRequest", + "additionalProperties": false + }, + "spin.v1.GetProjectCostsResponse": { + "type": "object", + "properties": { + "month": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "month", + "description": "The month in progress: its first instant, UTC." + }, + "readAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "read_at", + "description": "When the bill was last read; unset where it never was - an installation with no bill scope -\n and then nothing below is known and nothing is to be drawn." + }, + "currency": { + "type": "string", + "title": "currency", + "description": "ISO 4217; every amount here is in millionths of it." + }, + "workspaces": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.CostShare" + } + ], + "title": "workspaces", + "description": "Its workspaces' shares summed, deleted ones and its prebuilds' builds included: a workspace is\n the project's it opened, whoever's it was." + }, + "prebuilds": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.PrebuildsKept" + } + ], + "title": "prebuilds" + } + }, + "title": "GetProjectCostsResponse", + "additionalProperties": false + }, "spin.v1.GetProjectRequest": { "type": "object", "properties": { @@ -17886,26 +18930,6 @@ "title": "GetProjectResponse", "additionalProperties": false }, - "spin.v1.GetRegistrationModeRequest": { - "type": "object", - "title": "GetRegistrationModeRequest", - "additionalProperties": false - }, - "spin.v1.GetRegistrationModeResponse": { - "type": "object", - "properties": { - "mode": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.RegistrationMode" - } - ], - "title": "mode" - } - }, - "title": "GetRegistrationModeResponse", - "additionalProperties": false - }, "spin.v1.GetRunbookRequest": { "type": "object", "properties": { @@ -17942,24 +18966,57 @@ "title": "GetRunbookResponse", "additionalProperties": false }, - "spin.v1.GetSecuritySettingsRequest": { + "spin.v1.GetSettingsRequest": { "type": "object", - "title": "GetSecuritySettingsRequest", + "title": "GetSettingsRequest", "additionalProperties": false }, - "spin.v1.GetSecuritySettingsResponse": { + "spin.v1.GetSettingsResponse": { "type": "object", "properties": { - "settings": { + "values": { + "type": "object", + "title": "values", + "additionalProperties": { + "type": "string", + "title": "value" + }, + "description": "Every definition's value in force, by key, as text of its type: what the installation says,\n or the default where it says nothing. The control plane's own are left out." + }, + "definitions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.SettingDefinition" + }, + "title": "definitions", + "description": "Every setting an administrator is shown, in the registry's order." + }, + "runningWorkspaces": { + "type": "integer", + "title": "running_workspaces", + "format": "int32", + "description": "How many workspaces are running now: what a RUNNING_WORKSPACES setting reaches, and what a\n NEW_WORKSPACES one leaves as it is." + }, + "connectedRunners": { + "type": [ + "integer", + "null" + ], + "title": "connected_runners", + "format": "int32", + "description": "How many runners hold their stream to this control plane now: what a NEXT_HEARTBEAT setting\n reaches. Unset where this control plane cannot tell." + }, + "place": { "allOf": [ { - "$ref": "#/components/schemas/spin.v1.SecuritySettings" + "$ref": "#/components/schemas/spin.v1.InstallationPlace" } ], - "title": "settings" + "title": "place", + "description": "Where the installation runs and what it runs, read-only." } }, - "title": "GetSecuritySettingsResponse", + "title": "GetSettingsResponse", "additionalProperties": false }, "spin.v1.GetSetupStatusRequest": { @@ -18104,6 +19161,64 @@ "title": "GetStorageUsageResponse", "additionalProperties": false }, + "spin.v1.GetTeamCostsRequest": { + "type": "object", + "properties": { + "teamId": { + "type": "string", + "title": "team_id" + } + }, + "title": "GetTeamCostsRequest", + "additionalProperties": false + }, + "spin.v1.GetTeamCostsResponse": { + "type": "object", + "properties": { + "month": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "month", + "description": "The month in progress: its first instant, UTC." + }, + "readAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "read_at", + "description": "When the bill was last read; unset where it never was - an installation with no bill scope -\n and then nothing below is known and nothing is to be drawn." + }, + "currency": { + "type": "string", + "title": "currency", + "description": "ISO 4217; every amount here is in millionths of it." + }, + "total": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.CostShare" + } + ], + "title": "total", + "description": "Its workspaces, summed." + }, + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.TeamWorkspaceCost" + }, + "title": "workspaces", + "description": "Costliest first." + } + }, + "title": "GetTeamCostsResponse", + "additionalProperties": false + }, "spin.v1.GetTeamRequest": { "type": "object", "properties": { @@ -18180,6 +19295,14 @@ ], "title": "samples", "description": "Into the installation's store of metrics." + }, + "perVolumeHistograms": { + "type": "array", + "items": { + "type": "string" + }, + "title": "per_volume_histograms", + "description": "The metrics that cost most to keep, a histogram per volume, which a surface may offer to\n drop as one (the setting telemetry_metrics_drop)." } }, "title": "GetTelemetryStatusResponse", @@ -18264,6 +19387,38 @@ "title": "GetUserSettingsResponse", "additionalProperties": false }, + "spin.v1.GetUserWorkspaceEgressRequest": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The workspace's id." + }, + "reason": { + "type": "string", + "title": "reason", + "description": "Why, kept on the audit entry." + } + }, + "title": "GetUserWorkspaceEgressRequest", + "additionalProperties": false + }, + "spin.v1.GetUserWorkspaceEgressResponse": { + "type": "object", + "properties": { + "egress": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.GetWorkspaceEgressResponse" + } + ], + "title": "egress" + } + }, + "title": "GetUserWorkspaceEgressResponse", + "additionalProperties": false + }, "spin.v1.GetVSCodeServersRequest": { "type": "object", "title": "GetVSCodeServersRequest", @@ -18339,7 +19494,7 @@ "title": "GetWorkspaceConsoleResponse", "additionalProperties": false }, - "spin.v1.GetWorkspaceEgressRequest": { + "spin.v1.GetWorkspaceCostRequest": { "type": "object", "properties": { "workspaceId": { @@ -18347,26 +19502,122 @@ "title": "workspace_id" } }, - "title": "GetWorkspaceEgressRequest", - "additionalProperties": false, - "description": "GetWorkspaceEgressRequest asks what a workspace has reached beyond the node. The host keeps\n the record, so the workspace has to be running on one; it is kept for six hours and survives\n a restart of the runner, not of the host." + "title": "GetWorkspaceCostRequest", + "additionalProperties": false }, - "spin.v1.GetWorkspaceEgressResponse": { + "spin.v1.GetWorkspaceCostResponse": { "type": "object", "properties": { - "destinations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/spin.v1.ObservedDestination" - }, - "title": "destinations", - "description": "Most traffic first, so what an allowlist has to carry is at the top." + "month": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "month", + "description": "The month in progress: its first instant, UTC." }, - "lookups": { - "type": "array", - "items": { - "$ref": "#/components/schemas/spin.v1.ResolverLookup" - }, + "readAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "read_at", + "description": "When the bill was last read; unset where it never was - an installation with no bill scope, a\n cloud that sends none - and then nothing below is known and nothing is to be drawn." + }, + "currency": { + "type": "string", + "title": "currency", + "description": "ISO 4217; every amount here is in millionths of it." + }, + "share": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.CostShare" + } + ], + "title": "share", + "description": "The month so far." + }, + "estimate": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.CostEstimate" + } + ], + "title": "estimate", + "description": "Unset where no day before the reading was billed, or none says anything of this workspace." + } + }, + "title": "GetWorkspaceCostResponse", + "additionalProperties": false + }, + "spin.v1.GetWorkspaceDeletionRequest": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "permanently": { + "type": "boolean", + "title": "permanently", + "description": "What DeleteWorkspaceRequest.permanently would do: an erase, whatever the trash keeps." + } + }, + "title": "GetWorkspaceDeletionRequest", + "additionalProperties": false + }, + "spin.v1.GetWorkspaceDeletionResponse": { + "type": "object", + "properties": { + "trashDays": { + "type": "integer", + "title": "trash_days", + "format": "int32", + "description": "How many days the delete keeps the workspace in the trash; 0 is an erase now - the\n installation keeps no trash, or permanently was asked." + }, + "effects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.DeletionEffect" + }, + "title": "effects", + "description": "Everything the delete does to what the workspace has, ordered erased, trashed, kept. Only\n what this workspace has: one with no snapshot is told nothing of snapshots." + } + }, + "title": "GetWorkspaceDeletionResponse", + "additionalProperties": false + }, + "spin.v1.GetWorkspaceEgressRequest": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + } + }, + "title": "GetWorkspaceEgressRequest", + "additionalProperties": false, + "description": "GetWorkspaceEgressRequest asks what a workspace has reached beyond the node. The host keeps\n the record, so the workspace has to be running on one; it is kept for six hours and survives\n a restart of the runner, not of the host." + }, + "spin.v1.GetWorkspaceEgressResponse": { + "type": "object", + "properties": { + "destinations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.ObservedDestination" + }, + "title": "destinations", + "description": "Most traffic first, so what an allowlist has to carry is at the top." + }, + "lookups": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.ResolverLookup" + }, "title": "lookups", "description": "The workspace's recent lookups its host's resolver did not answer as asked, newest first:\n \"it cannot resolve github.com\" is an upstream failing, a name pointing into the node being\n removed, or a query type refused, and this says which. Answered lookups are not kept." }, @@ -18616,6 +19867,14 @@ }, "title": "injections", "description": "Each header a brokered rule of the workspace's policies sets, and whether what it is set\n from is there to set: a request to a brokered name is refused when it is not, and this is\n where that is seen from outside the machine." + }, + "network": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.WorkspaceNetworkStatus" + }, + "title": "network", + "description": "The policies the workspace's network is under (Workspace.network_tags), in their order, and\n whether each is declared and installed on its host. Empty for a workspace with no tag, which\n reaches anything beyond its host's own networks." } }, "title": "GetWorkspaceStatusResponse", @@ -18929,6 +20188,19 @@ ], "title": "known_at", "description": "known_at is when what decided the status was so: the moment it was asked for a part asked\n now, the collector's last report, the last WAL file archived or failed, a failed job's run." + }, + "problems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.Problem" + }, + "title": "problems", + "description": "What is wrong in this part, each on its own: one per host, one per job, one for a part that is\n a single thing. Empty while the part is OK. At most 20 are listed, the worst first;\n problem_count is how many there are." + }, + "problemCount": { + "type": "integer", + "title": "problem_count", + "format": "int32" } }, "title": "HealthCheck", @@ -18973,7 +20245,7 @@ "HEALTH_PART_HOSTS", "HEALTH_PART_WAITING_WORKSPACES" ], - "description": "HealthPart is which part of the installation a check is of.\n\n- `HEALTH_PART_DATABASE`: The control plane's database answers a query.\n- `HEALTH_PART_DATABASE_ARCHIVE`: The database's continuous archive keeps up: its last WAL file sent and how many wait. Its base\n backups are taken by a timer outside the control plane, which records nothing it can read.\n- `HEALTH_PART_LOG_STORE`: The log store (Quickwit) beside the control plane answers that it is ready.\n- `HEALTH_PART_COLLECTOR`: The collector sends what it is given: spans to the trace store, samples to the metric store\n (GetTelemetryStatus). It is what says whether the metric store takes writes - the control\n plane is not one of the metric store's readers.\n- `HEALTH_PART_JOBS`: The control plane's periodic work: a job whose last run failed (ListJobs).\n- `HEALTH_PART_HOSTS`: The hosts registered and not taken out of the fleet, against those whose command stream is up.\n- `HEALTH_PART_WAITING_WORKSPACES`: Workspaces waiting for a runner, across the whole fleet.", + "description": "HealthPart is which part of the installation a check is of.\n\n- `HEALTH_PART_DATABASE`: The control plane's database answers a query.\n- `HEALTH_PART_DATABASE_ARCHIVE`: The database's continuous archive keeps up: its last WAL file sent and how many wait. Its base\n backups are taken by a timer outside the control plane, which records nothing it can read.\n- `HEALTH_PART_LOG_STORE`: The log store (Quickwit) beside the control plane answers that it is ready.\n- `HEALTH_PART_COLLECTOR`: The collector sends what it is given: spans to the trace store, samples to the metric store\n (GetTelemetryStatus). It is what says whether the metric store takes writes - the control\n plane is not one of the metric store's readers.\n- `HEALTH_PART_JOBS`: The control plane's periodic work: a job whose last run failed (ListJobs).\n- `HEALTH_PART_HOSTS`: The hosts registered and not taken out of the fleet: those whose command stream is not up,\n and those that answer and fail a check of their own (HostCheck).\n- `HEALTH_PART_WAITING_WORKSPACES`: Workspaces waiting for a runner, across the whole fleet.", "x-enum-descriptions": [ "", "The control plane's database answers a query.", @@ -18981,7 +20253,7 @@ "The log store (Quickwit) beside the control plane answers that it is ready.", "The collector sends what it is given: spans to the trace store, samples to the metric store\n (GetTelemetryStatus). It is what says whether the metric store takes writes - the control\n plane is not one of the metric store's readers.", "The control plane's periodic work: a job whose last run failed (ListJobs).", - "The hosts registered and not taken out of the fleet, against those whose command stream is up.", + "The hosts registered and not taken out of the fleet: those whose command stream is not up,\n and those that answer and fail a check of their own (HostCheck).", "Workspaces waiting for a runner, across the whole fleet." ] }, @@ -19281,6 +20553,50 @@ "type": "string", "title": "removal_refused", "description": "removal_refused is why that removal cannot run now - a workspace whose disk is there - in\n words an administrator acts on; empty while it can." + }, + "keysGiven": { + "type": "integer", + "title": "keys_given", + "format": "int32", + "description": "keys_given is how many teams' disks this host was given the key of: what revoking it, or\n removing it as taken, marks for re-keying." + }, + "machineId": { + "type": "string", + "title": "machine_id", + "description": "Where the host's machine is, as its cloud said when the host joined - never as the host said\n of itself: the machine's id and type as that cloud names them, its zone and its region. Each\n empty for a host that joined with no cloud (a registration token: a development stack, a\n machine of one's own), and for what its cloud did not say." + }, + "machineType": { + "type": "string", + "title": "machine_type" + }, + "zone": { + "type": "string", + "title": "zone" + }, + "region": { + "type": "string", + "title": "region" + }, + "reclaimable": { + "type": [ + "boolean", + "null" + ], + "title": "reclaimable", + "description": "reclaimable is whether the cloud may take the machine back, as its cloud said of the instance\n when it joined - a spot machine is, whatever its class says: a group that fell back to\n on-demand, or one whose class was changed, starts machines its class does not describe. Unset\n where nothing says: a host that joined with no cloud, or one its cloud named nothing of." + }, + "holders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.HostHolder" + }, + "title": "holders", + "description": "holders is what keeps this host in the fleet, from the database: the workspaces whose disk is\n only here - whichever list of theirs leaves them out, a prebuild's build, a team's, another\n person's, a failed one - and the volumes still here that are no such workspace's disk, a\n trashed workspace's or an agent's home, each of which forgetting the host must detach first.\n At most 10; holder_count is how many there are." + }, + "holderCount": { + "type": "integer", + "title": "holder_count", + "format": "int32" } }, "title": "Host", @@ -19361,13 +20677,125 @@ "type": "integer", "title": "max_hosts", "format": "int32", - "description": "The most hosts of it; 0 is no limit of its own (InstallationSettings.autoscaling_max_hosts\n bounds them all)." + "description": "The most hosts of it; 0 is no limit of its own (the setting autoscaling_max_hosts\n bounds them all)." + }, + "fallbackOnDemand": { + "type": "boolean", + "title": "fallback_on_demand", + "description": "A spot class whose group the cloud refuses spot machines for capacity starts on-demand ones\n for a while, and then spot again (FleetClass.on_demand_until); spot itself is not changed.\n Off, the class's workspaces wait until somebody changes spot. Only a spot class has it." } }, "title": "HostClass", "additionalProperties": false, "description": "A kind of runner: the shape of the machines a group of the installation's cloud starts for it,\n which the control plane makes and sizes. What a machine boots is the installation's launch\n template, never this. \"default\" is the class a preset that names none runs on." }, + "spin.v1.HostDeparture": { + "type": "object", + "properties": { + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.WorkspaceDeparture" + }, + "title": "workspaces", + "description": "At most 20, the running first; count is how many there are." + }, + "count": { + "type": "integer", + "title": "count", + "format": "int32" + } + }, + "title": "HostDeparture", + "additionalProperties": false, + "description": "HostDeparture is what draining the host, and removing it as Host.removal says, would do to each\n workspace that names it - the ones a drain moves, the ones that stay, the ones that lose their\n place on it and the ones that hold it - so the confirmation says it before it is done." + }, + "spin.v1.HostHolder": { + "type": "object", + "properties": { + "kind": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.HostHolderKind" + } + ], + "title": "kind" + }, + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The workspace it is or was the disk of; for an agent's home, the workspace that mounts it, or\n that last did by name alone (workspace_id empty)." + }, + "workspaceName": { + "type": "string", + "title": "workspace_name" + }, + "volumeId": { + "type": "string", + "title": "volume_id", + "description": "The volume, for a holder that is a volume (a trashed workspace's disk, an agent's home); empty\n for a workspace, whose disk the workspace names." + }, + "ownerId": { + "type": "string", + "title": "owner_id", + "description": "Whose it is." + }, + "ownerEmail": { + "type": "string", + "title": "owner_email" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "state", + "description": "The workspace's state; UNSPECIFIED for an agent's home no workspace mounts." + }, + "lastEvent": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceEventType" + } + ], + "title": "last_event", + "description": "What last happened to the workspace, and when: its history's newest entry. UNSPECIFIED and\n unset where it has none." + }, + "lastEventAt": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "last_event_at" + } + }, + "title": "HostHolder", + "additionalProperties": false, + "description": "HostHolder is one thing a host holds that its removal would lose or has to wait for, with\n whose it is and what last happened to it." + }, + "spin.v1.HostHolderKind": { + "type": "string", + "title": "HostHolderKind", + "enum": [ + "HOST_HOLDER_KIND_UNSPECIFIED", + "HOST_HOLDER_KIND_WORKSPACE", + "HOST_HOLDER_KIND_TEAM_WORKSPACE", + "HOST_HOLDER_KIND_PREBUILD_BUILD", + "HOST_HOLDER_KIND_TRASHED_WORKSPACE", + "HOST_HOLDER_KIND_AGENT_HOME" + ], + "description": "HostHolderKind is what a holder is, which says which list of whose shows it.\n\n- `HOST_HOLDER_KIND_WORKSPACE`: A person's own workspace, in their workspaces list.\n- `HOST_HOLDER_KIND_TEAM_WORKSPACE`: A workspace of a team, in its members' lists.\n- `HOST_HOLDER_KIND_PREBUILD_BUILD`: A prebuild's build: kept stopped as its prebuild's anchor, and in nobody's list.\n- `HOST_HOLDER_KIND_TRASHED_WORKSPACE`: The disk of a workspace in its owner's trash.\n- `HOST_HOLDER_KIND_AGENT_HOME`: A person's agent's home (AgentVolume).", + "x-enum-descriptions": [ + "", + "A person's own workspace, in their workspaces list.", + "A workspace of a team, in its members' lists.", + "A prebuild's build: kept stopped as its prebuild's anchor, and in nobody's list.", + "The disk of a workspace in its owner's trash.", + "A person's agent's home (AgentVolume)." + ] + }, "spin.v1.HostNetworkPolicy": { "type": "object", "properties": { @@ -19801,199 +21229,87 @@ "additionalProperties": false, "description": "InjectionStatus is one header the egress proxy sets on the workspace's requests to a brokered\n name, and whether the workspace's team holds what it is set from." }, - "spin.v1.InstallationSettings": { + "spin.v1.InstallationEventType": { + "type": "string", + "title": "InstallationEventType", + "enum": [ + "INSTALLATION_EVENT_TYPE_UNSPECIFIED", + "INSTALLATION_EVENT_TYPE_ALERT_FIRING", + "INSTALLATION_EVENT_TYPE_ALERT_RESOLVED" + ], + "description": "What happened to the installation itself, which its administrators are told of: an event of no\n workspace, recorded where the control plane hears it.\n\n- `INSTALLATION_EVENT_TYPE_ALERT_FIRING`: One of the release's alert rules fired in the installation's Grafana\n- `INSTALLATION_EVENT_TYPE_ALERT_RESOLVED`: A rule that fired is no longer past its threshold", + "x-enum-descriptions": [ + "", + "One of the release's alert rules fired in the installation's Grafana", + "A rule that fired is no longer past its threshold" + ] + }, + "spin.v1.InstallationPlace": { "type": "object", "properties": { - "allowedImages": { - "type": "array", - "items": { - "type": "string" - }, - "title": "allowed_images", - "description": "Whitelist of allowed images (empty = all allowed)" + "provider": { + "type": "string", + "title": "provider", + "description": "The provider the installation was made on, as its document names it: \"aws\", \"self-hosted\"." }, - "maxWorkspacesPerUser": { - "type": "integer", - "title": "max_workspaces_per_user", - "format": "int32", - "description": "Maximum workspaces per user (0 = unlimited)" + "account": { + "type": "string", + "title": "account", + "description": "The cloud account it runs in, as that cloud names it." }, - "defaultNetworkTags": { - "type": "array", - "items": { - "type": "string" - }, - "title": "default_network_tags", - "description": "What a workspace created without a network tag of its own runs under, by policy name.\n Empty means \"offline\", the policy every host has and which allows nothing: no value of\n this field means open. An installation that wants such a workspace out declares a policy\n saying what it may reach and names it here." + "region": { + "type": "string", + "title": "region" }, - "updatedAt": { + "release": { + "type": "string", + "title": "release", + "description": "The release the control plane runs, \"v20261112.01\"." + }, + "machineImage": { + "type": "string", + "title": "machine_image", + "description": "The machine image the control plane's machine booted, as its cloud names it." + } + }, + "title": "InstallationPlace", + "additionalProperties": false, + "description": "InstallationPlace is where the installation runs, as its control plane's machine read it once\n when it started - never asked of the cloud again per request - and the release it runs. What\n a cloud did not say, or an installation with no cloud (a development stack, a machine of one's\n own), is empty." + }, + "spin.v1.Job": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "name" + }, + "intervalSeconds": { + "type": "string", + "title": "interval_seconds", + "format": "int64", + "description": "Zero for a job only run by hand, which has no next_run_at." + }, + "running": { + "type": "boolean", + "title": "running" + }, + "nextRunAt": { "allOf": [ { "$ref": "#/components/schemas/google.protobuf.Timestamp" } ], - "title": "updated_at" + "title": "next_run_at", + "description": "When the schedule next starts it." }, - "telemetryTracesKeepPercent": { - "type": "integer", - "title": "telemetry_traces_keep_percent", - "format": "int32", - "description": "What the installation's collector keeps, decided at the collector\n (internal/controlplane/domain/telemetrypolicy). A trace with an error in any span is always kept, and so\n is one slower than telemetry_traces_slow_ms (0: none is kept for being slow); of the rest,\n telemetry_traces_keep_percent, 0 to 100 and 100 until an admin says otherwise." - }, - "telemetryTracesSlowMs": { - "type": "integer", - "title": "telemetry_traces_slow_ms", - "format": "int32" - }, - "telemetryMetricsDrop": { - "type": "array", - "items": { - "type": "string" - }, - "title": "telemetry_metrics_drop", - "description": "The metrics that are not kept, by name; a pattern may begin or end with *." - }, - "telemetryPerVolumeHistograms": { - "type": "array", - "items": { - "type": "string" - }, - "title": "telemetry_per_volume_histograms", - "description": "Read only: the metrics that cost most to keep, a histogram per volume, which a surface may\n offer to drop as one." - }, - "workspaceTrashDays": { - "type": "integer", - "title": "workspace_trash_days", - "format": "int32", - "description": "How many days a deleted workspace stays in the trash, whole, for its owner to undelete\n before it is erased: 0 is no trash, and 7 is what an installation that says nothing keeps." - }, - "identityAudiences": { - "type": "array", - "items": { - "type": "string" - }, - "title": "identity_audiences", - "description": "Who a workspace's identity token may be for; empty is anybody. What the installation's\n issuer gives, not who trusts it: a relying party still decides which subjects it admits." - }, - "volumeRpoSeconds": { - "type": "integer", - "title": "volume_rpo_seconds", - "format": "int32", - "description": "How far behind the object store a workspace's volume may fall, in seconds, where its preset\n does not say: what it commits on (v6 §11). 0 is none - it commits on its size and its\n snapshots alone - and 300 is what an installation that says nothing keeps." - }, - "diskReservePercent": { - "type": "integer", - "title": "disk_reserve_percent", - "format": "int32", - "description": "How a host's disk is lent to workspaces. A volume holds what it wrote, not its size: a host\n is charged, per volume, the larger of disk_reserve_percent of its size and what it wrote,\n and takes new ones up to 80% of its device; disk_commit_max_percent bounds the sizes\n summed - every volume filling at once. 50 and 200 are what an installation that says\n nothing lends; 100 and 100 lends nothing past what the device holds." - }, - "diskCommitMaxPercent": { - "type": "integer", - "title": "disk_commit_max_percent", - "format": "int32" - }, - "workspaceIdleSuspendMinutes": { - "type": "integer", - "title": "workspace_idle_suspend_minutes", - "format": "int32", - "description": "How many minutes a running workspace may go unused - nobody working over its ssh, editor or\n forwarded ports, and its cores all but still - before it is suspended with its processes,\n which its next start resumes. 0 suspends none; 60 is what an installation that says nothing\n keeps, and a week is the most." - }, - "workspaceMaxRunningMinutes": { - "type": "integer", - "title": "workspace_max_running_minutes", - "format": "int32", - "description": "How many minutes a workspace may run at a stretch, used or not, before it is suspended the\n same way: a process that keeps its cores busy is use to the rule above, and this is what\n ends it. Counted from its machine's start, which a resume is too. 0 caps none; 720 is what\n an installation that says nothing keeps, and a week is the most." - }, - "workspaceLeaseMaxMinutes": { - "type": "integer", - "title": "workspace_lease_max_minutes", - "format": "int32", - "description": "How many minutes a process may keep its workspace awake at a stretch (a lease:\n `spin-supervisor keep -- \u003ccmd\u003e`, a .spin.yaml task with keep_awake). While a lease is held\n and within this of its start, nobody is asked, nothing idle is suspended, and the running\n cap waits for the lease's end. 0 honours no lease; 480 is what an installation that says\n nothing keeps, and a week is the most." - }, - "teamSecretsRole": { - "type": "string", - "title": "team_secrets_role", - "description": "The IAM role a team's secret kept in Secrets Manager or Parameter Store is read under, in a\n session tagged spin-team-id with the team: its policy lets that session read only what carries\n the same tag. Empty, no secret is kept as a reference." - }, - "projectCreation": { - "type": "string", - "title": "project_creation", - "description": "Who makes projects (ProjectService.CreateProject): \"everyone\" signed in, or only the\n instance's \"admins\". Empty is everyone." - }, - "autoscalingIdleMinutes": { - "type": "integer", - "title": "autoscaling_idle_minutes", - "format": "int32", - "description": "How the runner fleet is sized (GetFleet): the minutes it may be idle before it is emptied,\n above zero; the quiet hours, HH:MM-HH:MM in the IANA time zone, when it is emptied at once,\n empty for none; and the most hosts it is asked for, under the group's own maximum - zero is\n that maximum. Each takes effect at the autoscaler's next pass." - }, - "autoscalingQuietHours": { - "type": "string", - "title": "autoscaling_quiet_hours" - }, - "autoscalingTimeZone": { - "type": "string", - "title": "autoscaling_time_zone" - }, - "autoscalingMaxHosts": { - "type": "integer", - "title": "autoscaling_max_hosts", - "format": "int32" - }, - "hostShellNeedsApproval": { - "type": "boolean", - "title": "host_shell_needs_approval", - "description": "A root shell on a host (HostShellService): whether a second administrator approves each one\n before its host is touched, and the most minutes one may be open, 30 where unset." - }, - "hostShellMaxMinutes": { - "type": "integer", - "title": "host_shell_max_minutes", - "format": "int32" - }, - "prebuildRetireUnusedDays": { - "type": "integer", - "title": "prebuild_retire_unused_days", - "format": "int32", - "description": "How many days a ready prebuild may go without a workspace opened from it before it is retired\n and its disk given back - the newest of its ref too, so a project nobody opens stops holding\n one, and its next open starts from the image. It is not built again until its ref moves to\n another commit, or an admin runs it. Counted from its build for one never opened. 0, what an\n installation that says nothing keeps, retires none for this; a year is the most." - } - }, - "title": "InstallationSettings", - "additionalProperties": false, - "description": "InstallationSettings contains global settings for the Spin installation" - }, - "spin.v1.Job": { - "type": "object", - "properties": { - "name": { - "type": "string", - "title": "name" - }, - "intervalSeconds": { - "type": "string", - "title": "interval_seconds", - "format": "int64", - "description": "Zero for a job only run by hand, which has no next_run_at." - }, - "running": { - "type": "boolean", - "title": "running" - }, - "nextRunAt": { - "allOf": [ - { - "$ref": "#/components/schemas/google.protobuf.Timestamp" - } - ], - "title": "next_run_at", - "description": "When the schedule next starts it." - }, - "lastRun": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.JobRun" - } - ], - "title": "last_run", - "description": "Unset until it has run on this installation." + "lastRun": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.JobRun" + } + ], + "title": "last_run", + "description": "Unset until it has run on this installation." }, "lastSucceededAt": { "allOf": [ @@ -20008,10 +21324,15 @@ "title": "title", "description": "The job as a person names it: \"Storage collection\" for storage-gc." }, - "purpose": { + "does": { "type": "string", - "title": "purpose", - "description": "One sentence of what it does and what stays undone while it fails." + "title": "does", + "description": "A sentence of what it does." + }, + "whileFailing": { + "type": "string", + "title": "while_failing", + "description": "What stays undone while it fails, said after \"While it fails, \": \"new workspaces start from\n an older prebuild\". What a failure's verdict says, so whoever reads it knows how soon it\n needs them." }, "recentRuns": { "type": "array", @@ -20079,7 +21400,7 @@ "traceId": { "type": "string", "title": "trace_id", - "description": "The run's trace, empty when it was not recorded." + "description": "The run's trace id, which every record it logged carries; empty when it was not recorded.\n Whether the store kept the trace is LogService.KeptTraces's to say." }, "askedBy": { "type": "string", @@ -20136,6 +21457,36 @@ "title": "KeepWorkspaceResponse", "additionalProperties": false }, + "spin.v1.KeptTracesRequest": { + "type": "object", + "properties": { + "traceIds": { + "type": "array", + "items": { + "type": "string" + }, + "title": "trace_ids", + "description": "Hex, as a log record carries them: at most 1000, a page's." + } + }, + "title": "KeptTracesRequest", + "additionalProperties": false + }, + "spin.v1.KeptTracesResponse": { + "type": "object", + "properties": { + "traceIds": { + "type": "array", + "items": { + "type": "string" + }, + "title": "trace_ids", + "description": "Those of the request's the store holds, each once." + } + }, + "title": "KeptTracesResponse", + "additionalProperties": false + }, "spin.v1.ListAPIKeysRequest": { "type": "object", "properties": { @@ -20232,6 +21583,25 @@ "title": "ListAgentVolumesResponse", "additionalProperties": false }, + "spin.v1.ListAlertsRequest": { + "type": "object", + "title": "ListAlertsRequest", + "additionalProperties": false + }, + "spin.v1.ListAlertsResponse": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.Alert" + }, + "title": "alerts" + } + }, + "title": "ListAlertsResponse", + "additionalProperties": false + }, "spin.v1.ListAllowedEmailsRequest": { "type": "object", "properties": { @@ -20498,6 +21868,38 @@ "title": "ListHostClassesResponse", "additionalProperties": false }, + "spin.v1.ListHostProblemsRequest": { + "type": "object", + "properties": { + "hostId": { + "type": "string", + "title": "host_id", + "description": "The one host asked about; empty for every host of the fleet." + } + }, + "title": "ListHostProblemsRequest", + "additionalProperties": false + }, + "spin.v1.ListHostProblemsResponse": { + "type": "object", + "properties": { + "problems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.Problem" + }, + "title": "problems", + "description": "At most 20, hosts that are not connected first; problem_count is how many there are." + }, + "problemCount": { + "type": "integer", + "title": "problem_count", + "format": "int32" + } + }, + "title": "ListHostProblemsResponse", + "additionalProperties": false + }, "spin.v1.ListHostShellsRequest": { "type": "object", "properties": { @@ -20733,6 +22135,26 @@ "title": "ListNotificationsResponse", "additionalProperties": false }, + "spin.v1.ListOwnAgentThreadsRequest": { + "type": "object", + "title": "ListOwnAgentThreadsRequest", + "additionalProperties": false + }, + "spin.v1.ListOwnAgentThreadsResponse": { + "type": "object", + "properties": { + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.WorkspaceAgentThreads" + }, + "title": "workspaces", + "description": "One per workspace of the caller's whose supervisor reported a chat, in project then workspace\n name order; a workspace in the trash is not here." + } + }, + "title": "ListOwnAgentThreadsResponse", + "additionalProperties": false + }, "spin.v1.ListPasskeysRequest": { "type": "object", "title": "ListPasskeysRequest", @@ -20846,6 +22268,25 @@ "title": "ListProjectsResponse", "additionalProperties": false }, + "spin.v1.ListRefusedVolumesRequest": { + "type": "object", + "title": "ListRefusedVolumesRequest", + "additionalProperties": false + }, + "spin.v1.ListRefusedVolumesResponse": { + "type": "object", + "properties": { + "volumes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.RefusedVolume" + }, + "title": "volumes" + } + }, + "title": "ListRefusedVolumesResponse", + "additionalProperties": false + }, "spin.v1.ListRegistrationTokensRequest": { "type": "object", "properties": { @@ -21307,12 +22748,124 @@ "title": "ListTeamsResponse", "additionalProperties": false }, - "spin.v1.ListUserAPIKeysRequest": { + "spin.v1.ListTimelineRequest": { "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "hostId": { + "type": "string", + "title": "host_id" + } + }, + "title": "host_id" + }, + { + "type": "object", + "properties": { + "projectId": { + "type": "string", + "title": "project_id" + } + }, + "title": "project_id" + }, + { + "type": "object", + "properties": { + "teamId": { + "type": "string", + "title": "team_id" + } + }, + "title": "team_id" + }, + { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The one sample: a request names one of these at most." + } + }, + "title": "workspace_id" + } + ], + "unevaluatedProperties": false, + "not": { + "allOf": [ + { + "anyOf": [ + { + "required": [ + "hostId" + ] + }, + { + "required": [ + "projectId" + ] + }, + { + "required": [ + "teamId" + ] + }, + { + "required": [ + "workspaceId" + ] + } + ] + }, + { + "not": { + "oneOf": [ + { + "required": [ + "hostId" + ] + }, + { + "required": [ + "projectId" + ] + }, + { + "required": [ + "teamId" + ] + }, + { + "required": [ + "workspaceId" + ] + } + ] + } + } + ] + }, "properties": { - "userId": { - "type": "string", - "title": "user_id" + "since": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "since", + "description": "Inclusive; unset reads from as far back as the records go." + }, + "until": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "until", + "description": "Exclusive; unset is now." }, "page": { "allOf": [ @@ -21320,21 +22873,21 @@ "$ref": "#/components/schemas/spin.v1.PageRequest" } ], - "title": "page" + "title": "page", + "description": "page_size is at most 200, and zero is 50. total_count is not counted." } }, - "title": "ListUserAPIKeysRequest", - "additionalProperties": false + "title": "ListTimelineRequest" }, - "spin.v1.ListUserAPIKeysResponse": { + "spin.v1.ListTimelineResponse": { "type": "object", "properties": { - "apiKeys": { + "events": { "type": "array", "items": { - "$ref": "#/components/schemas/spin.v1.APIKey" + "$ref": "#/components/schemas/spin.v1.TimelineEvent" }, - "title": "api_keys" + "title": "events" }, "page": { "allOf": [ @@ -21343,14 +22896,24 @@ } ], "title": "page" + }, + "keptDays": { + "type": "integer", + "title": "kept_days", + "format": "int32", + "description": "kept_days is how long what the timeline reads is kept (ListWorkspaceTimelineResponse)." } }, - "title": "ListUserAPIKeysResponse", + "title": "ListTimelineResponse", "additionalProperties": false }, - "spin.v1.ListUsersRequest": { + "spin.v1.ListUserAPIKeysRequest": { "type": "object", "properties": { + "userId": { + "type": "string", + "title": "user_id" + }, "page": { "allOf": [ { @@ -21358,9 +22921,46 @@ } ], "title": "page" - }, - "query": { - "type": "string", + } + }, + "title": "ListUserAPIKeysRequest", + "additionalProperties": false + }, + "spin.v1.ListUserAPIKeysResponse": { + "type": "object", + "properties": { + "apiKeys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.APIKey" + }, + "title": "api_keys" + }, + "page": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.PageResponse" + } + ], + "title": "page" + } + }, + "title": "ListUserAPIKeysResponse", + "additionalProperties": false + }, + "spin.v1.ListUsersRequest": { + "type": "object", + "properties": { + "page": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.PageRequest" + } + ], + "title": "page" + }, + "query": { + "type": "string", "title": "query", "description": "Optional: filter by name or email" }, @@ -21475,6 +23075,72 @@ "title": "ListWorkspaceQuarantineDenialsResponse", "additionalProperties": false }, + "spin.v1.ListWorkspaceTimelineRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id" + }, + "since": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "since", + "description": "Inclusive; unset reads from as far back as the records go." + }, + "until": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "until", + "description": "Exclusive; unset is now." + }, + "page": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.PageRequest" + } + ], + "title": "page", + "description": "page_size is at most 200, and zero is 50. total_count is not counted." + } + }, + "title": "ListWorkspaceTimelineRequest", + "additionalProperties": false + }, + "spin.v1.ListWorkspaceTimelineResponse": { + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.TimelineEvent" + }, + "title": "events" + }, + "page": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.PageResponse" + } + ], + "title": "page" + }, + "keptDays": { + "type": "integer", + "title": "kept_days", + "format": "int32", + "description": "kept_days is how long what the timeline reads is kept: before it, an empty timeline is the\n records gone, not nothing having happened." + } + }, + "title": "ListWorkspaceTimelineResponse", + "additionalProperties": false + }, "spin.v1.ListWorkspacesRequest": { "type": "object", "properties": { @@ -22081,10 +23747,19 @@ "email": { "type": "boolean", "title": "email" + }, + "installationEvent": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.InstallationEventType" + } + ], + "title": "installation_event" } }, "title": "NotificationPreference", - "additionalProperties": false + "additionalProperties": false, + "description": "One kind of event and whether the caller is emailed about it: a workspace's event, or the\n installation's (an administrator's alone). Exactly one of event and installation_event is set." }, "spin.v1.ObservedDestination": { "type": "object", @@ -22385,6 +24060,28 @@ "title": "PasswordLoginResponse", "additionalProperties": false }, + "spin.v1.PlatformCost": { + "type": "object", + "properties": { + "role": { + "type": "string", + "title": "role", + "description": "As the resources carry it (on AWS, their spin:role tag); empty where they carry none, which\n is said as untagged." + }, + "service": { + "type": "string", + "title": "service" + }, + "micros": { + "type": "string", + "title": "micros", + "format": "int64" + } + }, + "title": "PlatformCost", + "additionalProperties": false, + "description": "What one role's resources of one of the cloud's services cost, in millionths of the bill's\n currency: the control plane, the proxy and the database are three roles of one EC2." + }, "spin.v1.PlatformImage": { "type": "object", "properties": { @@ -22695,6 +24392,15 @@ "title": "stored_bytes", "format": "int64", "description": "What it keeps in the object store, as storage-gc last read it; unset before a reading\n counted it." + }, + "buildWorkspaceId": { + "type": "string", + "title": "build_workspace_id", + "description": "The workspace that ran its build (workspaces.prebuild_id), while its row is kept, and whose it\n is: its events, logs and traces are its owner's to read, and no list of workspaces shows it." + }, + "buildWorkspaceOwnerId": { + "type": "string", + "title": "build_workspace_owner_id" } }, "title": "Prebuild", @@ -22741,6 +24447,37 @@ "Superseded and given back: its snapshot is being deleted." ] }, + "spin.v1.PrebuildsKept": { + "type": "object", + "properties": { + "storedBytes": { + "type": "string", + "title": "stored_bytes", + "format": "int64", + "description": "As the store was last read (Admin's Storage, PREBUILD)." + }, + "day": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "day", + "description": "The billed day the rate is from: its first instant, UTC. Unset, with month_micros, where no day\n before the reading billed the bucket, or the prebuilds keep nothing." + }, + "monthMicros": { + "type": [ + "string", + "null" + ], + "title": "month_micros", + "format": "int64" + } + }, + "title": "PrebuildsKept", + "additionalProperties": false, + "description": "What a project's prebuilds keep in the volumes' bucket, and what that would cost a month at what\n the last billed day charged: an estimate, never said without the day it is from." + }, "spin.v1.PreemptionSource": { "type": "string", "title": "PreemptionSource", @@ -22752,6 +24489,237 @@ ], "description": "PreemptionSource mirrors spin.agent.v1.PreemptionSource, which is what reaches the host." }, + "spin.v1.Problem": { + "type": "object", + "properties": { + "cause": { + "type": "string", + "title": "cause", + "description": "cause says in a line what was found." + }, + "since": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "since", + "description": "since is when what decided it was so, as HealthCheck.known_at is: a host's command stream\n last heard, a check's last run, a job's failed run. Unset where nothing says." + }, + "impact": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.ProblemImpact" + } + ], + "title": "impact" + }, + "evidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.ProblemEvidence" + }, + "title": "evidence", + "description": "What shows it, each a thing the console has a page for." + }, + "runbookId": { + "type": "string", + "title": "runbook_id", + "description": "The runbook to follow (Runbook.id), of the runbooks this release serves: never empty." + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.ProblemAction" + }, + "title": "actions", + "description": "What the console itself can do about it, each on the host or the job the problem is of." + }, + "hostId": { + "type": "string", + "title": "host_id", + "description": "The host the problem is of, and the name its machine goes by; empty for one that is not a\n host's." + }, + "hostname": { + "type": "string", + "title": "hostname" + }, + "job": { + "type": "string", + "title": "job", + "description": "The job (Job.name) the problem is of, and its title; empty for one that is not a job's." + }, + "jobTitle": { + "type": "string", + "title": "job_title" + } + }, + "title": "Problem", + "additionalProperties": false, + "description": "Problem is one thing wrong with the installation, said whole: why, what it takes down, what\n shows it and what to do. Nothing is kept of it - it is read off the installation as it is now,\n and is gone when what caused it is." + }, + "spin.v1.ProblemAction": { + "type": "string", + "title": "ProblemAction", + "enum": [ + "PROBLEM_ACTION_UNSPECIFIED", + "PROBLEM_ACTION_CORDON_HOST", + "PROBLEM_ACTION_DRAIN_HOST", + "PROBLEM_ACTION_RUN_JOB", + "PROBLEM_ACTION_RECOVER_WORKSPACE" + ], + "description": "ProblemAction is something the console can do about a problem, each an RPC it has.\n\n- `PROBLEM_ACTION_CORDON_HOST`: DrainHost without suspend: the host takes no new workspace and keeps what it runs.\n- `PROBLEM_ACTION_DRAIN_HOST`: DrainHost with suspend: its workspaces are moved to other hosts.\n- `PROBLEM_ACTION_RUN_JOB`: RunJobNow: the job is run again, now.\n- `PROBLEM_ACTION_RECOVER_WORKSPACE`: RecoverUserWorkspace, of each workspace the problem names: its disk is on a host that is gone,\n and it runs again only once moved off it.", + "x-enum-descriptions": [ + "", + "DrainHost without suspend: the host takes no new workspace and keeps what it runs.", + "DrainHost with suspend: its workspaces are moved to other hosts.", + "RunJobNow: the job is run again, now.", + "RecoverUserWorkspace, of each workspace the problem names: its disk is on a host that is gone,\n and it runs again only once moved off it." + ] + }, + "spin.v1.ProblemEvidence": { + "type": "object", + "anyOf": [ + { + "type": "object", + "properties": { + "hostId": { + "type": "string", + "title": "host_id", + "description": "A host: its page under Admin → Runners." + } + }, + "title": "host_id" + }, + { + "type": "object", + "properties": { + "job": { + "type": "string", + "title": "job", + "description": "A job (Job.name): its runs under Admin → Jobs." + } + }, + "title": "job" + }, + { + "type": "object", + "properties": { + "logQuery": { + "type": "string", + "title": "log_query", + "description": "A query of the log store, as SearchPlatformLogs takes it." + } + }, + "title": "log_query" + }, + { + "type": "object", + "properties": { + "traceId": { + "type": "string", + "title": "trace_id", + "description": "A trace: a failed job run's." + } + }, + "title": "trace_id" + } + ], + "unevaluatedProperties": false, + "not": { + "allOf": [ + { + "anyOf": [ + { + "required": [ + "hostId" + ] + }, + { + "required": [ + "job" + ] + }, + { + "required": [ + "logQuery" + ] + }, + { + "required": [ + "traceId" + ] + } + ] + }, + { + "not": { + "oneOf": [ + { + "required": [ + "hostId" + ] + }, + { + "required": [ + "job" + ] + }, + { + "required": [ + "logQuery" + ] + }, + { + "required": [ + "traceId" + ] + } + ] + } + } + ] + }, + "title": "ProblemEvidence", + "description": "ProblemEvidence names one thing that shows a problem. The console turns each into a link to\n its own page; none is a URL." + }, + "spin.v1.ProblemImpact": { + "type": "object", + "properties": { + "stops": { + "type": "string", + "title": "stops", + "description": "What stops working, in a line: \"log lines are not kept\". Empty where the rows say it." + }, + "runningWorkspaces": { + "type": "integer", + "title": "running_workspaces", + "format": "int32", + "description": "The workspaces it affects, counted whole: running on the host now, and at rest with their\n disk only there - stopped or failed, each one's next start is on that host. For workspaces\n waiting for a host, waiting_workspaces." + }, + "restingWorkspaces": { + "type": "integer", + "title": "resting_workspaces", + "format": "int32" + }, + "waitingWorkspaces": { + "type": "integer", + "title": "waiting_workspaces", + "format": "int32" + }, + "workspaces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.AffectedWorkspace" + }, + "title": "workspaces", + "description": "At most 10 of them, the running first, then by name: the counts above are the whole." + } + }, + "title": "ProblemImpact", + "additionalProperties": false, + "description": "ProblemImpact is what a problem takes down: the workspaces under it as rows of the database,\n or, for a part with nothing under it, what stops working." + }, "spin.v1.Project": { "type": "object", "properties": { @@ -23050,54 +25018,31 @@ "spin.v1.QuarantineWorkspaceResponse": { "type": "object", "properties": { - "workspace": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "name": { + "type": "string", + "title": "name" + }, + "state": { "allOf": [ { - "$ref": "#/components/schemas/spin.v1.Workspace" + "$ref": "#/components/schemas/spin.v1.WorkspaceState" } ], - "title": "workspace" - } - }, - "title": "QuarantineWorkspaceResponse", - "additionalProperties": false - }, - "spin.v1.RateLimitSettings": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "title": "enabled", - "description": "Enable rate limiting" - }, - "authRequestsPerMinute": { - "type": "integer", - "title": "auth_requests_per_minute", - "format": "int32", - "description": "Auth endpoints (login, token exchange, etc.) Requests per minute for auth endpoints" - }, - "authBurst": { - "type": "integer", - "title": "auth_burst", - "format": "int32", - "description": "Burst allowance for auth endpoints" - }, - "apiRequestsPerMinute": { - "type": "integer", - "title": "api_requests_per_minute", - "format": "int32", - "description": "API endpoints (all other endpoints) Requests per minute for API endpoints" + "title": "state" }, - "apiBurst": { - "type": "integer", - "title": "api_burst", - "format": "int32", - "description": "Burst allowance for API endpoints" + "quarantineHeld": { + "type": "boolean", + "title": "quarantine_held", + "description": "Held, as Workspace.quarantine_held: a running workspace answers STOPPING, and this is what\n says the stop is the quarantine's." } }, - "title": "RateLimitSettings", + "title": "QuarantineWorkspaceResponse", "additionalProperties": false, - "description": "RateLimitSettings configures API rate limiting" + "description": "The workspace as the quarantine left it, and no more of it: an administrator acts on a\n workspace that need not be theirs, and the rest of its row - its env - is its owner's (I27)." }, "spin.v1.ReadFileRequest": { "type": "object", @@ -23127,6 +25072,43 @@ "title": "ReadFileResponse", "additionalProperties": false }, + "spin.v1.RecoverUserWorkspaceRequest": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The workspace's id." + }, + "acceptDataLoss": { + "type": "boolean", + "title": "accept_data_loss", + "description": "False asks what it would cost. True accepts that cost, for its owner, and does it." + }, + "reason": { + "type": "string", + "title": "reason", + "description": "Why: required, kept on the audit entry and on the workspace's history for its owner." + } + }, + "title": "RecoverUserWorkspaceRequest", + "additionalProperties": false + }, + "spin.v1.RecoverUserWorkspaceResponse": { + "type": "object", + "properties": { + "recovery": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.RecoverWorkspaceResponse" + } + ], + "title": "recovery" + } + }, + "title": "RecoverUserWorkspaceResponse", + "additionalProperties": false + }, "spin.v1.RecoverWorkspaceRequest": { "type": "object", "properties": { @@ -23202,14 +25184,70 @@ "title": "port", "description": "For tcp and udp; zero otherwise." }, - "packets": { + "packets": { + "type": "string", + "title": "packets", + "format": "int64" + } + }, + "title": "RefusedDestination", + "additionalProperties": false + }, + "spin.v1.RefusedVolume": { + "type": "object", + "properties": { + "volumeId": { + "type": "string", + "title": "volume_id" + }, + "refusal": { + "type": "string", + "title": "refusal", + "description": "The token an alert keys on, and the sentence the host sent with it (VolumeStatus)." + }, + "refusalDetail": { + "type": "string", + "title": "refusal_detail" + }, + "hostId": { + "type": "string", + "title": "host_id", + "description": "The host refusing it; empty for a disk placed nowhere." + }, + "hostName": { + "type": "string", + "title": "host_name" + }, + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The workspace it is the disk of; empty for a disk no workspace runs on." + }, + "workspaceName": { + "type": "string", + "title": "workspace_name" + }, + "workspaceState": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "workspace_state" + }, + "ownerId": { + "type": "string", + "title": "owner_id", + "description": "Whose the workspace is." + }, + "ownerEmail": { "type": "string", - "title": "packets", - "format": "int64" + "title": "owner_email" } }, - "title": "RefusedDestination", - "additionalProperties": false + "title": "RefusedVolume", + "additionalProperties": false, + "description": "RefusedVolume is a disk its host is not serving, and the workspace that cannot run for it." }, "spin.v1.RegisterPasskeyRequest": { "type": "object", @@ -23413,17 +25451,30 @@ "spin.v1.ReleaseWorkspaceQuarantineResponse": { "type": "object", "properties": { - "workspace": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "name": { + "type": "string", + "title": "name" + }, + "state": { "allOf": [ { - "$ref": "#/components/schemas/spin.v1.Workspace" + "$ref": "#/components/schemas/spin.v1.WorkspaceState" } ], - "title": "workspace" + "title": "state" + }, + "quarantineHeld": { + "type": "boolean", + "title": "quarantine_held" } }, "title": "ReleaseWorkspaceQuarantineResponse", - "additionalProperties": false + "additionalProperties": false, + "description": "The workspace as the release left it, and no more of it, as QuarantineWorkspaceResponse." }, "spin.v1.ReleaseWorkspaceResourcesRequest": { "type": "object", @@ -23473,6 +25524,16 @@ "id": { "type": "string", "title": "id" + }, + "taken": { + "type": "boolean", + "title": "taken", + "description": "The host is believed taken - compromised - rather than only no longer wanted. Its credentials\n are deleted and its stream cut before its group is asked anything, and every team's disk whose\n key it was given is marked for re-keying. A routine removal - a scale-in, a machine reclaimed -\n leaves it unset, and marks none." + }, + "leaveWorkspaces": { + "type": "boolean", + "title": "leave_workspaces", + "description": "Remove a taken host although it is the only home of workspaces' disks. Refused without it,\n naming them; with it each runs again only once it is recovered onto another host\n (RecoverWorkspace), which gives up what this host had not published. Only with taken: a\n routine removal loses no disk." } }, "title": "RemoveHostRequest", @@ -23499,6 +25560,17 @@ "type": "boolean", "title": "forgotten", "description": "forgotten is the host's row gone. A host given back while connected is not: it drains under\n its termination hook, and its row goes when its stream ends." + }, + "markedForRekey": { + "type": "integer", + "title": "marked_for_rekey", + "format": "int32", + "description": "marked_for_rekey is how many teams' disks were marked for re-keying: those whose key a host\n removed as taken was given. Zero for a routine removal." + }, + "kept": { + "type": "string", + "title": "kept", + "description": "kept is why the row was not forgotten, where it was not and should have been: the workspace\n whose disk is only there, or the volume that would not leave it and whose that is. Empty\n when forgotten, and for a host given back while connected." } }, "title": "RemoveHostResponse", @@ -23723,7 +25795,7 @@ "type": "integer", "title": "rpo_seconds", "format": "int32", - "description": "How far behind the object store a workspace of it may fall, in seconds; 0 is the\n installation's (InstallationSettings.volume_rpo_seconds)." + "description": "How far behind the object store a workspace of it may fall, in seconds; 0 is the\n installation's (the setting volume_rpo_seconds)." }, "hostClass": { "type": "string", @@ -23740,6 +25812,12 @@ "type": "boolean", "title": "from_config", "description": "Seeded or applied from the installation's config file (the release's presets where it names\n none); false is one an administrator added. An edit since does not change it." + }, + "minMemoryMb": { + "type": "integer", + "title": "min_memory_mb", + "format": "int32", + "description": "The least any preset may start its workspaces at, in MB: memory_mb for a fixed one,\n burst_floor_memory_mb for a burst one. A create or update below it is refused; one written\n before it was asked keeps what it has until an administrator changes it." } }, "title": "ResourcePreset", @@ -23966,6 +26044,24 @@ "title": "RevokeUserAPIKeyResponse", "additionalProperties": false }, + "spin.v1.RoleCost": { + "type": "object", + "properties": { + "role": { + "type": "string", + "title": "role", + "description": "Empty where the resources carry none." + }, + "micros": { + "type": "string", + "title": "micros", + "format": "int64" + } + }, + "title": "RoleCost", + "additionalProperties": false, + "description": "What one role cost on one day, its services summed." + }, "spin.v1.RunJobNowRequest": { "type": "object", "properties": { @@ -24464,46 +26560,15 @@ "additionalProperties": false, "description": "The spans found, each marked as about the workspace asked for, and how many the store holds." }, - "spin.v1.SecuritySettings": { + "spin.v1.SendTestAlertRequest": { "type": "object", - "properties": { - "rateLimit": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.RateLimitSettings" - } - ], - "title": "rate_limit", - "description": "Rate limiting configuration" - }, - "sessionDurationHours": { - "type": "integer", - "title": "session_duration_hours", - "format": "int32", - "description": "Session configuration Session token lifetime in hours (default: 168 = 7 days)" - }, - "allowWorkspaceSharing": { - "type": "boolean", - "title": "allow_workspace_sharing", - "description": "Access control Allow users to share workspaces" - }, - "allowPublicWorkspaces": { - "type": "boolean", - "title": "allow_public_workspaces", - "description": "Allow publicly accessible workspace URLs" - }, - "updatedAt": { - "allOf": [ - { - "$ref": "#/components/schemas/google.protobuf.Timestamp" - } - ], - "title": "updated_at" - } - }, - "title": "SecuritySettings", - "additionalProperties": false, - "description": "SecuritySettings contains security-related configuration" + "title": "SendTestAlertRequest", + "additionalProperties": false + }, + "spin.v1.SendTestAlertResponse": { + "type": "object", + "title": "SendTestAlertResponse", + "additionalProperties": false }, "spin.v1.ServiceCost": { "type": "object", @@ -24760,34 +26825,36 @@ "title": "SetPortAccessResponse", "additionalProperties": false }, - "spin.v1.SetRegistrationModeRequest": { + "spin.v1.SetSettingsRequest": { "type": "object", "properties": { - "mode": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.RegistrationMode" - } - ], - "title": "mode" + "values": { + "type": "object", + "title": "values", + "additionalProperties": { + "type": "string", + "title": "value" + }, + "description": "The settings to write, by key, as text of each one's type. Only ADMIN settings are taken." } }, - "title": "SetRegistrationModeRequest", + "title": "SetSettingsRequest", "additionalProperties": false }, - "spin.v1.SetRegistrationModeResponse": { + "spin.v1.SetSettingsResponse": { "type": "object", "properties": { - "mode": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.RegistrationMode" - } - ], - "title": "mode" + "values": { + "type": "object", + "title": "values", + "additionalProperties": { + "type": "string", + "title": "value" + }, + "description": "Every value in force once written, as GetSettings answers them." } }, - "title": "SetRegistrationModeResponse", + "title": "SetSettingsResponse", "additionalProperties": false }, "spin.v1.SetSnapshotVisibilityRequest": { @@ -24877,7 +26944,7 @@ "ref": { "type": "string", "title": "ref", - "description": "In place of value: the ARN of a Secrets Manager secret or a Parameter Store parameter the team\n keeps it in, read as the team (InstallationSettings.team_secrets_role) now and whenever a host\n asks for it." + "description": "In place of value: the ARN of a Secrets Manager secret or a Parameter Store parameter the team\n keeps it in, read as the team (the setting team_secrets_role) now and whenever a host\n asks for it." } }, "title": "SetTeamSecretRequest", @@ -24908,18 +26975,32 @@ "spin.v1.SetWorkspaceBandwidthResponse": { "type": "object", "properties": { - "workspace": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "name": { + "type": "string", + "title": "name" + }, + "state": { "allOf": [ { - "$ref": "#/components/schemas/spin.v1.Workspace" + "$ref": "#/components/schemas/spin.v1.WorkspaceState" } ], - "title": "workspace" + "title": "state" + }, + "bandwidthMbps": { + "type": "integer", + "title": "bandwidth_mbps", + "format": "int32", + "description": "Megabits a second each way now in force; zero is unshaped." } }, "title": "SetWorkspaceBandwidthResponse", "additionalProperties": false, - "description": "SetWorkspaceBandwidthResponse is the workspace as recorded, which its host is then sent: a host\n that cannot take the value leaves the machine as it was and is asked again by the next set." + "description": "SetWorkspaceBandwidthResponse is the bandwidth as recorded, which its host is then sent: a host\n that cannot take the value leaves the machine as it was and is asked again by the next set. It\n names the workspace and no more of it: the rest of its row - its env - is its owner's (I27)." }, "spin.v1.SetWorkspaceNetworkTagsRequest": { "type": "object", @@ -24991,6 +27072,91 @@ "title": "SetWorkspaceNetworkWatchResponse", "additionalProperties": false }, + "spin.v1.SettingDefinition": { + "type": "object", + "properties": { + "key": { + "type": "string", + "title": "key" + }, + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.SettingType" + } + ], + "title": "type" + }, + "defaultValue": { + "type": "string", + "title": "default_value", + "description": "What is in force where the installation says nothing, as text of the type." + }, + "min": { + "type": [ + "string", + "null" + ], + "title": "min", + "format": "int64" + }, + "max": { + "type": [ + "string", + "null" + ], + "title": "max", + "format": "int64" + }, + "choices": { + "type": "array", + "items": { + "type": "string" + }, + "title": "choices", + "description": "The values a STRING setting takes; empty is any." + }, + "reach": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.SettingReach" + } + ], + "title": "reach", + "description": "What it touches once saved." + }, + "restart": { + "type": "boolean", + "title": "restart", + "description": "It is read when the control plane starts: a change is in force after its next restart." + }, + "section": { + "type": "string", + "title": "section", + "description": "Which page it is on: general, workspaces, fleet, network, security, registration,\n telemetry, logs, bill, email, storage, installation, control_plane." + }, + "label": { + "type": "string", + "title": "label", + "description": "A few words that name it, and a sentence that says what it does and what its zero means." + }, + "summary": { + "type": "string", + "title": "summary" + }, + "writer": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.SettingWriter" + } + ], + "title": "writer" + } + }, + "title": "SettingDefinition", + "additionalProperties": false, + "description": "SettingDefinition is one setting of the installation as the code defines it." + }, "spin.v1.SettingReach": { "type": "string", "title": "SettingReach", @@ -25002,7 +27168,7 @@ "SETTING_REACH_NEXT_REQUEST", "SETTING_REACH_NEXT_PASS" ], - "description": "SettingReach is what a saved installation setting touches, and when: each setting declares one\n beside its key (internal/controlplane/settings), so the screen that edits it can say so before\n the save.\n\n- `SETTING_REACH_RUNNING_WORKSPACES`: The workspaces running now, at the control plane's next look at them.\n- `SETTING_REACH_NEW_WORKSPACES`: Workspaces created from now on; those that exist keep what they were made with.\n- `SETTING_REACH_NEXT_HEARTBEAT`: Each runner at its next heartbeat, and the workspaces running on it.\n- `SETTING_REACH_NEXT_REQUEST`: What is asked from now on - a project made, a shell opened, a workspace deleted; what was\n done before keeps the terms it was done under.\n- `SETTING_REACH_NEXT_PASS`: The next pass of the job that applies it: the autoscaler, the prebuilds' retirement, the\n collector's read of its policy, the daily look for a release.", + "description": "SettingReach is what a saved installation setting touches, and when: each setting's definition\n declares one (internal/controlplane/settings), so the screen that edits it can say so before\n the save.\n\n- `SETTING_REACH_RUNNING_WORKSPACES`: The workspaces running now, at the control plane's next look at them.\n- `SETTING_REACH_NEW_WORKSPACES`: Workspaces created from now on; those that exist keep what they were made with.\n- `SETTING_REACH_NEXT_HEARTBEAT`: Each runner at its next heartbeat, and the workspaces running on it.\n- `SETTING_REACH_NEXT_REQUEST`: What is asked from now on - a project made, a shell opened, a workspace deleted; what was\n done before keeps the terms it was done under.\n- `SETTING_REACH_NEXT_PASS`: The next pass of the job that applies it: the autoscaler, the prebuilds' retirement, the\n collector's read of its policy, the daily look for a release.", "x-enum-descriptions": [ "", "The workspaces running now, at the control plane's next look at them.", @@ -25012,6 +27178,48 @@ "The next pass of the job that applies it: the autoscaler, the prebuilds' retirement, the\n collector's read of its policy, the daily look for a release." ] }, + "spin.v1.SettingType": { + "type": "string", + "title": "SettingType", + "enum": [ + "SETTING_TYPE_UNSPECIFIED", + "SETTING_TYPE_INT", + "SETTING_TYPE_BOOL", + "SETTING_TYPE_STRING", + "SETTING_TYPE_LIST", + "SETTING_TYPE_JSON", + "SETTING_TYPE_DURATION" + ], + "description": "SettingType is how a setting's value is written as text, which is how GetSettings answers it\n and SetSettings takes it.\n\n- `SETTING_TYPE_INT`: A whole number in decimal, within the definition's min and max.\n- `SETTING_TYPE_BOOL`: \"true\" or \"false\".\n- `SETTING_TYPE_STRING`: Text; a definition with choices takes only those.\n- `SETTING_TYPE_LIST`: A JSON array of strings.\n- `SETTING_TYPE_JSON`: A JSON document, as the code that reads it defines it.\n- `SETTING_TYPE_DURATION`: A Go duration (\"60s\"); min and max are seconds.", + "x-enum-descriptions": [ + "", + "A whole number in decimal, within the definition's min and max.", + "\"true\" or \"false\".", + "Text; a definition with choices takes only those.", + "A JSON array of strings.", + "A JSON document, as the code that reads it defines it.", + "A Go duration (\"60s\"); min and max are seconds." + ] + }, + "spin.v1.SettingWriter": { + "type": "string", + "title": "SettingWriter", + "enum": [ + "SETTING_WRITER_UNSPECIFIED", + "SETTING_WRITER_ADMIN", + "SETTING_WRITER_FILE", + "SETTING_WRITER_STORE", + "SETTING_WRITER_CONTROL_PLANE" + ], + "description": "SettingWriter is who writes a setting, and so where an administrator changes it.\n\n- `SETTING_WRITER_ADMIN`: SetSettings, and the installation's file seeds it.\n- `SETTING_WRITER_FILE`: The installation's file alone: what whoever made the installation knows, and a change of\n which the dashboard cannot make whole - the proxy holds the base domain too.\n- `SETTING_WRITER_STORE`: A page of its own, which checks it against the real thing before it is written: the object\n store, the mail server.\n- `SETTING_WRITER_CONTROL_PLANE`: The control plane, for itself.", + "x-enum-descriptions": [ + "", + "SetSettings, and the installation's file seeds it.", + "The installation's file alone: what whoever made the installation knows, and a change of\n which the dashboard cannot make whole - the proxy holds the base domain too.", + "A page of its own, which checks it against the real thing before it is written: the object\n store, the mail server.", + "The control plane, for itself." + ] + }, "spin.v1.ShareProjectRequest": { "type": "object", "properties": { @@ -25302,6 +27510,41 @@ "additionalProperties": false, "description": "Span is one operation of a trace, as OpenTelemetry names its parts." }, + "spin.v1.StandbyCost": { + "type": "object", + "properties": { + "day": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "day", + "description": "The billed day it is from: its first instant, UTC." + }, + "platform": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.RoleCost" + }, + "title": "platform", + "description": "The platform by role, costliest first." + }, + "idleMicros": { + "type": "string", + "title": "idle_micros", + "format": "int64" + }, + "unstoredMicros": { + "type": "string", + "title": "unstored_micros", + "format": "int64" + } + }, + "title": "StandbyCost", + "additionalProperties": false, + "description": "What the installation cost on its last billed day before the reading with nobody's work\n counted: the platform, the runners' capacity no workspace held, and the bucket's bytes no\n workspace keeps. An estimate of a day nobody works: that day is over, the next may be charged\n otherwise, and a day people worked held some of the runners - so it is never said without its\n day (finops.StandbyOf)." + }, "spin.v1.StartAppCreationRequest": { "type": "object", "properties": { @@ -25592,6 +27835,52 @@ "The supervisor reports a failing health check." ] }, + "spin.v1.StopUserWorkspaceRequest": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id", + "description": "The workspace's id: an administrator names no workspace of somebody else's by its name." + }, + "force": { + "type": "boolean", + "title": "force", + "description": "Give up what only its host has rather than publish it first, as StopWorkspace's force does." + }, + "reason": { + "type": "string", + "title": "reason", + "description": "Why, kept on the audit entry." + } + }, + "title": "StopUserWorkspaceRequest", + "additionalProperties": false + }, + "spin.v1.StopUserWorkspaceResponse": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "name": { + "type": "string", + "title": "name" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "state" + } + }, + "title": "StopUserWorkspaceResponse", + "additionalProperties": false, + "description": "The workspace as the stop left it, and no more of it: the rest of its row is its owner's." + }, "spin.v1.StopWorkspaceRequest": { "type": "object", "properties": { @@ -26092,6 +28381,30 @@ "title": "TeamSecret", "additionalProperties": false }, + "spin.v1.TeamWorkspaceCost": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "name": { + "type": "string", + "title": "name" + }, + "share": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.CostShare" + } + ], + "title": "share" + } + }, + "title": "TeamWorkspaceCost", + "additionalProperties": false, + "description": "What one of a team's workspaces cost in the month." + }, "spin.v1.TelemetryFlow": { "type": "object", "properties": { @@ -26258,6 +28571,118 @@ "title": "ThirdPartyTermsItem", "additionalProperties": false }, + "spin.v1.TimelineActor": { + "type": "string", + "title": "TimelineActor", + "enum": [ + "TIMELINE_ACTOR_UNSPECIFIED", + "TIMELINE_ACTOR_PERSON", + "TIMELINE_ACTOR_CONTROL_PLANE", + "TIMELINE_ACTOR_HOST" + ], + "description": "TimelineActor is who or what did what a TimelineEvent says.\n\n- `TIMELINE_ACTOR_PERSON`: A person signed in, named by actor_id.\n- `TIMELINE_ACTOR_CONTROL_PLANE`: The control plane, on its own: a reconciliation, a sweep, a job, a decision nobody asked for.\n- `TIMELINE_ACTOR_HOST`: A host, of itself: it joined, said it was going, lost its stream or came back.", + "x-enum-descriptions": [ + "", + "A person signed in, named by actor_id.", + "The control plane, on its own: a reconciliation, a sweep, a job, a decision nobody asked for.", + "A host, of itself: it joined, said it was going, lost its stream or came back." + ] + }, + "spin.v1.TimelineEvent": { + "type": "object", + "properties": { + "source": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.TimelineSource" + } + ], + "title": "source", + "description": "The record it is read from, and that record's id there: the evidence a reader opens." + }, + "id": { + "type": "string", + "title": "id" + }, + "at": { + "allOf": [ + { + "$ref": "#/components/schemas/google.protobuf.Timestamp" + } + ], + "title": "at" + }, + "actor": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.TimelineActor" + } + ], + "title": "actor", + "description": "Who or what did it." + }, + "actorId": { + "type": "string", + "title": "actor_id", + "description": "The person, when actor is PERSON: empty for one whose account has since been deleted, and\n the email is what their row says now." + }, + "actorEmail": { + "type": "string", + "title": "actor_email" + }, + "what": { + "type": "string", + "title": "what", + "description": "What happened, in its source's own words: an audit action (host_state_changed), a workspace\n event type without its prefix (FAILED), a failed job run's job (storage-gc), or for a\n prebuild prebuild_started, prebuild_built or prebuild_failed." + }, + "resourceType": { + "type": "string", + "title": "resource_type", + "description": "What it happened to: an audit entry's resource_type and id (workspace, host, settings, job by\n its name), the workspace of a workspace event, the job of a run, the project of a prebuild." + }, + "resourceId": { + "type": "string", + "title": "resource_id" + }, + "resourceName": { + "type": "string", + "title": "resource_name", + "description": "The name a person knows it by, where the installation still has its row: a workspace's, a\n host's machine's, a team's, a project's. Empty otherwise." + }, + "details": { + "type": "string", + "title": "details", + "description": "The JSON the record keeps, verbatim: an audit entry's details, a workspace event's, a failed\n run's counts and error, a prebuild's ref, commit and error." + }, + "traceId": { + "type": "string", + "title": "trace_id", + "description": "The trace of the request or the run that did it; empty when none was sampled. Whether the\n store kept it is LogService.KeptTraces's to say." + } + }, + "title": "TimelineEvent", + "additionalProperties": false, + "description": "TimelineEvent is one thing that happened, read from the record that keeps it - the audit log, a\n workspace's events, a job's runs, a project's prebuilds - and never a copy of it." + }, + "spin.v1.TimelineSource": { + "type": "string", + "title": "TimelineSource", + "enum": [ + "TIMELINE_SOURCE_UNSPECIFIED", + "TIMELINE_SOURCE_AUDIT", + "TIMELINE_SOURCE_WORKSPACE_EVENT", + "TIMELINE_SOURCE_JOB_RUN", + "TIMELINE_SOURCE_PREBUILD" + ], + "description": "TimelineSource is the record a TimelineEvent is read from.\n\n- `TIMELINE_SOURCE_AUDIT`: An audit entry (AuditEntry.id).\n- `TIMELINE_SOURCE_WORKSPACE_EVENT`: A workspace's event (WorkspaceEvent.id).\n- `TIMELINE_SOURCE_JOB_RUN`: A job's run that failed (JobRun.run_id).\n- `TIMELINE_SOURCE_PREBUILD`: A project's prebuild (Prebuild.id).", + "x-enum-descriptions": [ + "", + "An audit entry (AuditEntry.id).", + "A workspace's event (WorkspaceEvent.id).", + "A job's run that failed (JobRun.run_id).", + "A project's prebuild (Prebuild.id)." + ] + }, "spin.v1.UndeleteWorkspaceRequest": { "type": "object", "properties": { @@ -26420,45 +28845,6 @@ "title": "UpdateHostClassResponse", "additionalProperties": false }, - "spin.v1.UpdateInstallationSettingsRequest": { - "type": "object", - "properties": { - "settings": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.InstallationSettings" - } - ], - "title": "settings" - }, - "updateMask": { - "allOf": [ - { - "$ref": "#/components/schemas/google.protobuf.FieldMask" - } - ], - "title": "update_mask", - "description": "Required: only the named fields of settings are written, so a form that edits one\n setting cannot reset the others to their zero values." - } - }, - "title": "UpdateInstallationSettingsRequest", - "additionalProperties": false - }, - "spin.v1.UpdateInstallationSettingsResponse": { - "type": "object", - "properties": { - "settings": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.InstallationSettings" - } - ], - "title": "settings" - } - }, - "title": "UpdateInstallationSettingsResponse", - "additionalProperties": false - }, "spin.v1.UpdateNetworkPolicyRequest": { "type": "object", "properties": { @@ -26704,36 +29090,6 @@ "title": "UpdateResourcePresetResponse", "additionalProperties": false }, - "spin.v1.UpdateSecuritySettingsRequest": { - "type": "object", - "properties": { - "settings": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.SecuritySettings" - } - ], - "title": "settings" - } - }, - "title": "UpdateSecuritySettingsRequest", - "additionalProperties": false - }, - "spin.v1.UpdateSecuritySettingsResponse": { - "type": "object", - "properties": { - "settings": { - "allOf": [ - { - "$ref": "#/components/schemas/spin.v1.SecuritySettings" - } - ], - "title": "settings" - } - }, - "title": "UpdateSecuritySettingsResponse", - "additionalProperties": false - }, "spin.v1.UpdateStorageSettingsRequest": { "type": "object", "properties": { @@ -27742,6 +30098,52 @@ "Boot a suspended workspace from its disk, dropping its memory (StartWorkspaceRequest.from_disk):\n offered when that memory's machine is run by no host (Workspace.memory_machine_gone)." ] }, + "spin.v1.WorkspaceAgentThreads": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "workspaceName": { + "type": "string", + "title": "workspace_name" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "state" + }, + "projectId": { + "type": "string", + "title": "project_id", + "description": "Empty for a workspace that opened no project." + }, + "projectName": { + "type": "string", + "title": "project_name" + }, + "environmentId": { + "type": "string", + "title": "environment_id", + "description": "As ListAgentThreadsResponse.environment_id: the first part of a thread's page." + }, + "threads": { + "type": "array", + "items": { + "$ref": "#/components/schemas/spin.v1.AgentThread" + }, + "title": "threads", + "description": "Newest first." + } + }, + "title": "WorkspaceAgentThreads", + "additionalProperties": false, + "description": "WorkspaceAgentThreads is one workspace's chats with what a rail names it by: the workspace, its\n state, and the project it opened." + }, "spin.v1.WorkspaceAttempt": { "type": "object", "properties": { @@ -27840,6 +30242,61 @@ "additionalProperties": false, "description": "What one workspace cost in the month, in millionths of the bill's currency, and whose it was on\n the last day it cost anything." }, + "spin.v1.WorkspaceDeparture": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "title": "workspace_id" + }, + "workspaceName": { + "type": "string", + "title": "workspace_name" + }, + "ownerEmail": { + "type": "string", + "title": "owner_email" + }, + "state": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.WorkspaceState" + } + ], + "title": "state" + }, + "drain": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.DepartureFate" + } + ], + "title": "drain", + "description": "What a drain that moves the host's workspaces (DrainHostRequest.suspend) does to it: MOVES or\n STAYS." + }, + "removal": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.DepartureFate" + } + ], + "title": "removal", + "description": "What Host.removal does to it: LOSES_PLACE or HOLDS. UNSPECIFIED where the host has no removal." + }, + "taken": { + "allOf": [ + { + "$ref": "#/components/schemas/spin.v1.DepartureFate" + } + ], + "title": "taken", + "description": "What removing the host as taken (RemoveHostRequest.taken) does to it: LOSES_PLACE or STOPS.\n UNSPECIFIED for a host that is revoked, whose revoke already is that." + } + }, + "title": "WorkspaceDeparture", + "additionalProperties": false, + "description": "WorkspaceDeparture is one workspace on a host and what each way of taking the host out does to it." + }, "spin.v1.WorkspaceDurability": { "type": "object", "properties": { @@ -27957,9 +30414,12 @@ "WORKSPACE_EVENT_TYPE_KEPT", "WORKSPACE_EVENT_TYPE_UNRESPONSIVE", "WORKSPACE_EVENT_TYPE_REHOMED", - "WORKSPACE_EVENT_TYPE_HELD" + "WORKSPACE_EVENT_TYPE_HELD", + "WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN", + "WORKSPACE_EVENT_TYPE_OUT_OF_MEMORY", + "WORKSPACE_EVENT_TYPE_OVERSIZED" ], - "description": "- `WORKSPACE_EVENT_TYPE_CREATED`: Workspace was created\n- `WORKSPACE_EVENT_TYPE_STARTED`: Workspace was started\n- `WORKSPACE_EVENT_TYPE_STOPPED`: Workspace was stopped; its disk is kept, and no snapshot is taken\n- `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`: Manual snapshot\n- `WORKSPACE_EVENT_TYPE_DELETED`: Workspace was deleted\n- `WORKSPACE_EVENT_TYPE_FAILED`: Workspace start failed\n- `WORKSPACE_EVENT_TYPE_RESTORED`: Its disk was put back to a snapshot\n- `WORKSPACE_EVENT_TYPE_RESUMED`: It went on from a snapshot's memory\n- `WORKSPACE_EVENT_TYPE_BOOTED`: Suspended, its memory could not be resumed, and its disk was booted\n- `WORKSPACE_EVENT_TYPE_QUARANTINED`: An administrator held it: stopped, and startable by nobody until released\n- `WORKSPACE_EVENT_TYPE_UNQUARANTINED`: The hold was released: back to stopped, where it was held from\n- `WORKSPACE_EVENT_TYPE_PLACED`: A workspace that waited for a host was given one, and its disk there\n- `WORKSPACE_EVENT_TYPE_TRASHED`: Deleted into the trash: its disk kept, its owner able to undelete it\n- `WORKSPACE_EVENT_TYPE_UNDELETED`: Taken back out of the trash\n- `WORKSPACE_EVENT_TYPE_MOVED`: Suspended on its host and resumed on another, which had room for what it was granted\n- `WORKSPACE_EVENT_TYPE_DISPLACED`: Its host was drained or went away: suspended there, it is resumed on another on its own\n- `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`: A stop on a host that can be taken away could not publish its disk first, so it runs on\n- `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`: Nobody used it for as long as the installation allows: suspended with its processes, a start resumes them\n- `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`: It ran at a stretch for as long as the installation allows: suspended with its processes, a start resumes them\n- `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`: A suspension is minutes away and the question was asked: its terminals, its page, the CLI; a keep answers it\n- `WORKSPACE_EVENT_TYPE_KEPT`: Somebody kept it awake until a time: no suspension before it\n- `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`: Its supervisor said nothing for longer than the control plane waits: stopped by force, and a start brings it back\n- `WORKSPACE_EVENT_TYPE_REHOMED`: Its host stopped answering and it was moved to another on its own, giving up what that host had not published\n- `WORKSPACE_EVENT_TYPE_HELD`: Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner", + "description": "- `WORKSPACE_EVENT_TYPE_CREATED`: Workspace was created\n- `WORKSPACE_EVENT_TYPE_STARTED`: Workspace was started\n- `WORKSPACE_EVENT_TYPE_STOPPED`: Workspace was stopped; its disk is kept, and no snapshot is taken\n- `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`: Manual snapshot\n- `WORKSPACE_EVENT_TYPE_DELETED`: Workspace was deleted\n- `WORKSPACE_EVENT_TYPE_FAILED`: Workspace start failed\n- `WORKSPACE_EVENT_TYPE_RESTORED`: Its disk was put back to a snapshot\n- `WORKSPACE_EVENT_TYPE_RESUMED`: It went on from a snapshot's memory\n- `WORKSPACE_EVENT_TYPE_BOOTED`: Suspended, its memory could not be resumed, and its disk was booted\n- `WORKSPACE_EVENT_TYPE_QUARANTINED`: An administrator held it: stopped, and startable by nobody until released\n- `WORKSPACE_EVENT_TYPE_UNQUARANTINED`: The hold was released: back to stopped, where it was held from\n- `WORKSPACE_EVENT_TYPE_PLACED`: A workspace that waited for a host was given one, and its disk there\n- `WORKSPACE_EVENT_TYPE_TRASHED`: Deleted into the trash: its disk kept, its owner able to undelete it\n- `WORKSPACE_EVENT_TYPE_UNDELETED`: Taken back out of the trash\n- `WORKSPACE_EVENT_TYPE_MOVED`: Suspended on its host and resumed on another, which had room for what it was granted\n- `WORKSPACE_EVENT_TYPE_DISPLACED`: Its host was drained or went away: suspended there, it is resumed on another on its own\n- `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`: A stop on a host that can be taken away could not publish its disk first, so it runs on\n- `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`: Nobody used it for as long as the installation allows: suspended with its processes, a start resumes them\n- `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`: It ran at a stretch for as long as the installation allows: suspended with its processes, a start resumes them\n- `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`: A suspension is minutes away and the question was asked: its terminals, its page, the CLI; a keep answers it\n- `WORKSPACE_EVENT_TYPE_KEPT`: Somebody kept it awake until a time: no suspension before it\n- `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`: Its supervisor said nothing for longer than the control plane waits: stopped by force, and a start brings it back\n- `WORKSPACE_EVENT_TYPE_REHOMED`: Its host stopped answering and it was moved to another on its own, giving up what that host had not published\n- `WORKSPACE_EVENT_TYPE_HELD`: Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner\n- `WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN`: An administrator moved it off a host that is gone, giving up for its owner what that host had not published; its details say who and why\n- `WORKSPACE_EVENT_TYPE_OUT_OF_MEMORY`: It ran out of memory: its guest's kernel killed a process (cause \"guest\", with the kills and\n their victims), its host found it short at its ceiling for a while (cause \"ceiling\"), or its\n host killed the machine (cause \"host\"). Its details say what it held, its grant and its\n ceiling. Said, never acted on: nothing resizes a workspace on its own.\n- `WORKSPACE_EVENT_TYPE_OVERSIZED`: It held far more than it used for a week: the most memory its processes used stayed under half\n its own, and a smaller preset would fit what it used. Its details say what it used, what it\n holds, and that preset. Said, never acted on: nothing resizes a workspace on its own.", "x-enum-descriptions": [ "", "Workspace was created", @@ -27985,7 +30445,10 @@ "Somebody kept it awake until a time: no suspension before it", "Its supervisor said nothing for longer than the control plane waits: stopped by force, and a start brings it back", "Its host stopped answering and it was moved to another on its own, giving up what that host had not published", - "Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner" + "Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner", + "An administrator moved it off a host that is gone, giving up for its owner what that host had not published; its details say who and why", + "It ran out of memory: its guest's kernel killed a process (cause \"guest\", with the kills and\n their victims), its host found it short at its ceiling for a while (cause \"ceiling\"), or its\n host killed the machine (cause \"host\"). Its details say what it held, its grant and its\n ceiling. Said, never acted on: nothing resizes a workspace on its own.", + "It held far more than it used for a week: the most memory its processes used stayed under half\n its own, and a smaller preset would fit what it used. Its details say what it used, what it\n holds, and that preset. Said, never acted on: nothing resizes a workspace on its own." ] }, "spin.v1.WorkspaceFailure": { @@ -28083,6 +30546,15 @@ "hostClass": { "type": "string", "title": "host_class" + }, + "machineType": { + "type": "string", + "title": "machine_type", + "description": "The host's machine as its cloud names it, said when the host joined (Host.machine_type,\n Host.zone): what kind of machine the workspace runs on, and where. Empty for a host that\n joined with no cloud. The machine's id and whether it may be taken back stay an\n administrator's." + }, + "zone": { + "type": "string", + "title": "zone" } }, "title": "WorkspaceHostStatus", @@ -28178,6 +30650,28 @@ "title": "WorkspaceLogFieldValuesResponse", "additionalProperties": false }, + "spin.v1.WorkspaceNetworkStatus": { + "type": "object", + "properties": { + "policy": { + "type": "string", + "title": "policy" + }, + "declared": { + "type": "boolean", + "title": "declared", + "description": "declared says a policy of this name exists: the installation's, or a team's." + }, + "installed": { + "type": "boolean", + "title": "installed", + "description": "installed says the workspace's host last reported enforcing a policy of this name. False\n for a workspace on no host." + } + }, + "title": "WorkspaceNetworkStatus", + "additionalProperties": false, + "description": "WorkspaceNetworkStatus is one policy a workspace runs under, and whether it holds there: the\n installation declares it, and the workspace's host reports having installed it. A tag no policy\n is declared for, or one its host has not installed, is what a start is refused over." + }, "spin.v1.WorkspacePhase": { "type": "string", "title": "WorkspacePhase", @@ -28555,6 +31049,12 @@ { "name": "AuthService" }, + { + "name": "WorkspaceService" + }, + { + "name": "SettingsService" + }, { "name": "AdminService", "description": "AdminService manages installation-wide settings, user management, and resource presets\n Only accessible to instance administrators --- Installation Settings ---" @@ -28580,17 +31080,11 @@ }, { "name": "ProjectService", - "description": "ProjectService is the installation's projects (spin#720): a repository and the shape of the\n workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where\n the installation lets everybody (InstallationSettings.project_creation), and is its first admin.\n A project is seen by everybody while it is shared with everyone, and otherwise by its admins and\n whom it is shared with; the instance's admins see and administer every one. Whoever sees one\n opens workspaces from it, in a team of theirs." + "description": "ProjectService is the installation's projects (spin#720): a repository and the shape of the\n workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where\n the installation lets everybody (the setting project_creation), and is its first admin.\n A project is seen by everybody while it is shared with everyone, and otherwise by its admins and\n whom it is shared with; the instance's admins see and administer every one. Whoever sees one\n opens workspaces from it, in a team of theirs." }, { "name": "RunnerTokenService" }, - { - "name": "WorkspaceService" - }, - { - "name": "SettingsService" - }, { "name": "SetupService", "description": "SetupService handles initial platform setup and admin bootstrap.\n Most endpoints require admin authentication, except GetSetupStatus and PasswordLogin." diff --git a/src/assets/shots/snapshots.png b/src/assets/shots/snapshots.png index 29ff62d..0ed4a28 100644 Binary files a/src/assets/shots/snapshots.png and b/src/assets/shots/snapshots.png differ diff --git a/src/assets/shots/workspace.png b/src/assets/shots/workspace.png index 95014c6..5dd5397 100644 Binary files a/src/assets/shots/workspace.png and b/src/assets/shots/workspace.png differ diff --git a/src/assets/shots/workspaces.png b/src/assets/shots/workspaces.png index fc7f800..61a4099 100644 Binary files a/src/assets/shots/workspaces.png and b/src/assets/shots/workspaces.png differ diff --git a/src/content/docs/reference/api/adminservice.md b/src/content/docs/reference/api/adminservice.md index 1777dc8..f267b5b 100644 --- a/src/content/docs/reference/api/adminservice.md +++ b/src/content/docs/reference/api/adminservice.md @@ -4,7 +4,7 @@ description: "spin.v1.AdminService: each procedure, its request and its response source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 3 + order: 5 --- @@ -241,7 +241,7 @@ What the installation cost in a month, as its cloud's bill says: by day and serv `POST /spin.v1.AdminService/GetCosts` -A month of the bill shared out to the workspaces that caused it: the runners' by what each workspace held of them, the volumes' bucket by what its volumes kept. What nobody caused is said apart and never spread: the runners' capacity nobody held, the bucket's bytes no workspace keeps, and the platform's own cost by service (internal/controlplane/domain/finops). +A month of the bill shared out to the workspaces that caused it: the runners' by what each workspace held of them, the volumes' bucket by what its volumes kept. What nobody caused is said apart and never spread: the runners' capacity nobody held, the bucket's bytes no workspace keeps, and the platform's own cost by role and service; and, from those, what the installation costs a day with nobody working (internal/controlplane/domain/finops). **Request** @@ -258,7 +258,10 @@ A month of the bill shared out to the workspaces that caused it: the runners' by | `workspaces` | list of [WorkspaceCost](/reference/api/types/#workspacecost) | Costliest first. | | `idleMicros` | string (int64) | What the runners cost that no workspace held. | | `unstoredMicros` | string (int64) | What the volumes' bucket cost for bytes no workspace keeps: the images, what nothing references any more. | -| `platform` | list of [ServiceCost](/reference/api/types/#servicecost) | Every other cost - the control plane, the proxy, keys, logs - by service, costliest first. | +| `platform` | list of [PlatformCost](/reference/api/types/#platformcost) | Every other cost - the control plane, the proxy, keys, logs - by role and service, costliest first. | +| `standby` | [StandbyCost](/reference/api/types/#standbycost) | What the installation costs a day with nobody working; unset where no day before the reading billed anything. | +| `readAt` | `Timestamp` | When the month's bill was last read; unset where it never was, and then nothing is answered. | +| `trend` | list of [CostMonth](/reference/api/types/#costmonth) | The year up to this month, oldest first, this month last: each month the bill was read in, shared out as this one is. A month never read is not among them. | ## GetEmailSettings @@ -312,25 +315,6 @@ No fields: `{}`. | `status` | [HealthStatus](/reference/api/types/#healthstatus) | status is the worst of the checks'. | | `checks` | list of [HealthCheck](/reference/api/types/#healthcheck) | One per part the installation has: a part it does not have - a database archive on a development stack, a log store where none is kept, a collector that never reported, jobs on a control plane that does not lead - is left out rather than answered for. | -## GetInstallationSettings - -`POST /spin.v1.AdminService/GetInstallationSettings` - -Get installation settings - -**Request** - -No fields: `{}`. - -**Response** - -| Field | Type | What it is | -|---|---|---| -| `settings` | [InstallationSettings](/reference/api/types/#installationsettings) | | -| `reaches` | map of string to [SettingReach](/reference/api/types/#settingreach) | What each setting touches when saved, by its update_mask path: every path UpdateInstallationSettings accepts, and no other. | -| `runningWorkspaces` | integer (int32) | How many workspaces are running now: what a RUNNING_WORKSPACES setting reaches, and what a NEW_WORKSPACES one leaves as it is. | -| `connectedRunners` | integer or null (int32) | How many runners hold their stream to this control plane now: what a NEXT_HEARTBEAT setting reaches. Unset where this control plane cannot tell. | - ## GetMonitoring `POST /spin.v1.AdminService/GetMonitoring` @@ -366,22 +350,6 @@ No fields: `{}`. | `supervisorReleases` | list of [ExtensionRelease](/reference/api/types/#extensionrelease) | Every release of the supervisor, newest first. | | `agentReleases` | list of [ExtensionRelease](/reference/api/types/#extensionrelease) | Every release of the agents' programs (Claude Code, Codex and T3 Code, one extension), newest first. | -## GetRegistrationMode - -`POST /spin.v1.AdminService/GetRegistrationMode` - -Get registration mode (open, whitelist, closed) - -**Request** - -No fields: `{}`. - -**Response** - -| Field | Type | What it is | -|---|---|---| -| `mode` | [RegistrationMode](/reference/api/types/#registrationmode) | | - ## GetRunbook `POST /spin.v1.AdminService/GetRunbook` @@ -402,11 +370,11 @@ One runbook, its markdown and where its file is at the pinned commit. NOT_FOUND | `markdown` | string | Its body, markdown without the front matter: the app renders it and nothing else (no raw HTML). | | `sourceUrl` | string | The file at the commit this release pins, to propose a change from. | -## GetSecuritySettings +## GetSettings -`POST /spin.v1.AdminService/GetSecuritySettings` +`POST /spin.v1.AdminService/GetSettings` -Get security settings +Every setting of the installation as it is in force, and what each is: its type, its default, its bounds, what it reaches when saved and who writes it (internal/controlplane/settings, the one registry the file, the dashboard and the CLI are made from). **Request** @@ -416,7 +384,11 @@ No fields: `{}`. | Field | Type | What it is | |---|---|---| -| `settings` | [SecuritySettings](/reference/api/types/#securitysettings) | | +| `values` | map of string to string | Every definition's value in force, by key, as text of its type: what the installation says, or the default where it says nothing. The control plane's own are left out. | +| `definitions` | list of [SettingDefinition](/reference/api/types/#settingdefinition) | Every setting an administrator is shown, in the registry's order. | +| `runningWorkspaces` | integer (int32) | How many workspaces are running now: what a RUNNING_WORKSPACES setting reaches, and what a NEW_WORKSPACES one leaves as it is. | +| `connectedRunners` | integer or null (int32) | How many runners hold their stream to this control plane now: what a NEXT_HEARTBEAT setting reaches. Unset where this control plane cannot tell. | +| `place` | [InstallationPlace](/reference/api/types/#installationplace) | Where the installation runs and what it runs, read-only. | ## GetStorageSettings @@ -472,6 +444,7 @@ No fields: `{}`. | `windowSeconds` | integer (int32) | How long the counts below are over. | | `spans` | [TelemetryFlow](/reference/api/types/#telemetryflow) | Into the installation's store, after the traces policy chose which to keep. | | `samples` | [TelemetryFlow](/reference/api/types/#telemetryflow) | Into the installation's store of metrics. | +| `perVolumeHistograms` | list of string | The metrics that cost most to keep, a histogram per volume, which a surface may offer to drop as one (the setting telemetry_metrics_drop). | ## GetThirdPartyTerms @@ -491,6 +464,25 @@ No fields: `{}`. | `text` | list of string | The terms, a paragraph each. | | `items` | list of [ThirdPartyTermsItem](/reference/api/types/#thirdpartytermsitem) | | +## GetUserWorkspaceEgress + +`POST /spin.v1.AdminService/GetUserWorkspaceEgress` + +Where a workspace sends, as its owner's GetWorkspaceEgress answers it: the host's counters by destination, nothing of what was carried. Audited, as a reading of somebody else's. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | The workspace's id. | +| `reason` | string | Why, kept on the audit entry. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `egress` | [GetWorkspaceEgressResponse](/reference/api/workspaceservice/#getworkspaceegress) | | + ## GetVSCodeServers `POST /spin.v1.AdminService/GetVSCodeServers` @@ -527,6 +519,22 @@ The names of the workspaces ids are, whoever owns them and whatever state they a |---|---|---| | `names` | map of string to string | Each id asked for that the installation holds a row for, to its workspace's name. | +## ListAlerts + +`POST /spin.v1.AdminService/ListAlerts` + +What the release's alert rules said in the installation's Grafana, newest first: each firing and each resolution the control plane heard, with its runbook and how many administrators it reached. + +**Request** + +No fields: `{}`. + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `alerts` | list of [Alert](/reference/api/types/#alert) | | + ## ListAllowedEmails `POST /spin.v1.AdminService/ListAllowedEmails` @@ -603,6 +611,25 @@ No fields: `{}`. |---|---|---| | `classes` | list of [HostClass](/reference/api/types/#hostclass) | | +## ListHostProblems + +`POST /spin.v1.AdminService/ListHostProblems` + +What is wrong with the fleet's hosts, one problem per host that is not connected or fails a check of its own: the same problems GetInstallationHealth's HOSTS part lists, for the pages that read one host or the fleet and nothing else of the installation. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `hostId` | string | The one host asked about; empty for every host of the fleet. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `problems` | list of [Problem](/reference/api/types/#problem) | At most 20, hosts that are not connected first; problem_count is how many there are. | +| `problemCount` | integer (int32) | | + ## ListJobRuns `POST /spin.v1.AdminService/ListJobRuns` @@ -638,6 +665,22 @@ No fields: `{}`. |---|---|---| | `jobs` | list of [Job](/reference/api/types/#job) | | +## ListRefusedVolumes + +`POST /spin.v1.AdminService/ListRefusedVolumes` + +The disks a host refuses to serve, each with the workspace it is the disk of and whose that is. Audited. + +**Request** + +No fields: `{}`. + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `volumes` | list of [RefusedVolume](/reference/api/types/#refusedvolume) | | + ## ListRekeys `POST /spin.v1.AdminService/ListRekeys` @@ -690,6 +733,28 @@ No fields: `{}`. |---|---|---| | `runbooks` | list of [Runbook](/reference/api/types/#runbook) | | +## ListTimeline + +`POST /spin.v1.AdminService/ListTimeline` + +What happened, newest first, between two instants, to one workspace, host, team or project, or to the whole installation: read from the audit log, the workspaces' events, the jobs' failed runs and the projects' prebuilds as they are, never copied. Sign-ins and what is done inside a workspace (a session, a command) are the audit log's alone: they outnumber everything else. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `since` | `Timestamp` | Inclusive; unset reads from as far back as the records go. | +| `until` | `Timestamp` | Exclusive; unset is now. | +| `page` | [PageRequest](/reference/api/types/#pagerequest) | page_size is at most 200, and zero is 50. total_count is not counted. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `events` | list of [TimelineEvent](/reference/api/types/#timelineevent) | | +| `page` | [PageResponse](/reference/api/types/#pageresponse) | | +| `keptDays` | integer (int32) | kept_days is how long what the timeline reads is kept (ListWorkspaceTimelineResponse). | + ## ListUserAPIKeys `POST /spin.v1.AdminService/ListUserAPIKeys` @@ -750,6 +815,26 @@ Publish the servers of the newest stable VS Code releases, or of the commits nam No fields: `{}`. +## RecoverUserWorkspace + +`POST /spin.v1.AdminService/RecoverUserWorkspace` + +Move a workspace off a host that is gone, as its owner's RecoverWorkspace does, and with the same question first: without accept_data_loss it answers what the move would give up and changes nothing. The work given up is its owner's, so a reason is required, kept on the audit entry, and written with who asked on the workspace's history, which its owner reads (WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN). Refused for a workspace held in quarantine. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | The workspace's id. | +| `acceptDataLoss` | boolean | False asks what it would cost. True accepts that cost, for its owner, and does it. | +| `reason` | string | Why: required, kept on the audit entry and on the workspace's history for its owner. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `recovery` | [RecoverWorkspaceResponse](/reference/api/workspaceservice/#recoverworkspace) | | + ## RekeyVolume `POST /spin.v1.AdminService/RekeyVolume` @@ -863,23 +948,59 @@ Run a job now rather than when it is next due. Refused while it is running: a se No fields: `{}`. -## SetRegistrationMode +## SendTestAlert + +`POST /spin.v1.AdminService/SendTestAlert` + +Has Grafana test its contact point, which sends a test alert the whole way an alert goes: Grafana, the control plane, an email to every administrator who has not opted out. FAILED_PRECONDITION where this control plane serves no Grafana, or Grafana could not reach the control plane. + +**Request** + +No fields: `{}`. + +**Response** + +No fields: `{}`. + +## SetSettings + +`POST /spin.v1.AdminService/SetSettings` + +Writes the settings named, each checked by its definition and all of them together before any is written: a refused one writes nothing. -`POST /spin.v1.AdminService/SetRegistrationMode` +**Request** + +| Field | Type | What it is | +|---|---|---| +| `values` | map of string to string | The settings to write, by key, as text of each one's type. Only ADMIN settings are taken. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `values` | map of string to string | Every value in force once written, as GetSettings answers them. | + +## StopUserWorkspace -Set registration mode +`POST /spin.v1.AdminService/StopUserWorkspace` + +Stop a workspace, as its owner's StopWorkspace does: one whose stop could not publish its disk runs on and holds its host until somebody stops it. Audited. **Request** | Field | Type | What it is | |---|---|---| -| `mode` | [RegistrationMode](/reference/api/types/#registrationmode) | | +| `workspaceId` | string | The workspace's id: an administrator names no workspace of somebody else's by its name. | +| `force` | boolean | Give up what only its host has rather than publish it first, as StopWorkspace's force does. | +| `reason` | string | Why, kept on the audit entry. | **Response** | Field | Type | What it is | |---|---|---| -| `mode` | [RegistrationMode](/reference/api/types/#registrationmode) | | +| `workspaceId` | string | | +| `name` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | ## UntagImage @@ -935,25 +1056,6 @@ Set the SMTP server, checked against it - connected to and signed in to - before |---|---|---| | `class` | [HostClass](/reference/api/types/#hostclass) | | -## UpdateInstallationSettings - -`POST /spin.v1.AdminService/UpdateInstallationSettings` - -Update installation settings - -**Request** - -| Field | Type | What it is | -|---|---|---| -| `settings` | [InstallationSettings](/reference/api/types/#installationsettings) | | -| `updateMask` | `FieldMask` | Required: only the named fields of settings are written, so a form that edits one setting cannot reset the others to their zero values. | - -**Response** - -| Field | Type | What it is | -|---|---|---| -| `settings` | [InstallationSettings](/reference/api/types/#installationsettings) | | - ## UpdateNetworkPolicy `POST /spin.v1.AdminService/UpdateNetworkPolicy` @@ -1000,24 +1102,6 @@ Update a resource preset |---|---|---| | `preset` | [ResourcePreset](/reference/api/types/#resourcepreset) | | -## UpdateSecuritySettings - -`POST /spin.v1.AdminService/UpdateSecuritySettings` - -Update security settings - -**Request** - -| Field | Type | What it is | -|---|---|---| -| `settings` | [SecuritySettings](/reference/api/types/#securitysettings) | | - -**Response** - -| Field | Type | What it is | -|---|---|---| -| `settings` | [SecuritySettings](/reference/api/types/#securitysettings) | | - ## UpdateStorageSettings `POST /spin.v1.AdminService/UpdateStorageSettings` diff --git a/src/content/docs/reference/api/authservice.md b/src/content/docs/reference/api/authservice.md index e965fbf..9b3a24a 100644 --- a/src/content/docs/reference/api/authservice.md +++ b/src/content/docs/reference/api/authservice.md @@ -138,7 +138,7 @@ Create API key | `scopes` | list of string | e.g., ["workspace:read", "workspace:write"] | | `expiresAt` | `Timestamp` | | | `stepUp` | [PasskeyAssertion](/reference/api/types/#passkeyassertion) | An assertion of a passkey of the caller's over a PASSKEY_PURPOSE_CREATE_EXEC_KEY challenge naming the key, asked for a key with workspace:exec alone: that key is a shell, which a session alone does not open. | -| `teamId` | string | What it reaches and what its tree may hold: as APIKey's. Asked by a key holding keys:delegate, it is that key's child, and holds no more than it (delegation.Within). | +| `teamId` | string | What it reaches and what its tree may have running at once: as APIKey's, where what is counted is said. Asked by a key holding keys:delegate, it is that key's child, and holds no more than it (delegation.Within). | | `projectIds` | list of string | | | `maxWorkspaces` | integer or null (int32) | | | `maxCores` | integer or null (int32) | | diff --git a/src/content/docs/reference/api/execservice.md b/src/content/docs/reference/api/execservice.md index f728612..4059910 100644 --- a/src/content/docs/reference/api/execservice.md +++ b/src/content/docs/reference/api/execservice.md @@ -4,7 +4,7 @@ description: "spin.v1.ExecService: each procedure, its request and its response. source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 4 + order: 6 --- diff --git a/src/content/docs/reference/api/forgeservice.md b/src/content/docs/reference/api/forgeservice.md index 84f1539..e5eb174 100644 --- a/src/content/docs/reference/api/forgeservice.md +++ b/src/content/docs/reference/api/forgeservice.md @@ -4,7 +4,7 @@ description: "spin.v1.ForgeService: each procedure, its request and its response source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 5 + order: 7 --- diff --git a/src/content/docs/reference/api/hostservice.md b/src/content/docs/reference/api/hostservice.md index f8bba47..f97f529 100644 --- a/src/content/docs/reference/api/hostservice.md +++ b/src/content/docs/reference/api/hostservice.md @@ -4,7 +4,7 @@ description: "spin.v1.HostService: each procedure, its request and its response. source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 6 + order: 8 --- @@ -88,6 +88,7 @@ Get host details (admin only) | Field | Type | What it is | |---|---|---| | `host` | [Host](/reference/api/types/#host) | | +| `departure` | [HostDeparture](/reference/api/types/#hostdeparture) | What taking the host out would do to each workspace on it, as the server would do it: asked of one host, because it reads every workspace there. | ## GetHostPolicy @@ -133,13 +134,15 @@ List all hosts (admin only) `POST /spin.v1.HostService/RemoveHost` -Takes a machine of an autoscaling group out of the fleet (admin only), as Host.removal says: given back to its group (GIVE_BACK), or forgotten (FORGET). A host whose removal is REVOKE is refused: RevokeRunner is its removal. +Takes a machine of an autoscaling group out of the fleet (admin only), as Host.removal says: given back to its group (GIVE_BACK), or forgotten (FORGET). A host whose removal is REVOKE is refused: RevokeRunner is its removal. Asked with taken, the host leaves as one believed compromised: its credentials revoked and every team's disk whose key it was given marked for re-keying (ListRekeys), as RevokeRunner does, and its machine given back all the same. **Request** | Field | Type | What it is | |---|---|---| | `id` | string | | +| `taken` | boolean | The host is believed taken - compromised - rather than only no longer wanted. Its credentials are deleted and its stream cut before its group is asked anything, and every team's disk whose key it was given is marked for re-keying. A routine removal - a scale-in, a machine reclaimed - leaves it unset, and marks none. | +| `leaveWorkspaces` | boolean | Remove a taken host although it is the only home of workspaces' disks. Refused without it, naming them; with it each runs again only once it is recovered onto another host (RecoverWorkspace), which gives up what this host had not published. Only with taken: a routine removal loses no disk. | **Response** @@ -148,6 +151,8 @@ Takes a machine of an autoscaling group out of the fleet (admin only), as Host.r | `removal` | [HostRemoval](/reference/api/types/#hostremoval) | removal is what was done: GIVE_BACK or FORGET. | | `givenBack` | boolean | given_back is the machine terminated and its group asked for one less; false where the group no longer had it. | | `forgotten` | boolean | forgotten is the host's row gone. A host given back while connected is not: it drains under its termination hook, and its row goes when its stream ends. | +| `markedForRekey` | integer (int32) | marked_for_rekey is how many teams' disks were marked for re-keying: those whose key a host removed as taken was given. Zero for a routine removal. | +| `kept` | string | kept is why the row was not forgotten, where it was not and should have been: the workspace whose disk is only there, or the volume that would not leave it and whose that is. Empty when forgotten, and for a host given back while connected. | ## SetHostPolicy diff --git a/src/content/docs/reference/api/hostshellservice.md b/src/content/docs/reference/api/hostshellservice.md index 8be481e..8a3e69d 100644 --- a/src/content/docs/reference/api/hostshellservice.md +++ b/src/content/docs/reference/api/hostshellservice.md @@ -4,7 +4,7 @@ description: "spin.v1.HostShellService: each procedure, its request and its resp source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 7 + order: 9 --- diff --git a/src/content/docs/reference/api/index.md b/src/content/docs/reference/api/index.md index f027cb8..c0be152 100644 --- a/src/content/docs/reference/api/index.md +++ b/src/content/docs/reference/api/index.md @@ -22,18 +22,18 @@ curl -s https://app.example.com/spin.v1.WorkspaceService/ListWorkspaces \ - [PasskeyService](/reference/api/passkeyservice/): PasskeyService is a person's passkeys: WebAuthn credentials of their account, under the. installation's base domain (internal/passkey). A passkey signs a challenge the control plane. issued, never a document: what turns a session into a key that logs in (SSHKeyService.AddSSHKey) is an assertion of one, so a session alone - stolen, or an agent's - is no shell. - [AuthService](/reference/api/authservice/): 16 procedures +- [WorkspaceService](/reference/api/workspaceservice/): 57 procedures +- [SettingsService](/reference/api/settingsservice/): 6 procedures - [AdminService](/reference/api/adminservice/): AdminService manages installation-wide settings, user management, and resource presets. Only accessible to instance administrators. - [ExecService](/reference/api/execservice/): ExecService runs commands and moves files in a running workspace, as its root, for a caller who. is no terminal: a program or an agent holding a key with workspace:exec. The control plane logs. in over the same session a terminal's goes over (a host's stream from the guest's vsock, met at. the control plane rather than at the relay) with a certificate of its own for five minutes, and. every call is recorded with who made it and on which credential (audit: workspace_exec). - [ForgeService](/reference/api/forgeservice/): ForgeService is a person's account on a forge, connected through the installation's GitHub App. (spin#758): the control plane keeps the token, lists the person's repositories with it, and a. host's egress proxy sets it on their workspaces' brokered requests. No workspace holds it. - [HostService](/reference/api/hostservice/): 9 procedures - [HostShellService](/reference/api/hostshellservice/): HostShellService is a root shell on a host, asked for by an administrator, for a while, with a. reason (spin#712). The host is emptied of its workspaces first - unless the request is an. incident's, which says so - and retired after, whatever happened in it: once a person has had root. on a host it runs no workspace again. Every session is recorded by the control plane, which. carries it; the terminal itself is a WebSocket at /api/host-shells/<id>/terminal. (internal/hostshell). - [LogService](/reference/api/logservice/): LogService is what the installation logged, as its store of logs and traces keeps it. (internal/logstore). Every search is confined by the control plane to what the caller may see; what the caller types is searched inside that and cannot widen it. -- [ProjectService](/reference/api/projectservice/): ProjectService is the installation's projects (spin#720): a repository and the shape of the. workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where. the installation lets everybody (InstallationSettings.project_creation), and is its first admin. A project is seen by everybody while it is shared with everyone, and otherwise by its admins and. whom it is shared with; the instance's admins see and administer every one. Whoever sees one. opens workspaces from it, in a team of theirs. +- [ProjectService](/reference/api/projectservice/): ProjectService is the installation's projects (spin#720): a repository and the shape of the. workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where. the installation lets everybody (the setting project_creation), and is its first admin. A project is seen by everybody while it is shared with everyone, and otherwise by its admins and. whom it is shared with; the instance's admins see and administer every one. Whoever sees one. opens workspaces from it, in a team of theirs. - [RunnerTokenService](/reference/api/runnertokenservice/): 4 procedures -- [WorkspaceService](/reference/api/workspaceservice/): 53 procedures -- [SettingsService](/reference/api/settingsservice/): 6 procedures - [SetupService](/reference/api/setupservice/): SetupService handles initial platform setup and admin bootstrap. Most endpoints require admin authentication, except GetSetupStatus and PasswordLogin. - [ShareService](/reference/api/shareservice/): ShareService manages snapshot sharing. Snapshots can be shared with specific users or made public. Users can create new workspaces from shared snapshots. - [SSHKeyService](/reference/api/sshkeyservice/): SSHKeyService is the keys that log in as the person, to every workspace that lets them in: a key. of their own, for good, or a browser's or a CLI's enrolled for twelve hours. Each is added with an. assertion of a passkey of theirs (PasskeyService), so a session alone adds none. -- [TeamService](/reference/api/teamservice/): 18 procedures +- [TeamService](/reference/api/teamservice/): 19 procedures - [Types](/reference/api/types/): every message and enum the procedures name diff --git a/src/content/docs/reference/api/logservice.md b/src/content/docs/reference/api/logservice.md index 38dd57a..6f6b92c 100644 --- a/src/content/docs/reference/api/logservice.md +++ b/src/content/docs/reference/api/logservice.md @@ -4,7 +4,7 @@ description: "spin.v1.LogService: each procedure, its request and its response." source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 8 + order: 10 --- @@ -91,6 +91,24 @@ HistogramWorkspaceLogs is how many of those records each interval of the window |---|---|---| | `intervals` | list of [LogInterval](/reference/api/types/#loginterval) | | +## KeptTraces + +`POST /spin.v1.LogService/KeptTraces` + +KeptTraces is which of the trace ids a page shows the store holds: those, and only those, are links. A trace id is written where its span began, before the collector decides whether to keep the trace, and it stays after the store's retention has deleted it. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `traceIds` | list of string | Hex, as a log record carries them: at most 1000, a page's. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `traceIds` | list of string | Those of the request's the store holds, each once. | + ## ListLogIndexes `POST /spin.v1.LogService/ListLogIndexes` diff --git a/src/content/docs/reference/api/projectservice.md b/src/content/docs/reference/api/projectservice.md index a2b402f..34e63b5 100644 --- a/src/content/docs/reference/api/projectservice.md +++ b/src/content/docs/reference/api/projectservice.md @@ -4,12 +4,12 @@ description: "spin.v1.ProjectService: each procedure, its request and its respon source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 9 + order: 11 --- -ProjectService is the installation's projects (spin#720): a repository and the shape of the. workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where. the installation lets everybody (InstallationSettings.project_creation), and is its first admin. A project is seen by everybody while it is shared with everyone, and otherwise by its admins and. whom it is shared with; the instance's admins see and administer every one. Whoever sees one. opens workspaces from it, in a team of theirs. +ProjectService is the installation's projects (spin#720): a repository and the shape of the. workspace that opens it (CreateWorkspaceRequest.project_id). Anybody signed in makes one where. the installation lets everybody (the setting project_creation), and is its first admin. A project is seen by everybody while it is shared with everyone, and otherwise by its admins and. whom it is shared with; the instance's admins see and administer every one. Whoever sees one. opens workspaces from it, in a team of theirs. ## CreateProject @@ -87,6 +87,28 @@ One of them whole, with what its build's create tasks wrote, to whoever sees the |---|---|---| | `project` | [Project](/reference/api/types/#project) | | +## GetProjectCosts + +`POST /spin.v1.ProjectService/GetProjectCosts` + +What a project cost in the month in progress, to its admins and nobody else, as ListProjectWorkspaces: everybody's workspaces of it summed, its prebuilds' builds included, and a month of what its prebuilds keep. A sum, never by workspace: one workspace's cost says how much its owner works, which a project's admin who is not their team's is not told. Read from the bill as the installation last read it, never from the cloud. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `projectId` | string | | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `month` | `Timestamp` | The month in progress: its first instant, UTC. | +| `readAt` | `Timestamp` | When the bill was last read; unset where it never was - an installation with no bill scope - and then nothing below is known and nothing is to be drawn. | +| `currency` | string | ISO 4217; every amount here is in millionths of it. | +| `workspaces` | [CostShare](/reference/api/types/#costshare) | Its workspaces' shares summed, deleted ones and its prebuilds' builds included: a workspace is the project's it opened, whoever's it was. | +| `prebuilds` | [PrebuildsKept](/reference/api/types/#prebuildskept) | | + ## ListPrebuilds `POST /spin.v1.ProjectService/ListPrebuilds` diff --git a/src/content/docs/reference/api/runnertokenservice.md b/src/content/docs/reference/api/runnertokenservice.md index f5b100d..efd0698 100644 --- a/src/content/docs/reference/api/runnertokenservice.md +++ b/src/content/docs/reference/api/runnertokenservice.md @@ -4,7 +4,7 @@ description: "spin.v1.RunnerTokenService: each procedure, its request and its re source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 10 + order: 12 --- diff --git a/src/content/docs/reference/api/settingsservice.md b/src/content/docs/reference/api/settingsservice.md index 2f92d8a..ea81f35 100644 --- a/src/content/docs/reference/api/settingsservice.md +++ b/src/content/docs/reference/api/settingsservice.md @@ -4,7 +4,7 @@ description: "spin.v1.SettingsService: each procedure, its request and its respo source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 12 + order: 4 --- @@ -13,7 +13,7 @@ sidebar: `POST /spin.v1.SettingsService/GetNotificationPreferences` -Which of those the caller is emailed about. +Which of those the caller is emailed about, and for an administrator which of the installation's events besides. **Request** @@ -23,7 +23,7 @@ No fields: `{}`. | Field | Type | What it is | |---|---|---| -| `preferences` | list of [NotificationPreference](/reference/api/types/#notificationpreference) | One per event a person may be told of, in the order they are shown. | +| `preferences` | list of [NotificationPreference](/reference/api/types/#notificationpreference) | One per event a person may be told of, in the order they are shown: an administrator's include the installation's. | | `emailConfigured` | boolean | | ## GetUserSettings diff --git a/src/content/docs/reference/api/teamservice.md b/src/content/docs/reference/api/teamservice.md index 925f6b4..bbaab55 100644 --- a/src/content/docs/reference/api/teamservice.md +++ b/src/content/docs/reference/api/teamservice.md @@ -153,6 +153,28 @@ Get team by ID or path | `team` | [Team](/reference/api/types/#team) | | | `currentUserRole` | [TeamRole](/reference/api/types/#teamrole) | Role of the requesting user in this team | +## GetTeamCosts + +`POST /spin.v1.TeamService/GetTeamCosts` + +What the team's workspaces cost in the month in progress, each and summed, for the team's admins: a member reads their own workspace's (WorkspaceService.GetWorkspaceCost) and nobody else's. A workspace is the team's whose it was on the last day it cost anything; a team below this one is not counted in it. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `teamId` | string | | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `month` | `Timestamp` | The month in progress: its first instant, UTC. | +| `readAt` | `Timestamp` | When the bill was last read; unset where it never was - an installation with no bill scope - and then nothing below is known and nothing is to be drawn. | +| `currency` | string | ISO 4217; every amount here is in millionths of it. | +| `total` | [CostShare](/reference/api/types/#costshare) | Its workspaces, summed. | +| `workspaces` | list of [TeamWorkspaceCost](/reference/api/types/#teamworkspacecost) | Costliest first. | + ## ListIdentityProviders `POST /spin.v1.TeamService/ListIdentityProviders` @@ -278,7 +300,7 @@ A team's secrets: what a brokered network rule has the host's egress proxy add t | `name` | string | Lower-case letters, digits and dashes, starting with a letter; what a rule names it by. | | `value` | string | At most 8 KiB of visible ASCII. Replaces what the name held. | | `hosts` | list of string | The exact names the secret may be sent to, 1 to 16: a brokered rule that injects it and names any other host is refused it. | -| `ref` | string | In place of value: the ARN of a Secrets Manager secret or a Parameter Store parameter the team keeps it in, read as the team (InstallationSettings.team_secrets_role) now and whenever a host asks for it. | +| `ref` | string | In place of value: the ARN of a Secrets Manager secret or a Parameter Store parameter the team keeps it in, read as the team (the setting team_secrets_role) now and whenever a host asks for it. | **Response** diff --git a/src/content/docs/reference/api/types.md b/src/content/docs/reference/api/types.md index 09cfd00..d6a3aca 100644 --- a/src/content/docs/reference/api/types.md +++ b/src/content/docs/reference/api/types.md @@ -24,9 +24,17 @@ sidebar: | `depth` | integer (int32) | | | `teamId` | string | The one team it reaches; empty for every team its person is in. Of that team, the projects it reaches; empty for any. | | `projectIds` | list of string | | -| `maxWorkspaces` | integer or null (int32) | What the workspaces its tree made may hold at once; unset for no bound past its person's. | +| `maxWorkspaces` | integer or null (int32) | How many of the workspaces its tree made - this key and every key delegated from it - may be on a runner at once, and how many cores those may hold: bounds on what runs, unset for none past its person's. A workspace counts while it is being made or started, runs, is stopping or waits for a runner. One that is stopped, suspended or in the trash counts for nothing, whatever disk it keeps, and is counted again when it is started: a start past the bound is refused as a create is, naming the workspaces that hold it. | | `maxCores` | integer or null (int32) | | +## AffectedWorkspace + +| Field | Type | What it is | +|---|---|---| +| `id` | string | | +| `name` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | + ## AgentThread AgentThread is one of the agent's threads as T3 Code names it: what a person is shown of a chat without opening it. @@ -52,6 +60,27 @@ AgentVolume is one agent home of the caller's. | `workspaceId` | string | The workspace mounting it, or empty for one no workspace mounts - which a new workspace may. | | `workspaceName` | string | The name of the workspace that mounts it, or mounted it last. | +## Alert + +Alert is one firing or one resolution of an alert rule, as Grafana sent it. + +| Field | Type | What it is | +|---|---|---| +| `id` | string | The installation event's id. | +| `event` | [InstallationEventType](/reference/api/types/#installationeventtype) | | +| `name` | string | The rule's title (Grafana's alertname). | +| `summary` | string | The rule's summary, rendered for what fired; empty where it has none. | +| `description` | string | | +| `severity` | string | critical, warning; empty where the rule says none. | +| `runbookUrl` | string | Where the dashboard's runbook for it is, as the rule names it: /admin/runbooks/<id>. | +| `labels` | map of string to string | What else the alert's series is labelled with: which host, which volume. | +| `startsAt` | `Timestamp` | When it began firing, and for a resolution when it stopped. | +| `endsAt` | `Timestamp` | | +| `createdAt` | `Timestamp` | When the control plane heard it. | +| `sent` | integer (int32) | How many administrators it was emailed to, is waiting for, and failed every attempt for. | +| `pending` | integer (int32) | | +| `failed` | integer (int32) | | + ## AllowedEmail Allowed email/domain entry @@ -160,6 +189,42 @@ What one item of the installation's config file is. - `CONFIG_ITEM_KIND_SETTING` One of: `CONFIG_ITEM_KIND_UNSPECIFIED`, `CONFIG_ITEM_KIND_SETTING`, `CONFIG_ITEM_KIND_EMAIL`, `CONFIG_ITEM_KIND_PRESET`, `CONFIG_ITEM_KIND_POLICY`, `CONFIG_ITEM_KIND_HOST_CLASS`. +## CostEstimate + +What a workspace would cost at what the last billed day charged. An estimate: that day is over and the next may be charged otherwise, so it is never said without the day it is from. + +| Field | Type | What it is | +|---|---|---| +| `day` | `Timestamp` | The billed day the rate is from: its first instant, UTC. | +| `runningHourMicros` | string or null (int64) | An hour of its machine running, for the cores and memory it holds; unset where that day billed no runner. | +| `stoppedMonthMicros` | string or null (int64) | A month of its disk kept while it is stopped, for the bytes it kept that day; unset where that day billed no volume, or the workspace was made since. | + +## CostMonth + +One month of the trend on Admin → Costs, in millionths of the bill's currency. An estimate of what nobody caused, from the reading it names: a month's last days are revised by the cloud into the next, and read again until they settle. + +| Field | Type | What it is | +|---|---|---| +| `month` | `Timestamp` | Its first instant, UTC. | +| `readAt` | `Timestamp` | The reading its costs are from. | +| `currency` | string | ISO 4217. | +| `totalMicros` | string (int64) | Every cost of its days; idle and unstored are parts of it. | +| `idleMicros` | string (int64) | What the runners cost that no workspace held. | +| `unstoredMicros` | string (int64) | What the volumes' bucket cost for bytes no workspace keeps. | +| `budgetLimitMicros` | string or null (int64) | The installation's budget as the month's last reading in it said; unset where it had none. | + +## CostShare + +What was caused in a month of the cloud's bill, as the installation shares it out (AdminService.GetCosts): by one workspace, or by several summed. In millionths of the bill's currency. A share is of days already billed, never of this hour: the bill is read from the cloud a few times a day. + +| Field | Type | What it is | +|---|---|---| +| `computeMicros` | string (int64) | Of the runners: what was held of them. | +| `storageMicros` | string (int64) | Of the volumes' bucket: what the volumes kept. | +| `sentBytes` | string (int64) | What the traffic came to in the month: the machine's network out and back, and its sessions through the relay, both ways. Counted, not costed. | +| `receivedBytes` | string (int64) | | +| `forwardedBytes` | string (int64) | | + ## CpuUsagePoint CpuUsagePoint is the cores the machine used over a step, on average, against what it held: held_cores attached, lent ones included; cpu_cores its own; max_cpu_cores what it may be lent. @@ -184,6 +249,28 @@ DeletedWorkspace is a workspace in the trash. | `trashedUntil` | `Timestamp` | | | `agentVolumeId` | string | The agent's home it mounts, which its erase keeps for another workspace (ListAgentVolumes); empty for a workspace with none. | +## DeletionEffect + +DeletionEffect is one thing a workspace has and what a delete does to it, in words its owner reads before confirming. + +| Field | Type | What it is | +|---|---|---| +| `fate` | [DeletionFate](/reference/api/types/#deletionfate) | | +| `what` | string | What it is: "Its disk", "2 snapshots". | +| `detail` | string | What happens to it and what follows from that, one sentence. | + +## DeletionFate + +DeletionFate is what a delete does to one thing a workspace has. - `DELETION_FATE_ERASED`: Gone when the delete is done, and not undeleted. - `DELETION_FATE_TRASHED`: In the trash for GetWorkspaceDeletionResponse.trash_days, restored by an undelete, erased when they are up. - `DELETION_FATE_KEPT`: Outlives the workspace, the trash's days included. + +One of: `DELETION_FATE_UNSPECIFIED`, `DELETION_FATE_ERASED`, `DELETION_FATE_TRASHED`, `DELETION_FATE_KEPT`. + +## DepartureFate + +DepartureFate is what taking a host out does to one workspace on it. - `DEPARTURE_FATE_MOVES`: Suspended with its memory and resumed on another runner with room. - `DEPARTURE_FATE_STAYS`: Keeps its place on this runner: a drain moves only what runs. - `DEPARTURE_FATE_LOSES_PLACE`: Suspended at a published snapshot: its disk is let go of here, and its next start is on another runner, from what it published. - `DEPARTURE_FATE_HOLDS`: It keeps the host: the removal is refused while it is here. A group's machine is held by a disk only here, until it is stopped, which publishes it, or moved; a revoked host by any workspace that names it. - `DEPARTURE_FATE_STOPS`: Its disk is only here and is left here: it runs again only once recovered onto another runner, giving up what this one had not published. + +One of: `DEPARTURE_FATE_UNSPECIFIED`, `DEPARTURE_FATE_MOVES`, `DEPARTURE_FATE_STAYS`, `DEPARTURE_FATE_LOSES_PLACE`, `DEPARTURE_FATE_HOLDS`, `DEPARTURE_FATE_STOPS`. + ## DeviceLoginStatus - `DEVICE_LOGIN_STATUS_PENDING`: User hasn't completed login yet - `DEVICE_LOGIN_STATUS_COMPLETE`: Login complete, token available - `DEVICE_LOGIN_STATUS_EXPIRED`: Device code expired - `DEVICE_LOGIN_STATUS_DENIED`: User denied the request @@ -232,11 +319,10 @@ ExtensionRelease is one release of an extension: the supervisor, or the VS Code |---|---|---| | `version` | string | The release's own name: the control plane's version for the supervisor; for the VS Code servers, the versions it carries joined with +, unless it was named otherwise. | | `releasedAt` | `Timestamp` | | -| `workspaces` | string (int64) | How many workspaces run it; one that nothing runs and is not the newest is collectable. | | `newest` | boolean | Whether a start is given this one. | | `volumeId` | string | Its volume in the store, which RemoveStoredVolume takes when removable. | -| `removable` | boolean | Not the newest and nothing runs it: RemoveStoredVolume removes it, and so does the collect-releases job on its own. | -| `keptBecause` | string | Why it stays, in words, where it may not go. | +| `removable` | boolean | Not the newest, and open in no machine and no suspended memory: RemoveStoredVolume removes it, and so does the collect-releases job on its own. A release only stopped workspaces last ran is removable, since their next start is given the newest. | +| `keptBecause` | string | Why it stays, in words, where it may not go: how many machines and suspended memories have it open, or that it is the newest. | ## ExtensionVersion @@ -246,7 +332,7 @@ ExtensionVersion is a release and how many workspaces are on it: what an update |---|---|---| | `extension` | string | | | `version` | string | | -| `workspaces` | string (int64) | | +| `workspaces` | string (int64) | The machines that have it open and the suspended memories that do: a stopped workspace is given the newest at its next start, and is on none. | | `newest` | boolean | Whether a start now would choose this release. | ## FieldValue @@ -310,6 +396,8 @@ One host class's group, as the control plane last saw it. | `launchFailedAt` | `Timestamp` | | | `giveBackRefused` | string | A machine of the group the cloud would not take back, in its own words - a role or a policy somebody has to change - and when; it is asked again every quarter hour until it is taken. Empty while none is refused. | | `giveBackRefusedAt` | `Timestamp` | | +| `capacityRefused` | boolean | launch_failure is the cloud having no machine of the kind the class asks for - no spot capacity, or the spot quota spent - which the class's spot setting, or its fallback, is what changes; false for any other failure, and while there is none. | +| `onDemandUntil` | `Timestamp` | Set while a spot class's group starts on-demand machines because the cloud refused it spot (HostClass.fallback_on_demand): until this, and spot again after. Its class still says spot. | ## FleetHostVersion @@ -399,6 +487,8 @@ HealthCheck is one part of the installation as the control plane knows it. | `status` | [HealthStatus](/reference/api/types/#healthstatus) | | | `reason` | string | reason says in a line what was found, OK or not. | | `knownAt` | `Timestamp` | known_at is when what decided the status was so: the moment it was asked for a part asked now, the collector's last report, the last WAL file archived or failed, a failed job's run. | +| `problems` | list of [Problem](/reference/api/types/#problem) | What is wrong in this part, each on its own: one per host, one per job, one for a part that is a single thing. Empty while the part is OK. At most 20 are listed, the worst first; problem_count is how many there are. | +| `problemCount` | integer (int32) | | ## HealthCheckResult @@ -411,7 +501,7 @@ HealthCheck is one part of the installation as the control plane knows it. ## HealthPart -HealthPart is which part of the installation a check is of. - `HEALTH_PART_DATABASE`: The control plane's database answers a query. - `HEALTH_PART_DATABASE_ARCHIVE`: The database's continuous archive keeps up: its last WAL file sent and how many wait. Its base backups are taken by a timer outside the control plane, which records nothing it can read. - `HEALTH_PART_LOG_STORE`: The log store (Quickwit) beside the control plane answers that it is ready. - `HEALTH_PART_COLLECTOR`: The collector sends what it is given: spans to the trace store, samples to the metric store (GetTelemetryStatus). It is what says whether the metric store takes writes - the control plane is not one of the metric store's readers. - `HEALTH_PART_JOBS`: The control plane's periodic work: a job whose last run failed (ListJobs). - `HEALTH_PART_HOSTS`: The hosts registered and not taken out of the fleet, against those whose command stream is up. - `HEALTH_PART_WAITING_WORKSPACES`: Workspaces waiting for a runner, across the whole fleet. +HealthPart is which part of the installation a check is of. - `HEALTH_PART_DATABASE`: The control plane's database answers a query. - `HEALTH_PART_DATABASE_ARCHIVE`: The database's continuous archive keeps up: its last WAL file sent and how many wait. Its base backups are taken by a timer outside the control plane, which records nothing it can read. - `HEALTH_PART_LOG_STORE`: The log store (Quickwit) beside the control plane answers that it is ready. - `HEALTH_PART_COLLECTOR`: The collector sends what it is given: spans to the trace store, samples to the metric store (GetTelemetryStatus). It is what says whether the metric store takes writes - the control plane is not one of the metric store's readers. - `HEALTH_PART_JOBS`: The control plane's periodic work: a job whose last run failed (ListJobs). - `HEALTH_PART_HOSTS`: The hosts registered and not taken out of the fleet: those whose command stream is not up, and those that answer and fail a check of their own (HostCheck). - `HEALTH_PART_WAITING_WORKSPACES`: Workspaces waiting for a runner, across the whole fleet. One of: `HEALTH_PART_UNSPECIFIED`, `HEALTH_PART_DATABASE`, `HEALTH_PART_DATABASE_ARCHIVE`, `HEALTH_PART_LOG_STORE`, `HEALTH_PART_COLLECTOR`, `HEALTH_PART_JOBS`, `HEALTH_PART_HOSTS`, `HEALTH_PART_WAITING_WORKSPACES`. @@ -458,6 +548,14 @@ One of: `HEALTH_STATUS_UNSPECIFIED`, `HEALTH_STATUS_OK`, `HEALTH_STATUS_DEGRADED | `hostname` | string | hostname is the name the host's machine goes by, as it last registered it: how a person tells one host from another where its id says nothing. Empty for a host that has not registered since it was made. | | `removal` | [HostRemoval](/reference/api/types/#hostremoval) | removal is how this host leaves the fleet, decided by where it came from: the only removal the API takes for it, so the dashboard offers that one and no other. | | `removalRefused` | string | removal_refused is why that removal cannot run now - a workspace whose disk is there - in words an administrator acts on; empty while it can. | +| `keysGiven` | integer (int32) | keys_given is how many teams' disks this host was given the key of: what revoking it, or removing it as taken, marks for re-keying. | +| `machineId` | string | Where the host's machine is, as its cloud said when the host joined - never as the host said of itself: the machine's id and type as that cloud names them, its zone and its region. Each empty for a host that joined with no cloud (a registration token: a development stack, a machine of one's own), and for what its cloud did not say. | +| `machineType` | string | | +| `zone` | string | | +| `region` | string | | +| `reclaimable` | boolean or null | reclaimable is whether the cloud may take the machine back, as its cloud said of the instance when it joined - a spot machine is, whatever its class says: a group that fell back to on-demand, or one whose class was changed, starts machines its class does not describe. Unset where nothing says: a host that joined with no cloud, or one its cloud named nothing of. | +| `holders` | list of [HostHolder](/reference/api/types/#hostholder) | holders is what keeps this host in the fleet, from the database: the workspaces whose disk is only here - whichever list of theirs leaves them out, a prebuild's build, a team's, another person's, a failed one - and the volumes still here that are no such workspace's disk, a trashed workspace's or an agent's home, each of which forgetting the host must detach first. At most 10; holder_count is how many there are. | +| `holderCount` | integer (int32) | | ## HostCheck @@ -483,7 +581,39 @@ A kind of runner: the shape of the machines a group of the installation's cloud | `vcpusMax` | integer (int32) | | | `memoryMibPerVcpu` | integer (int32) | The least memory a vCPU of it has, in MiB; 0 is 2048. | | `spot` | boolean | A machine the cloud may take back (each host drains first), or an on-demand one. | -| `maxHosts` | integer (int32) | The most hosts of it; 0 is no limit of its own (InstallationSettings.autoscaling_max_hosts bounds them all). | +| `maxHosts` | integer (int32) | The most hosts of it; 0 is no limit of its own (the setting autoscaling_max_hosts bounds them all). | +| `fallbackOnDemand` | boolean | A spot class whose group the cloud refuses spot machines for capacity starts on-demand ones for a while, and then spot again (FleetClass.on_demand_until); spot itself is not changed. Off, the class's workspaces wait until somebody changes spot. Only a spot class has it. | + +## HostDeparture + +HostDeparture is what draining the host, and removing it as Host.removal says, would do to each workspace that names it - the ones a drain moves, the ones that stay, the ones that lose their place on it and the ones that hold it - so the confirmation says it before it is done. + +| Field | Type | What it is | +|---|---|---| +| `workspaces` | list of [WorkspaceDeparture](/reference/api/types/#workspacedeparture) | At most 20, the running first; count is how many there are. | +| `count` | integer (int32) | | + +## HostHolder + +HostHolder is one thing a host holds that its removal would lose or has to wait for, with whose it is and what last happened to it. + +| Field | Type | What it is | +|---|---|---| +| `kind` | [HostHolderKind](/reference/api/types/#hostholderkind) | | +| `workspaceId` | string | The workspace it is or was the disk of; for an agent's home, the workspace that mounts it, or that last did by name alone (workspace_id empty). | +| `workspaceName` | string | | +| `volumeId` | string | The volume, for a holder that is a volume (a trashed workspace's disk, an agent's home); empty for a workspace, whose disk the workspace names. | +| `ownerId` | string | Whose it is. | +| `ownerEmail` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | The workspace's state; UNSPECIFIED for an agent's home no workspace mounts. | +| `lastEvent` | [WorkspaceEventType](/reference/api/types/#workspaceeventtype) | What last happened to the workspace, and when: its history's newest entry. UNSPECIFIED and unset where it has none. | +| `lastEventAt` | `Timestamp` | | + +## HostHolderKind + +HostHolderKind is what a holder is, which says which list of whose shows it. - `HOST_HOLDER_KIND_WORKSPACE`: A person's own workspace, in their workspaces list. - `HOST_HOLDER_KIND_TEAM_WORKSPACE`: A workspace of a team, in its members' lists. - `HOST_HOLDER_KIND_PREBUILD_BUILD`: A prebuild's build: kept stopped as its prebuild's anchor, and in nobody's list. - `HOST_HOLDER_KIND_TRASHED_WORKSPACE`: The disk of a workspace in its owner's trash. - `HOST_HOLDER_KIND_AGENT_HOME`: A person's agent's home (AgentVolume). + +One of: `HOST_HOLDER_KIND_UNSPECIFIED`, `HOST_HOLDER_KIND_WORKSPACE`, `HOST_HOLDER_KIND_TEAM_WORKSPACE`, `HOST_HOLDER_KIND_PREBUILD_BUILD`, `HOST_HOLDER_KIND_TRASHED_WORKSPACE`, `HOST_HOLDER_KIND_AGENT_HOME`. ## HostNetworkPolicy @@ -596,37 +726,23 @@ InjectionStatus is one header the egress proxy sets on the workspace's requests | `from` | string | What the header is set from: a team secret by name, "<forge> app", or "identity for <audience>". | | `held` | boolean | Whether the team holds it: the secret is set, the forge app is set, an identity is always held. False is a request refused with credentials. | -## InstallationSettings +## InstallationEventType + +What happened to the installation itself, which its administrators are told of: an event of no workspace, recorded where the control plane hears it. - `INSTALLATION_EVENT_TYPE_ALERT_FIRING`: One of the release's alert rules fired in the installation's Grafana - `INSTALLATION_EVENT_TYPE_ALERT_RESOLVED`: A rule that fired is no longer past its threshold + +One of: `INSTALLATION_EVENT_TYPE_UNSPECIFIED`, `INSTALLATION_EVENT_TYPE_ALERT_FIRING`, `INSTALLATION_EVENT_TYPE_ALERT_RESOLVED`. + +## InstallationPlace -InstallationSettings contains global settings for the Spin installation +InstallationPlace is where the installation runs, as its control plane's machine read it once when it started - never asked of the cloud again per request - and the release it runs. What a cloud did not say, or an installation with no cloud (a development stack, a machine of one's own), is empty. | Field | Type | What it is | |---|---|---| -| `allowedImages` | list of string | Whitelist of allowed images (empty = all allowed) | -| `maxWorkspacesPerUser` | integer (int32) | Maximum workspaces per user (0 = unlimited) | -| `defaultNetworkTags` | list of string | What a workspace created without a network tag of its own runs under, by policy name. Empty means "offline", the policy every host has and which allows nothing: no value of this field means open. An installation that wants such a workspace out declares a policy saying what it may reach and names it here. | -| `updatedAt` | `Timestamp` | | -| `telemetryTracesKeepPercent` | integer (int32) | What the installation's collector keeps, decided at the collector (internal/controlplane/domain/telemetrypolicy). A trace with an error in any span is always kept, and so is one slower than telemetry_traces_slow_ms (0: none is kept for being slow); of the rest, telemetry_traces_keep_percent, 0 to 100 and 100 until an admin says otherwise. | -| `telemetryTracesSlowMs` | integer (int32) | | -| `telemetryMetricsDrop` | list of string | The metrics that are not kept, by name; a pattern may begin or end with \*. | -| `telemetryPerVolumeHistograms` | list of string | Read only: the metrics that cost most to keep, a histogram per volume, which a surface may offer to drop as one. | -| `workspaceTrashDays` | integer (int32) | How many days a deleted workspace stays in the trash, whole, for its owner to undelete before it is erased: 0 is no trash, and 7 is what an installation that says nothing keeps. | -| `identityAudiences` | list of string | Who a workspace's identity token may be for; empty is anybody. What the installation's issuer gives, not who trusts it: a relying party still decides which subjects it admits. | -| `volumeRpoSeconds` | integer (int32) | How far behind the object store a workspace's volume may fall, in seconds, where its preset does not say: what it commits on (v6 §11). 0 is none - it commits on its size and its snapshots alone - and 300 is what an installation that says nothing keeps. | -| `diskReservePercent` | integer (int32) | How a host's disk is lent to workspaces. A volume holds what it wrote, not its size: a host is charged, per volume, the larger of disk_reserve_percent of its size and what it wrote, and takes new ones up to 80% of its device; disk_commit_max_percent bounds the sizes summed - every volume filling at once. 50 and 200 are what an installation that says nothing lends; 100 and 100 lends nothing past what the device holds. | -| `diskCommitMaxPercent` | integer (int32) | | -| `workspaceIdleSuspendMinutes` | integer (int32) | How many minutes a running workspace may go unused - nobody working over its ssh, editor or forwarded ports, and its cores all but still - before it is suspended with its processes, which its next start resumes. 0 suspends none; 60 is what an installation that says nothing keeps, and a week is the most. | -| `workspaceMaxRunningMinutes` | integer (int32) | How many minutes a workspace may run at a stretch, used or not, before it is suspended the same way: a process that keeps its cores busy is use to the rule above, and this is what ends it. Counted from its machine's start, which a resume is too. 0 caps none; 720 is what an installation that says nothing keeps, and a week is the most. | -| `workspaceLeaseMaxMinutes` | integer (int32) | How many minutes a process may keep its workspace awake at a stretch (a lease: `spin-supervisor keep -- <cmd>`, a .spin.yaml task with keep_awake). While a lease is held and within this of its start, nobody is asked, nothing idle is suspended, and the running cap waits for the lease's end. 0 honours no lease; 480 is what an installation that says nothing keeps, and a week is the most. | -| `teamSecretsRole` | string | The IAM role a team's secret kept in Secrets Manager or Parameter Store is read under, in a session tagged spin-team-id with the team: its policy lets that session read only what carries the same tag. Empty, no secret is kept as a reference. | -| `projectCreation` | string | Who makes projects (ProjectService.CreateProject): "everyone" signed in, or only the instance's "admins". Empty is everyone. | -| `autoscalingIdleMinutes` | integer (int32) | How the runner fleet is sized (GetFleet): the minutes it may be idle before it is emptied, above zero; the quiet hours, HH:MM-HH:MM in the IANA time zone, when it is emptied at once, empty for none; and the most hosts it is asked for, under the group's own maximum - zero is that maximum. Each takes effect at the autoscaler's next pass. | -| `autoscalingQuietHours` | string | | -| `autoscalingTimeZone` | string | | -| `autoscalingMaxHosts` | integer (int32) | | -| `hostShellNeedsApproval` | boolean | A root shell on a host (HostShellService): whether a second administrator approves each one before its host is touched, and the most minutes one may be open, 30 where unset. | -| `hostShellMaxMinutes` | integer (int32) | | -| `prebuildRetireUnusedDays` | integer (int32) | How many days a ready prebuild may go without a workspace opened from it before it is retired and its disk given back - the newest of its ref too, so a project nobody opens stops holding one, and its next open starts from the image. It is not built again until its ref moves to another commit, or an admin runs it. Counted from its build for one never opened. 0, what an installation that says nothing keeps, retires none for this; a year is the most. | +| `provider` | string | The provider the installation was made on, as its document names it: "aws", "self-hosted". | +| `account` | string | The cloud account it runs in, as that cloud names it. | +| `region` | string | | +| `release` | string | The release the control plane runs, "v20261112.01". | +| `machineImage` | string | The machine image the control plane's machine booted, as its cloud names it. | ## Job @@ -639,7 +755,8 @@ InstallationSettings contains global settings for the Spin installation | `lastRun` | [JobRun](/reference/api/types/#jobrun) | Unset until it has run on this installation. | | `lastSucceededAt` | `Timestamp` | | | `title` | string | The job as a person names it: "Storage collection" for storage-gc. | -| `purpose` | string | One sentence of what it does and what stays undone while it fails. | +| `does` | string | A sentence of what it does. | +| `whileFailing` | string | What stays undone while it fails, said after "While it fails, ": "new workspaces start from an older prebuild". What a failure's verdict says, so whoever reads it knows how soon it needs them. | | `recentRuns` | list of [JobRun](/reference/api/types/#jobrun) | Its newest runs, newest first, at most twenty: what a list of jobs shows each one's recent outcomes and durations from, without asking for every job's runs. Each has its id, start, end and outcome only; ListJobRuns has the rest. | ## JobRun @@ -653,7 +770,7 @@ InstallationSettings contains global settings for the Spin installation | `outcome` | [JobRunOutcome](/reference/api/types/#jobrunoutcome) | | | `counts` | map of string to string (int64) | What it did, by the job's own names for it: objects deleted, bytes freed, hosts forgotten. | | `error` | string | Why it failed; empty when it did not. | -| `traceId` | string | The run's trace, empty when it was not recorded. | +| `traceId` | string | The run's trace id, which every record it logged carries; empty when it was not recorded. Whether the store kept the trace is LogService.KeptTraces's to say. | | `askedBy` | string | The admin who ran it by hand; empty for a run the schedule started. | ## JobRunOutcome @@ -814,10 +931,13 @@ One of: `NOTIFICATION_DELIVERY_STATE_UNSPECIFIED`, `NOTIFICATION_DELIVERY_STATE_ ## NotificationPreference +One kind of event and whether the caller is emailed about it: a workspace's event, or the installation's (an administrator's alone). Exactly one of event and installation_event is set. + | Field | Type | What it is | |---|---|---| | `event` | [WorkspaceEventType](/reference/api/types/#workspaceeventtype) | | | `email` | boolean | | +| `installationEvent` | [InstallationEventType](/reference/api/types/#installationeventtype) | | ## ObservedDestination @@ -884,6 +1004,16 @@ PasskeyPurpose is what a ceremony's challenge is for. - `PASSKEY_PURPOSE_REGISTE One of: `PASSKEY_PURPOSE_UNSPECIFIED`, `PASSKEY_PURPOSE_REGISTER`, `PASSKEY_PURPOSE_ADD_PASSKEY`, `PASSKEY_PURPOSE_ADD_SSH_KEY`, `PASSKEY_PURPOSE_DELETE_PASSKEY`, `PASSKEY_PURPOSE_ADD_GUEST`, `PASSKEY_PURPOSE_CREATE_EXEC_KEY`. +## PlatformCost + +What one role's resources of one of the cloud's services cost, in millionths of the bill's currency: the control plane, the proxy and the database are three roles of one EC2. + +| Field | Type | What it is | +|---|---|---| +| `role` | string | As the resources carry it (on AWS, their spin:role tag); empty where they carry none, which is said as untagged. | +| `service` | string | | +| `micros` | string (int64) | | + ## PlatformImage | Field | Type | What it is | @@ -945,6 +1075,8 @@ A prebuild: one build of a project's ref at a commit, and the snapshot it left. | `reportedAt` | `Timestamp` | When its build's guest said the checkout and create tasks were done; unset before it did. | | `spinYaml` | boolean | Whether the checkout had a .spin.yaml at its root, as the guest reported: without one the build is the clone alone, with no create tasks run. Read only with reported_at. | | `storedBytes` | string or null (int64) | What it keeps in the object store, as storage-gc last read it; unset before a reading counted it. | +| `buildWorkspaceId` | string | The workspace that ran its build (workspaces.prebuild_id), while its row is kept, and whose it is: its events, logs and traces are its owner's to read, and no list of workspaces shows it. | +| `buildWorkspaceOwnerId` | string | | ## PrebuildRefNotFound @@ -961,12 +1093,63 @@ A prebuild ref the project's forge has not. One of: `PREBUILD_STATE_UNSPECIFIED`, `PREBUILD_STATE_BUILDING`, `PREBUILD_STATE_READY`, `PREBUILD_STATE_FAILED`, `PREBUILD_STATE_RETIRED`. +## PrebuildsKept + +What a project's prebuilds keep in the volumes' bucket, and what that would cost a month at what the last billed day charged: an estimate, never said without the day it is from. + +| Field | Type | What it is | +|---|---|---| +| `storedBytes` | string (int64) | As the store was last read (Admin's Storage, PREBUILD). | +| `day` | `Timestamp` | The billed day the rate is from: its first instant, UTC. Unset, with month_micros, where no day before the reading billed the bucket, or the prebuilds keep nothing. | +| `monthMicros` | string or null (int64) | | + ## PreemptionSource PreemptionSource mirrors spin.agent.v1.PreemptionSource, which is what reaches the host. One of: `PREEMPTION_SOURCE_UNSPECIFIED`, `PREEMPTION_SOURCE_AWS`, `PREEMPTION_SOURCE_GCP`, `PREEMPTION_SOURCE_AZURE`. +## Problem + +Problem is one thing wrong with the installation, said whole: why, what it takes down, what shows it and what to do. Nothing is kept of it - it is read off the installation as it is now, and is gone when what caused it is. + +| Field | Type | What it is | +|---|---|---| +| `cause` | string | cause says in a line what was found. | +| `since` | `Timestamp` | since is when what decided it was so, as HealthCheck.known_at is: a host's command stream last heard, a check's last run, a job's failed run. Unset where nothing says. | +| `impact` | [ProblemImpact](/reference/api/types/#problemimpact) | | +| `evidence` | list of [ProblemEvidence](/reference/api/types/#problemevidence) | What shows it, each a thing the console has a page for. | +| `runbookId` | string | The runbook to follow (Runbook.id), of the runbooks this release serves: never empty. | +| `actions` | list of [ProblemAction](/reference/api/types/#problemaction) | What the console itself can do about it, each on the host or the job the problem is of. | +| `hostId` | string | The host the problem is of, and the name its machine goes by; empty for one that is not a host's. | +| `hostname` | string | | +| `job` | string | The job (Job.name) the problem is of, and its title; empty for one that is not a job's. | +| `jobTitle` | string | | + +## ProblemAction + +ProblemAction is something the console can do about a problem, each an RPC it has. - `PROBLEM_ACTION_CORDON_HOST`: DrainHost without suspend: the host takes no new workspace and keeps what it runs. - `PROBLEM_ACTION_DRAIN_HOST`: DrainHost with suspend: its workspaces are moved to other hosts. - `PROBLEM_ACTION_RUN_JOB`: RunJobNow: the job is run again, now. - `PROBLEM_ACTION_RECOVER_WORKSPACE`: RecoverUserWorkspace, of each workspace the problem names: its disk is on a host that is gone, and it runs again only once moved off it. + +One of: `PROBLEM_ACTION_UNSPECIFIED`, `PROBLEM_ACTION_CORDON_HOST`, `PROBLEM_ACTION_DRAIN_HOST`, `PROBLEM_ACTION_RUN_JOB`, `PROBLEM_ACTION_RECOVER_WORKSPACE`. + +## ProblemEvidence + +ProblemEvidence names one thing that shows a problem. The console turns each into a link to its own page; none is a URL. + +No fields: `{}`. + +## ProblemImpact + +ProblemImpact is what a problem takes down: the workspaces under it as rows of the database, or, for a part with nothing under it, what stops working. + +| Field | Type | What it is | +|---|---|---| +| `stops` | string | What stops working, in a line: "log lines are not kept". Empty where the rows say it. | +| `runningWorkspaces` | integer (int32) | The workspaces it affects, counted whole: running on the host now, and at rest with their disk only there - stopped or failed, each one's next start is on that host. For workspaces waiting for a host, waiting_workspaces. | +| `restingWorkspaces` | integer (int32) | | +| `waitingWorkspaces` | integer (int32) | | +| `workspaces` | list of [AffectedWorkspace](/reference/api/types/#affectedworkspace) | At most 10 of them, the running first, then by name: the counts above are the whole. | + ## Project A project: a repository and the shape of the workspace that opens it. @@ -1029,18 +1212,6 @@ A workspace open on a project, as the project's admins read it: whose it is and | `details` | map of string to string | What the supervisor held of it, in its own words: which user in the guest, which process, why the policy refused. | | `at` | `Timestamp` | When the attempt was made, as the guest clock held it. | -## RateLimitSettings - -RateLimitSettings configures API rate limiting - -| Field | Type | What it is | -|---|---|---| -| `enabled` | boolean | Enable rate limiting | -| `authRequestsPerMinute` | integer (int32) | Auth endpoints (login, token exchange, etc.) Requests per minute for auth endpoints | -| `authBurst` | integer (int32) | Burst allowance for auth endpoints | -| `apiRequestsPerMinute` | integer (int32) | API endpoints (all other endpoints) Requests per minute for API endpoints | -| `apiBurst` | integer (int32) | Burst allowance for API endpoints | - ## RefusedDestination | Field | Type | What it is | @@ -1051,6 +1222,23 @@ RateLimitSettings configures API rate limiting | `port` | integer | For tcp and udp; zero otherwise. | | `packets` | string (int64) | | +## RefusedVolume + +RefusedVolume is a disk its host is not serving, and the workspace that cannot run for it. + +| Field | Type | What it is | +|---|---|---| +| `volumeId` | string | | +| `refusal` | string | The token an alert keys on, and the sentence the host sent with it (VolumeStatus). | +| `refusalDetail` | string | | +| `hostId` | string | The host refusing it; empty for a disk placed nowhere. | +| `hostName` | string | | +| `workspaceId` | string | The workspace it is the disk of; empty for a disk no workspace runs on. | +| `workspaceName` | string | | +| `workspaceState` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `ownerId` | string | Whose the workspace is. | +| `ownerEmail` | string | | + ## RegistrationMode Registration mode enum - `REGISTRATION_MODE_OPEN`: Anyone can register - `REGISTRATION_MODE_WHITELIST`: Only whitelisted emails/domains - `REGISTRATION_MODE_CLOSED`: No new registrations @@ -1108,10 +1296,11 @@ ResourcePreset is a class of workspace: its size, and how a workspace of it may | `burstFloorCpuCores` | integer (int32) | What a workspace of a burst mode starts with; at most cpu_cores and memory_mb. | | `burstFloorMemoryMb` | integer (int32) | | | `bandwidthMbps` | integer (int32) | What a workspace of it may carry each way, in megabits a second; 0 is unshaped. | -| `rpoSeconds` | integer (int32) | How far behind the object store a workspace of it may fall, in seconds; 0 is the installation's (InstallationSettings.volume_rpo_seconds). | +| `rpoSeconds` | integer (int32) | How far behind the object store a workspace of it may fall, in seconds; 0 is the installation's (the setting volume_rpo_seconds). | | `hostClass` | string | The host class its workspaces run on (HostClass). | | `agentMinMemoryMb` | integer (int32) | What a workspace has to hold for its agent to start there, in MB, for whoever picks a preset. | | `fromConfig` | boolean | Seeded or applied from the installation's config file (the release's presets where it names none); false is one an administrator added. An edit since does not change it. | +| `minMemoryMb` | integer (int32) | The least any preset may start its workspaces at, in MB: memory_mb for a fixed one, burst_floor_memory_mb for a burst one. A create or update below it is refused; one written before it was asked keeps what it has until an administrator changes it. | ## Resources @@ -1126,6 +1315,15 @@ Resource allocation for workspaces. Initial values (cpu_cores, memory_mb) are us | `maxMemoryMb` | integer (int32) | Max memory in MB (0 = unlimited) | | `bandwidthMbps` | integer (int32) | What a workspace's network carries each way, in megabits a second (0 = unshaped): its preset's, held on its host by a token bucket on each way out of its machine's namespace. Its vsock - ssh, the editor, forwarded ports - is not shaped. | +## RoleCost + +What one role cost on one day, its services summed. + +| Field | Type | What it is | +|---|---|---| +| `role` | string | Empty where the resources carry none. | +| `micros` | string (int64) | | + ## Runbook | Field | Type | What it is | @@ -1161,18 +1359,6 @@ User's uploaded SSH public key | `createdAt` | `Timestamp` | | | `expiresAt` | `Timestamp` | No last_used_at: a user's key authenticates inside the guest, over SSH, where the control plane is not, so the only honest answer it could give is "unknown". Compare ApiKey.last_used_at, which the control plane stamps because it is the thing being presented to. When an enrolled key stops logging in; unset for a key of the person's own. | -## SecuritySettings - -SecuritySettings contains security-related configuration - -| Field | Type | What it is | -|---|---|---| -| `rateLimit` | [RateLimitSettings](/reference/api/types/#ratelimitsettings) | Rate limiting configuration | -| `sessionDurationHours` | integer (int32) | Session configuration Session token lifetime in hours (default: 168 = 7 days) | -| `allowWorkspaceSharing` | boolean | Access control Allow users to share workspaces | -| `allowPublicWorkspaces` | boolean | Allow publicly accessible workspace URLs | -| `updatedAt` | `Timestamp` | | - ## ServiceCost What one of the cloud's services cost on one day, in millionths of the bill's currency. @@ -1204,12 +1390,43 @@ ServiceHealth is one service over a window: the traces begun in it - what it was | `traces` | string (int64) | | | `failedTraces` | string (int64) | | +## SettingDefinition + +SettingDefinition is one setting of the installation as the code defines it. + +| Field | Type | What it is | +|---|---|---| +| `key` | string | | +| `type` | [SettingType](/reference/api/types/#settingtype) | | +| `defaultValue` | string | What is in force where the installation says nothing, as text of the type. | +| `min` | string or null (int64) | | +| `max` | string or null (int64) | | +| `choices` | list of string | The values a STRING setting takes; empty is any. | +| `reach` | [SettingReach](/reference/api/types/#settingreach) | What it touches once saved. | +| `restart` | boolean | It is read when the control plane starts: a change is in force after its next restart. | +| `section` | string | Which page it is on: general, workspaces, fleet, network, security, registration, telemetry, logs, bill, email, storage, installation, control_plane. | +| `label` | string | A few words that name it, and a sentence that says what it does and what its zero means. | +| `summary` | string | | +| `writer` | [SettingWriter](/reference/api/types/#settingwriter) | | + ## SettingReach -SettingReach is what a saved installation setting touches, and when: each setting declares one beside its key (internal/controlplane/settings), so the screen that edits it can say so before the save. - `SETTING_REACH_RUNNING_WORKSPACES`: The workspaces running now, at the control plane's next look at them. - `SETTING_REACH_NEW_WORKSPACES`: Workspaces created from now on; those that exist keep what they were made with. - `SETTING_REACH_NEXT_HEARTBEAT`: Each runner at its next heartbeat, and the workspaces running on it. - `SETTING_REACH_NEXT_REQUEST`: What is asked from now on - a project made, a shell opened, a workspace deleted; what was done before keeps the terms it was done under. - `SETTING_REACH_NEXT_PASS`: The next pass of the job that applies it: the autoscaler, the prebuilds' retirement, the collector's read of its policy, the daily look for a release. +SettingReach is what a saved installation setting touches, and when: each setting's definition declares one (internal/controlplane/settings), so the screen that edits it can say so before the save. - `SETTING_REACH_RUNNING_WORKSPACES`: The workspaces running now, at the control plane's next look at them. - `SETTING_REACH_NEW_WORKSPACES`: Workspaces created from now on; those that exist keep what they were made with. - `SETTING_REACH_NEXT_HEARTBEAT`: Each runner at its next heartbeat, and the workspaces running on it. - `SETTING_REACH_NEXT_REQUEST`: What is asked from now on - a project made, a shell opened, a workspace deleted; what was done before keeps the terms it was done under. - `SETTING_REACH_NEXT_PASS`: The next pass of the job that applies it: the autoscaler, the prebuilds' retirement, the collector's read of its policy, the daily look for a release. One of: `SETTING_REACH_UNSPECIFIED`, `SETTING_REACH_RUNNING_WORKSPACES`, `SETTING_REACH_NEW_WORKSPACES`, `SETTING_REACH_NEXT_HEARTBEAT`, `SETTING_REACH_NEXT_REQUEST`, `SETTING_REACH_NEXT_PASS`. +## SettingType + +SettingType is how a setting's value is written as text, which is how GetSettings answers it and SetSettings takes it. - `SETTING_TYPE_INT`: A whole number in decimal, within the definition's min and max. - `SETTING_TYPE_BOOL`: "true" or "false". - `SETTING_TYPE_STRING`: Text; a definition with choices takes only those. - `SETTING_TYPE_LIST`: A JSON array of strings. - `SETTING_TYPE_JSON`: A JSON document, as the code that reads it defines it. - `SETTING_TYPE_DURATION`: A Go duration ("60s"); min and max are seconds. + +One of: `SETTING_TYPE_UNSPECIFIED`, `SETTING_TYPE_INT`, `SETTING_TYPE_BOOL`, `SETTING_TYPE_STRING`, `SETTING_TYPE_LIST`, `SETTING_TYPE_JSON`, `SETTING_TYPE_DURATION`. + +## SettingWriter + +SettingWriter is who writes a setting, and so where an administrator changes it. - `SETTING_WRITER_ADMIN`: SetSettings, and the installation's file seeds it. - `SETTING_WRITER_FILE`: The installation's file alone: what whoever made the installation knows, and a change of which the dashboard cannot make whole - the proxy holds the base domain too. - `SETTING_WRITER_STORE`: A page of its own, which checks it against the real thing before it is written: the object store, the mail server. - `SETTING_WRITER_CONTROL_PLANE`: The control plane, for itself. + +One of: `SETTING_WRITER_UNSPECIFIED`, `SETTING_WRITER_ADMIN`, `SETTING_WRITER_FILE`, `SETTING_WRITER_STORE`, `SETTING_WRITER_CONTROL_PLANE`. + ## Snapshot A snapshot is a rotation of a workspace's volume, published as a commit. It has no name outside its id. @@ -1271,6 +1488,17 @@ Span is one operation of a trace, as OpenTelemetry names its parts. | `attributes` | `Struct` | | | `events` | list of `Struct` | What happened during it, as the span recorded it: each event's name, time and attributes. | +## StandbyCost + +What the installation cost on its last billed day before the reading with nobody's work counted: the platform, the runners' capacity no workspace held, and the bucket's bytes no workspace keeps. An estimate of a day nobody works: that day is over, the next may be charged otherwise, and a day people worked held some of the runners - so it is never said without its day (finops.StandbyOf). + +| Field | Type | What it is | +|---|---|---| +| `day` | `Timestamp` | The billed day it is from: its first instant, UTC. | +| `platform` | list of [RoleCost](/reference/api/types/#rolecost) | The platform by role, costliest first. | +| `idleMicros` | string (int64) | | +| `unstoredMicros` | string (int64) | | + ## StatusDisagreement | Field | Type | What it is | @@ -1407,6 +1635,16 @@ One of: `TEAM_ROLE_UNSPECIFIED`, `TEAM_ROLE_OWNER`, `TEAM_ROLE_ADMIN`, `TEAM_ROL | `hosts` | list of string | | | `ref` | string | Where the team keeps the value; empty where the control plane does. | +## TeamWorkspaceCost + +What one of a team's workspaces cost in the month. + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | | +| `name` | string | | +| `share` | [CostShare](/reference/api/types/#costshare) | | + ## TelemetryFlow TelemetryFlow is what one exporter of the collector did over the last window. @@ -1454,6 +1692,37 @@ ThirdPartyPart is one program an item downloads, and the license it comes under. | `acceptance` | [ThirdPartyAcceptance](/reference/api/types/#thirdpartyacceptance) | Unset where nobody accepted the terms for it. | | `accepted` | boolean | Whether this installation downloads it: accepted, as the current text. | +## TimelineActor + +TimelineActor is who or what did what a TimelineEvent says. - `TIMELINE_ACTOR_PERSON`: A person signed in, named by actor_id. - `TIMELINE_ACTOR_CONTROL_PLANE`: The control plane, on its own: a reconciliation, a sweep, a job, a decision nobody asked for. - `TIMELINE_ACTOR_HOST`: A host, of itself: it joined, said it was going, lost its stream or came back. + +One of: `TIMELINE_ACTOR_UNSPECIFIED`, `TIMELINE_ACTOR_PERSON`, `TIMELINE_ACTOR_CONTROL_PLANE`, `TIMELINE_ACTOR_HOST`. + +## TimelineEvent + +TimelineEvent is one thing that happened, read from the record that keeps it - the audit log, a workspace's events, a job's runs, a project's prebuilds - and never a copy of it. + +| Field | Type | What it is | +|---|---|---| +| `source` | [TimelineSource](/reference/api/types/#timelinesource) | The record it is read from, and that record's id there: the evidence a reader opens. | +| `id` | string | | +| `at` | `Timestamp` | | +| `actor` | [TimelineActor](/reference/api/types/#timelineactor) | Who or what did it. | +| `actorId` | string | The person, when actor is PERSON: empty for one whose account has since been deleted, and the email is what their row says now. | +| `actorEmail` | string | | +| `what` | string | What happened, in its source's own words: an audit action (host_state_changed), a workspace event type without its prefix (FAILED), a failed job run's job (storage-gc), or for a prebuild prebuild_started, prebuild_built or prebuild_failed. | +| `resourceType` | string | What it happened to: an audit entry's resource_type and id (workspace, host, settings, job by its name), the workspace of a workspace event, the job of a run, the project of a prebuild. | +| `resourceId` | string | | +| `resourceName` | string | The name a person knows it by, where the installation still has its row: a workspace's, a host's machine's, a team's, a project's. Empty otherwise. | +| `details` | string | The JSON the record keeps, verbatim: an audit entry's details, a workspace event's, a failed run's counts and error, a prebuild's ref, commit and error. | +| `traceId` | string | The trace of the request or the run that did it; empty when none was sampled. Whether the store kept it is LogService.KeptTraces's to say. | + +## TimelineSource + +TimelineSource is the record a TimelineEvent is read from. - `TIMELINE_SOURCE_AUDIT`: An audit entry (AuditEntry.id). - `TIMELINE_SOURCE_WORKSPACE_EVENT`: A workspace's event (WorkspaceEvent.id). - `TIMELINE_SOURCE_JOB_RUN`: A job's run that failed (JobRun.run_id). - `TIMELINE_SOURCE_PREBUILD`: A project's prebuild (Prebuild.id). + +One of: `TIMELINE_SOURCE_UNSPECIFIED`, `TIMELINE_SOURCE_AUDIT`, `TIMELINE_SOURCE_WORKSPACE_EVENT`, `TIMELINE_SOURCE_JOB_RUN`, `TIMELINE_SOURCE_PREBUILD`. + ## User | Field | Type | What it is | @@ -1580,6 +1849,20 @@ WorkspaceAction is an act a person can ask of a workspace. - `WORKSPACE_ACTION_F One of: `WORKSPACE_ACTION_UNSPECIFIED`, `WORKSPACE_ACTION_START`, `WORKSPACE_ACTION_STOP`, `WORKSPACE_ACTION_FORCE_STOP`, `WORKSPACE_ACTION_DELETE`, `WORKSPACE_ACTION_FORCE_DELETE`, `WORKSPACE_ACTION_SUSPEND`, `WORKSPACE_ACTION_START_FROM_DISK`. +## WorkspaceAgentThreads + +WorkspaceAgentThreads is one workspace's chats with what a rail names it by: the workspace, its state, and the project it opened. + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | | +| `workspaceName` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `projectId` | string | Empty for a workspace that opened no project. | +| `projectName` | string | | +| `environmentId` | string | As ListAgentThreadsResponse.environment_id: the first part of a thread's page. | +| `threads` | list of [AgentThread](/reference/api/types/#agentthread) | Newest first. | + ## WorkspaceAttempt WorkspaceAttempt is one attempt a host made at a workspace's generation that failed. @@ -1609,6 +1892,20 @@ What one workspace cost in the month, in millionths of the bill's currency, and | `receivedBytes` | string (int64) | | | `forwardedBytes` | string (int64) | | +## WorkspaceDeparture + +WorkspaceDeparture is one workspace on a host and what each way of taking the host out does to it. + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | | +| `workspaceName` | string | | +| `ownerEmail` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `drain` | [DepartureFate](/reference/api/types/#departurefate) | What a drain that moves the host's workspaces (DrainHostRequest.suspend) does to it: MOVES or STAYS. | +| `removal` | [DepartureFate](/reference/api/types/#departurefate) | What Host.removal does to it: LOSES_PLACE or HOLDS. UNSPECIFIED where the host has no removal. | +| `taken` | [DepartureFate](/reference/api/types/#departurefate) | What removing the host as taken (RemoveHostRequest.taken) does to it: LOSES_PLACE or STOPS. UNSPECIFIED for a host that is revoked, whose revoke already is that. | + ## WorkspaceDurability WorkspaceDurability is what of a workspace's disk is published, so it survives its host. While a host serves the disk - running, or stopped with its disk kept there - it is that host's last report; once none does - suspended, or its disk handed back to the store - it is the newest published snapshot of the disk (snapshot_id), which is what a start boots from. @@ -1637,9 +1934,9 @@ Timeline event for workspace state changes ## WorkspaceEventType -- `WORKSPACE_EVENT_TYPE_CREATED`: Workspace was created - `WORKSPACE_EVENT_TYPE_STARTED`: Workspace was started - `WORKSPACE_EVENT_TYPE_STOPPED`: Workspace was stopped; its disk is kept, and no snapshot is taken - `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`: Manual snapshot - `WORKSPACE_EVENT_TYPE_DELETED`: Workspace was deleted - `WORKSPACE_EVENT_TYPE_FAILED`: Workspace start failed - `WORKSPACE_EVENT_TYPE_RESTORED`: Its disk was put back to a snapshot - `WORKSPACE_EVENT_TYPE_RESUMED`: It went on from a snapshot's memory - `WORKSPACE_EVENT_TYPE_BOOTED`: Suspended, its memory could not be resumed, and its disk was booted - `WORKSPACE_EVENT_TYPE_QUARANTINED`: An administrator held it: stopped, and startable by nobody until released - `WORKSPACE_EVENT_TYPE_UNQUARANTINED`: The hold was released: back to stopped, where it was held from - `WORKSPACE_EVENT_TYPE_PLACED`: A workspace that waited for a host was given one, and its disk there - `WORKSPACE_EVENT_TYPE_TRASHED`: Deleted into the trash: its disk kept, its owner able to undelete it - `WORKSPACE_EVENT_TYPE_UNDELETED`: Taken back out of the trash - `WORKSPACE_EVENT_TYPE_MOVED`: Suspended on its host and resumed on another, which had room for what it was granted - `WORKSPACE_EVENT_TYPE_DISPLACED`: Its host was drained or went away: suspended there, it is resumed on another on its own - `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`: A stop on a host that can be taken away could not publish its disk first, so it runs on - `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`: Nobody used it for as long as the installation allows: suspended with its processes, a start resumes them - `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`: It ran at a stretch for as long as the installation allows: suspended with its processes, a start resumes them - `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`: A suspension is minutes away and the question was asked: its terminals, its page, the CLI; a keep answers it - `WORKSPACE_EVENT_TYPE_KEPT`: Somebody kept it awake until a time: no suspension before it - `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`: Its supervisor said nothing for longer than the control plane waits: stopped by force, and a start brings it back - `WORKSPACE_EVENT_TYPE_REHOMED`: Its host stopped answering and it was moved to another on its own, giving up what that host had not published - `WORKSPACE_EVENT_TYPE_HELD`: Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner +- `WORKSPACE_EVENT_TYPE_CREATED`: Workspace was created - `WORKSPACE_EVENT_TYPE_STARTED`: Workspace was started - `WORKSPACE_EVENT_TYPE_STOPPED`: Workspace was stopped; its disk is kept, and no snapshot is taken - `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`: Manual snapshot - `WORKSPACE_EVENT_TYPE_DELETED`: Workspace was deleted - `WORKSPACE_EVENT_TYPE_FAILED`: Workspace start failed - `WORKSPACE_EVENT_TYPE_RESTORED`: Its disk was put back to a snapshot - `WORKSPACE_EVENT_TYPE_RESUMED`: It went on from a snapshot's memory - `WORKSPACE_EVENT_TYPE_BOOTED`: Suspended, its memory could not be resumed, and its disk was booted - `WORKSPACE_EVENT_TYPE_QUARANTINED`: An administrator held it: stopped, and startable by nobody until released - `WORKSPACE_EVENT_TYPE_UNQUARANTINED`: The hold was released: back to stopped, where it was held from - `WORKSPACE_EVENT_TYPE_PLACED`: A workspace that waited for a host was given one, and its disk there - `WORKSPACE_EVENT_TYPE_TRASHED`: Deleted into the trash: its disk kept, its owner able to undelete it - `WORKSPACE_EVENT_TYPE_UNDELETED`: Taken back out of the trash - `WORKSPACE_EVENT_TYPE_MOVED`: Suspended on its host and resumed on another, which had room for what it was granted - `WORKSPACE_EVENT_TYPE_DISPLACED`: Its host was drained or went away: suspended there, it is resumed on another on its own - `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`: A stop on a host that can be taken away could not publish its disk first, so it runs on - `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`: Nobody used it for as long as the installation allows: suspended with its processes, a start resumes them - `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`: It ran at a stretch for as long as the installation allows: suspended with its processes, a start resumes them - `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`: A suspension is minutes away and the question was asked: its terminals, its page, the CLI; a keep answers it - `WORKSPACE_EVENT_TYPE_KEPT`: Somebody kept it awake until a time: no suspension before it - `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`: Its supervisor said nothing for longer than the control plane waits: stopped by force, and a start brings it back - `WORKSPACE_EVENT_TYPE_REHOMED`: Its host stopped answering and it was moved to another on its own, giving up what that host had not published - `WORKSPACE_EVENT_TYPE_HELD`: Its host stopped answering and moving it would give up more than the installation does on its own: it waits for its owner - `WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN`: An administrator moved it off a host that is gone, giving up for its owner what that host had not published; its details say who and why - `WORKSPACE_EVENT_TYPE_OUT_OF_MEMORY`: It ran out of memory: its guest's kernel killed a process (cause "guest", with the kills and their victims), its host found it short at its ceiling for a while (cause "ceiling"), or its host killed the machine (cause "host"). Its details say what it held, its grant and its ceiling. Said, never acted on: nothing resizes a workspace on its own. - `WORKSPACE_EVENT_TYPE_OVERSIZED`: It held far more than it used for a week: the most memory its processes used stayed under half its own, and a smaller preset would fit what it used. Its details say what it used, what it holds, and that preset. Said, never acted on: nothing resizes a workspace on its own. -One of: `WORKSPACE_EVENT_TYPE_UNSPECIFIED`, `WORKSPACE_EVENT_TYPE_CREATED`, `WORKSPACE_EVENT_TYPE_STARTED`, `WORKSPACE_EVENT_TYPE_STOPPED`, `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`, `WORKSPACE_EVENT_TYPE_DELETED`, `WORKSPACE_EVENT_TYPE_FAILED`, `WORKSPACE_EVENT_TYPE_RESTORED`, `WORKSPACE_EVENT_TYPE_RESUMED`, `WORKSPACE_EVENT_TYPE_BOOTED`, `WORKSPACE_EVENT_TYPE_QUARANTINED`, `WORKSPACE_EVENT_TYPE_UNQUARANTINED`, `WORKSPACE_EVENT_TYPE_PLACED`, `WORKSPACE_EVENT_TYPE_TRASHED`, `WORKSPACE_EVENT_TYPE_UNDELETED`, `WORKSPACE_EVENT_TYPE_MOVED`, `WORKSPACE_EVENT_TYPE_DISPLACED`, `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`, `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`, `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`, `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`, `WORKSPACE_EVENT_TYPE_KEPT`, `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`, `WORKSPACE_EVENT_TYPE_REHOMED`, `WORKSPACE_EVENT_TYPE_HELD`. +One of: `WORKSPACE_EVENT_TYPE_UNSPECIFIED`, `WORKSPACE_EVENT_TYPE_CREATED`, `WORKSPACE_EVENT_TYPE_STARTED`, `WORKSPACE_EVENT_TYPE_STOPPED`, `WORKSPACE_EVENT_TYPE_SNAPSHOT_CREATED`, `WORKSPACE_EVENT_TYPE_DELETED`, `WORKSPACE_EVENT_TYPE_FAILED`, `WORKSPACE_EVENT_TYPE_RESTORED`, `WORKSPACE_EVENT_TYPE_RESUMED`, `WORKSPACE_EVENT_TYPE_BOOTED`, `WORKSPACE_EVENT_TYPE_QUARANTINED`, `WORKSPACE_EVENT_TYPE_UNQUARANTINED`, `WORKSPACE_EVENT_TYPE_PLACED`, `WORKSPACE_EVENT_TYPE_TRASHED`, `WORKSPACE_EVENT_TYPE_UNDELETED`, `WORKSPACE_EVENT_TYPE_MOVED`, `WORKSPACE_EVENT_TYPE_DISPLACED`, `WORKSPACE_EVENT_TYPE_STOP_BACKUP_FAILED`, `WORKSPACE_EVENT_TYPE_IDLE_SUSPENDED`, `WORKSPACE_EVENT_TYPE_RUNNING_CAP_SUSPENDED`, `WORKSPACE_EVENT_TYPE_SUSPENSION_DUE`, `WORKSPACE_EVENT_TYPE_KEPT`, `WORKSPACE_EVENT_TYPE_UNRESPONSIVE`, `WORKSPACE_EVENT_TYPE_REHOMED`, `WORKSPACE_EVENT_TYPE_HELD`, `WORKSPACE_EVENT_TYPE_RECOVERED_BY_ADMIN`, `WORKSPACE_EVENT_TYPE_OUT_OF_MEMORY`, `WORKSPACE_EVENT_TYPE_OVERSIZED`. ## WorkspaceFailure @@ -1667,6 +1964,8 @@ Whether the machine's host is reachable at all. A workspace cannot be healthier | `lastHeartbeat` | `Timestamp` | | | `hostname` | string | Which host it is, as a person reads it: the name its machine goes by (Host.hostname), and the class of the group that launched it ("default" for one no group launched). What its owner and guests are shown of the host and no more: its capacity, what else it runs and its policy are an administrator's (HostService). | | `hostClass` | string | | +| `machineType` | string | The host's machine as its cloud names it, said when the host joined (Host.machine_type, Host.zone): what kind of machine the workspace runs on, and where. Empty for a host that joined with no cloud. The machine's id and whether it may be taken back stay an administrator's. | +| `zone` | string | | ## WorkspaceLease @@ -1678,6 +1977,16 @@ WorkspaceLease is a process in the workspace keeping it awake while it lives. | `since` | `Timestamp` | | | `until` | `Timestamp` | until is when the lease stops counting whether or not the process lives: since plus the installation's workspace_lease_max_minutes. | +## WorkspaceNetworkStatus + +WorkspaceNetworkStatus is one policy a workspace runs under, and whether it holds there: the installation declares it, and the workspace's host reports having installed it. A tag no policy is declared for, or one its host has not installed, is what a start is refused over. + +| Field | Type | What it is | +|---|---|---| +| `policy` | string | | +| `declared` | boolean | declared says a policy of this name exists: the installation's, or a team's. | +| `installed` | boolean | installed says the workspace's host last reported enforcing a policy of this name. False for a workspace on no host. | + ## WorkspacePhase WorkspacePhase is what a host is doing to bring a workspace up. - `WORKSPACE_PHASE_DISK`: Waiting for its disk to be served on the host. - `WORKSPACE_PHASE_EXTENSIONS`: Holding the extension releases it runs with. - `WORKSPACE_PHASE_NETWORK`: Setting up its network. - `WORKSPACE_PHASE_MACHINE`: Booting its machine, or resuming it from a checkpoint. - `WORKSPACE_PHASE_FIRST_BOOT`: Its first boot of a disk another person's workspace was cloned from. - `WORKSPACE_PHASE_GUEST`: The machine is up and its workspace inside is starting. - `WORKSPACE_PHASE_BACKUP`: Stopping on a host that can be taken away: its disk is being published before the machine stops, the control plane's doing rather than the host's, and retried if it restarts. diff --git a/src/content/docs/reference/api/workspaceservice.md b/src/content/docs/reference/api/workspaceservice.md index b731f3e..7d36395 100644 --- a/src/content/docs/reference/api/workspaceservice.md +++ b/src/content/docs/reference/api/workspaceservice.md @@ -4,7 +4,7 @@ description: "spin.v1.WorkspaceService: each procedure, its request and its resp source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json" editUrl: false sidebar: - order: 11 + order: 3 --- @@ -147,7 +147,7 @@ Create a new workspace | `name` | string | Optional, generated if empty | | `teamId` | string | Optional: team ID (personal workspace if empty) | | `image` | string | A published image (ListImages); required unless from_snapshot_id is set | -| `preset` | string | The preset it is sized by, by name (AdminService.ListResourcePresets): its cores, memory, disk and how it grows. Empty is the project's preset when project_id names one, otherwise the installation's default preset, or with none marked 1 core, 512 MB and 10 GB free. A disabled preset is refused. | +| `preset` | string | The preset it is sized by, by name (AdminService.ListResourcePresets): its cores, memory, disk and how it grows. Empty is the project's preset when project_id names one, otherwise the installation's default preset, or with none marked 1 core, 2048 MB and 10 GB free. A disabled preset is refused. | | `env` | map of string to string | Environment variables | | `fromSnapshotId` | string | Create workspace from a shared snapshot | | `networkTags` | list of string | network_tags are the network policies this workspace runs under, by name - the same idea as a GCP instance's network tags. The host's ruleset names the tag and holds the set of machines carrying it, so this decides which rules apply without naming a rule. Empty runs the workspace under the installation's default, and under "offline" where it declares none: a create that names nothing reaches nothing, rather than everything. A tag the host has no policy for fails the start rather than being ignored: a workspace asking for a policy the node does not have is a question with two answers, and quietly choosing "run it without" is how one ends up less contained than whoever launched it believes. | @@ -298,6 +298,48 @@ The tail of the workspace's serial console: the kernel, vminit, then the supervi | `truncated` | boolean | truncated says there are lines before the first one here, so what this begins with is not mistaken for the start of the boot. | | `hostId` | string | Which host answered. A console belongs to the machine that wrote it, and a workspace that has moved has nothing to say about the run that failed on the host it left. | +## GetWorkspaceCost + +`POST /spin.v1.WorkspaceService/GetWorkspaceCost` + +What the workspace cost in the month in progress and what it costs while it runs, for whoever reads the workspace itself: its owner, and a guest its owner let in. Nobody else is answered - one workspace's cost says how much another person works. Read from the bill as the installation last read it, never from the cloud. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `month` | `Timestamp` | The month in progress: its first instant, UTC. | +| `readAt` | `Timestamp` | When the bill was last read; unset where it never was - an installation with no bill scope, a cloud that sends none - and then nothing below is known and nothing is to be drawn. | +| `currency` | string | ISO 4217; every amount here is in millionths of it. | +| `share` | [CostShare](/reference/api/types/#costshare) | The month so far. | +| `estimate` | [CostEstimate](/reference/api/types/#costestimate) | Unset where no day before the reading was billed, or none says anything of this workspace. | + +## GetWorkspaceDeletion + +`POST /spin.v1.WorkspaceService/GetWorkspaceDeletion` + +What deleting a workspace would do, asked before it is done: what goes to the trash and for how long, what is erased at once, and what outlives it. It changes nothing. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `workspaceId` | string | | +| `permanently` | boolean | What DeleteWorkspaceRequest.permanently would do: an erase, whatever the trash keeps. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `trashDays` | integer (int32) | How many days the delete keeps the workspace in the trash; 0 is an erase now - the installation keeps no trash, or permanently was asked. | +| `effects` | list of [DeletionEffect](/reference/api/types/#deletioneffect) | Everything the delete does to what the workspace has, ordered erased, trashed, kept. Only what this workspace has: one with no snapshot is told nothing of snapshots. | + ## GetWorkspaceEgress `POST /spin.v1.WorkspaceService/GetWorkspaceEgress` @@ -386,6 +428,7 @@ What every party believes about one workspace, and where they disagree. | `runtime` | [WorkspaceRuntime](/reference/api/types/#workspaceruntime) | What the machine has and uses now, asked of its host. Absent - not zero - when there is no machine to ask about: not running, on no connected host, or a host that did not answer in time. Zero cores would read as a machine that has none. | | `failure` | [WorkspaceFailure](/reference/api/types/#workspacefailure) | Which part failed, when state = FAILED; failure_reason is the detail. Workspace.failure. | | `injections` | list of [InjectionStatus](/reference/api/types/#injectionstatus) | Each header a brokered rule of the workspace's policies sets, and whether what it is set from is there to set: a request to a brokered name is refused when it is not, and this is where that is seen from outside the machine. | +| `network` | list of [WorkspaceNetworkStatus](/reference/api/types/#workspacenetworkstatus) | The policies the workspace's network is under (Workspace.network_tags), in their order, and whether each is declared and installed on its host. Empty for a workspace with no tag, which reaches anything beyond its host's own networks. | ## GetWorkspaceUsage @@ -577,6 +620,22 @@ No fields: `{}`. | `hostsEnforcing` | integer (int32) | Of the hosts connected, how many have installed what the installation declares. They are told on their next heartbeat, so the two differ for a moment after an apply - and go on differing when a host cannot install what was declared, which is the thing worth seeing. | | `hostsConnected` | integer (int32) | | +## ListOwnAgentThreads + +`POST /spin.v1.WorkspaceService/ListOwnAgentThreads` + +The caller's own chats across every workspace of theirs, each workspace's as its supervisor last reported them: what the dashboard's chat mode lists in one rail. Selected by the caller's id, never one from the request, and a key bound to a team or projects lists theirs alone. + +**Request** + +No fields: `{}`. + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `workspaces` | list of [WorkspaceAgentThreads](/reference/api/types/#workspaceagentthreads) | One per workspace of the caller's whose supervisor reported a chat, in project then workspace name order; a workspace in the trash is not here. | + ## ListSnapshots `POST /spin.v1.WorkspaceService/ListSnapshots` @@ -635,6 +694,29 @@ What a workspace attempted past its policy: the refusals its supervisor reported |---|---|---| | `denials` | list of [QuarantineDenial](/reference/api/types/#quarantinedenial) | Newest first. | +## ListWorkspaceTimeline + +`POST /spin.v1.WorkspaceService/ListWorkspaceTimeline` + +What happened to a workspace, newest first, between two instants: its own events and what the audit log records of it and its snapshots, each with who did it - for its owner, read as GetWorkspaceHistory is. AdminService.ListTimeline is the same reading of any workspace, a host, a team, a project or the whole installation. + +**Request** + +| Field | Type | What it is | +|---|---|---| +| `id` | string | | +| `since` | `Timestamp` | Inclusive; unset reads from as far back as the records go. | +| `until` | `Timestamp` | Exclusive; unset is now. | +| `page` | [PageRequest](/reference/api/types/#pagerequest) | page_size is at most 200, and zero is 50. total_count is not counted. | + +**Response** + +| Field | Type | What it is | +|---|---|---| +| `events` | list of [TimelineEvent](/reference/api/types/#timelineevent) | | +| `page` | [PageResponse](/reference/api/types/#pageresponse) | | +| `keptDays` | integer (int32) | kept_days is how long what the timeline reads is kept: before it, an empty timeline is the records gone, not nothing having happened. | + ## ListWorkspaces `POST /spin.v1.WorkspaceService/ListWorkspaces` @@ -717,7 +799,10 @@ Hold a workspace in quarantine: an administrator's decision, held until one rele | Field | Type | What it is | |---|---|---| -| `workspace` | [Workspace](/reference/api/types/#workspace) | | +| `workspaceId` | string | | +| `name` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `quarantineHeld` | boolean | Held, as Workspace.quarantine_held: a running workspace answers STOPPING, and this is what says the stop is the quarantine's. | ## RecoverWorkspace @@ -758,7 +843,10 @@ Release what was quarantined: stopped, with its disk as it was - or, where the s | Field | Type | What it is | |---|---|---| -| `workspace` | [Workspace](/reference/api/types/#workspace) | | +| `workspaceId` | string | | +| `name` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `quarantineHeld` | boolean | | ## ReleaseWorkspaceResources @@ -870,7 +958,10 @@ Set what a workspace's network carries each way, an instance admin's to change: | Field | Type | What it is | |---|---|---| -| `workspace` | [Workspace](/reference/api/types/#workspace) | | +| `workspaceId` | string | | +| `name` | string | | +| `state` | [WorkspaceState](/reference/api/types/#workspacestate) | | +| `bandwidthMbps` | integer (int32) | Megabits a second each way now in force; zero is unshaped. | ## SetWorkspaceNetworkTags diff --git a/src/content/docs/reference/cli/admin.md b/src/content/docs/reference/cli/admin.md index 547886f..453eb77 100644 --- a/src/content/docs/reference/cli/admin.md +++ b/src/content/docs/reference/cli/admin.md @@ -24,11 +24,13 @@ Available Commands: jobs The control plane's periodic work, and running a job now logs Search what the installation's processes logged runner Manage registered runners + settings The installation's settings: read them, and change one shell A root shell on a runner, for a while: asked for, recorded, and the runner retired after storage What the object store holds, and taking out what nothing needs token Manage runner registration tokens versions What is running where: this control plane, each runner and its machine - volume Re-key a workspace's disk + volume Re-key a workspace's disk, and list the disks hosts refuse + workspace Stop, recover or read the egress of a workspace that is somebody else's Flags: -h, --help[=true|false] help for admin @@ -75,7 +77,8 @@ Use "spin admin costs [command] --help" for more information about a command. A month of the bill shared out: the runners by what each workspace held of them - cores and memory, half each - and the volumes' bucket by what its volumes kept. What nobody caused is said apart and never spread: the runners' capacity nobody held (idle), the bucket's bytes no workspace -keeps (images, unreferenced), and the platform's own cost by service. +keeps (images, unreferenced), and the platform's own cost by role and service. First, the standby: +what the installation costs a day with nobody working, estimated from the last billed day. Usage: spin admin costs by [flags] @@ -350,12 +353,20 @@ A runner that is the only home of a workspace's disk is refused, naming them: st publishes each disk, or move them first. A runner registered with a token is revoked instead (spin admin runner revoke). +--taken is for a machine that may be compromised: its credentials are deleted and it is cut off +before its group is asked anything, and every team's disk whose key it was given is marked for +re-keying (spin admin volume rekeys lists them), as a revoke does. Its machine is given back all +the same. With --leave-workspaces it goes although workspaces' disks are only there: each runs +again only once recovered onto another runner (spin workspace recover). + Usage: spin admin runner remove [flags] Flags: - -h, --help[=true|false] help for remove - -y, --yes[=true|false] Skip confirmation + -h, --help[=true|false] help for remove + --leave-workspaces[=true|false] with --taken, remove although workspaces' disks are only on this runner; each runs again only once recovered elsewhere + --taken[=true|false] the machine may be compromised: revoke its credentials and mark every disk whose key it was given for re-keying + -y, --yes[=true|false] Skip confirmation Global Flags: --config string config file (default is $HOME/.spin/config.yaml) @@ -393,6 +404,71 @@ Global Flags: --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false ``` +### spin admin settings + +``` +The installation's settings: read them, and change one + +Usage: + spin admin settings [flags] + spin admin settings [command] + +Available Commands: + get Every setting as it is in force, or one + set Change one setting + +Flags: + -h, --help[=true|false] help for settings + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false + +Use "spin admin settings [command] --help" for more information about a command. +``` + +#### spin admin settings get + +``` +Every setting as it is in force - what the installation says, or its default - with where it is +changed and when a change reaches what it governs. With a key, that setting's value alone, as +'spin admin settings set' takes it. + +Usage: + spin admin settings get [key] [flags] + +Flags: + -h, --help[=true|false] help for get + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + +#### spin admin settings set + +``` +Change one setting, checked as the dashboard's save is: a number within its bounds, true or +false, one of its choices, a list as JSON (["a","b"]), a duration as 60s. The control plane +refuses anything else, and writes nothing. + +Usage: + spin admin settings set [flags] + +Flags: + -h, --help[=true|false] help for set + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + ### spin admin shell ``` @@ -776,13 +852,14 @@ Global Flags: ### spin admin volume ``` -Re-key a workspace's disk +Re-key a workspace's disk, and list the disks hosts refuse Usage: spin admin volume [flags] spin admin volume [command] Available Commands: + refused The disks a host refuses to serve, and whose workspace each is rekey Copy a workspace's disk under a key of its own and destroy the old one rekeys The disks being re-keyed, and the ones a revoked host's keys marked for it @@ -798,6 +875,25 @@ Global Flags: Use "spin admin volume [command] --help" for more information about a command. ``` +#### spin admin volume refused + +``` +List every disk its host refuses to serve, with why, the workspace that cannot run for it +and that workspace's owner. The listing is audited: it names workspaces that are not yours. + +Usage: + spin admin volume refused [flags] + +Flags: + -h, --help[=true|false] help for refused + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + #### spin admin volume rekey ``` @@ -840,3 +936,101 @@ Global Flags: --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false ``` +### spin admin workspace + +``` +Stop, recover or read the egress of a workspace that is somebody else's + +Usage: + spin admin workspace [flags] + spin admin workspace [command] + +Available Commands: + egress What somebody else's workspace has reached beyond this installation + recover Move somebody else's workspace off a host that is gone + stop Stop somebody else's workspace + +Flags: + -h, --help[=true|false] help for workspace + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false + +Use "spin admin workspace [command] --help" for more information about a command. +``` + +#### spin admin workspace egress + +``` +List every destination a running workspace that is not yours has been allowed to reach, +as its owner's 'spin workspace egress' does: the host's counters by destination, and nothing of +what was carried. The reading is audited, with the reason. + +Usage: + spin admin workspace egress [flags] + +Flags: + -h, --help[=true|false] help for egress + --reason string Why: kept on the audit entry for the reading + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + +#### spin admin workspace recover + +``` +Move a workspace that is not yours to another host after the one it was on died, as its +owner's 'spin workspace recover' does. + +The cost is the work the dead host had written and not yet published, and it is the owner's +work. Run without --accept-data-loss to see that cost; nothing is changed until you pass it. +--reason is required, and the server refuses a recover without one: it is kept on the audit +entry, and the owner reads it, with who asked, on the workspace's history. + +Usage: + spin admin workspace recover [flags] + +Flags: + --accept-data-loss[=true|false] go ahead and give up, for its owner, the work the old host had not published + -h, --help[=true|false] help for recover + --reason string Why (required): kept on the audit entry, and on the workspace's history for its owner + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + +#### spin admin workspace stop + +``` +Stop a workspace that is not yours, as its owner's 'spin workspace stop' does: what to do +with one whose stop could not publish its disk, which runs on and holds its host. + +--force gives up what only its host has rather than publish it first. The stop is audited with +the reason, and the workspace's own history names you as who stopped it. + +Usage: + spin admin workspace stop [flags] + +Flags: + --force[=true|false] give up what only its host has rather than publish it first + -h, --help[=true|false] help for stop + --reason string Why: kept on the audit entry for the stop + -y, --yes[=true|false] Skip confirmation + +Global Flags: + --config string config file (default is $HOME/.spin/config.yaml) + -o, --output string text for a person, or json: one document of what the API answered, and no prose (default "text") + --profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default") + --traceparent string the W3C traceparent of the span this command is part of; its own span is sent to the installation unless the profile says telemetry: false +``` + diff --git a/src/content/docs/reference/cli/api-key.md b/src/content/docs/reference/cli/api-key.md index 509dcff..4b30521 100644 --- a/src/content/docs/reference/cli/api-key.md +++ b/src/content/docs/reference/cli/api-key.md @@ -71,8 +71,8 @@ Usage: Flags: --expires string How long the key lasts, at most a year: a whole number of d, w or y (30d, 2w, 1y), or a duration like 36h (default "90d") -h, --help[=true|false] help for create - --max-cores int32 How many cores their workspaces may hold at once - --max-workspaces int32 How many workspaces the key and the keys it delegates may hold at once + --max-cores int32 How many cores their workspaces may hold on runners at once: a stopped one's do not count + --max-workspaces int32 How many workspaces the key and the keys it delegates may have on a runner at once: stopped ones do not count --project strings A project whose workspaces the key reaches, by id (can be repeated; default: any) --scope strings Scopes for the key (can be repeated) --team string The one team whose workspaces the key reaches (default: every team you are in) diff --git a/src/content/docs/reference/cli/open.md b/src/content/docs/reference/cli/open.md index 990e6f1..64f1365 100644 --- a/src/content/docs/reference/cli/open.md +++ b/src/content/docs/reference/cli/open.md @@ -17,8 +17,8 @@ its repository among the projects you see; with more than one, --project says wh workspace is made in --team, or your personal team. A repository no project has is one to make a project of first ('spin project create'). -The workspace is the project's shape, sized by its preset unless --preset names another, and its first access is signed here with a key of your -keyring. It ends in 'spin ssh' unless --no-connect. +The workspace is the project's shape, sized by its preset unless --preset names another. It ends +in 'spin ssh', which logs in with a key you added ('spin ssh-key add'), unless --no-connect. Examples: spin open https://github.com/spin-stack/qboot diff --git a/src/content/docs/reference/cli/project.md b/src/content/docs/reference/cli/project.md index de74304..e4cec1e 100644 --- a/src/content/docs/reference/cli/project.md +++ b/src/content/docs/reference/cli/project.md @@ -12,8 +12,8 @@ sidebar: ``` A project is a repository and the shape of the workspace that opens it: the image, the preset and the network policies, and the ref it opens at. 'spin workspace create --project ' makes a -workspace of that shape, whose guest clones the repository into /workspace/ once -you approve its settings, and runs the repository's own .spin.yaml tasks there. +workspace of that shape, whose guest clones the repository into /workspace/ and +runs the repository's own .spin.yaml tasks there. Projects are the installation's. Whoever makes one is its admin, and it is seen by everybody until its admins share it with only some people and teams ('spin project share'). A private repository @@ -140,9 +140,7 @@ Global Flags: ``` List a project's prebuilds: each build of one of its prebuild refs at a commit, what became of it, -and the snapshot opening the project at that ref is cloned from once it is ready. A ready one is -what its host signed it to be; spin shows that claim, and holds it to its own anchors, when a -workspace opened from it asks you to approve its settings. +and the snapshot opening the project at that ref is cloned from once it is ready. Usage: spin project prebuilds [flags] diff --git a/src/content/docs/reference/cli/ssh-config.md b/src/content/docs/reference/cli/ssh-config.md index b2a6fdf..9cc5d7c 100644 --- a/src/content/docs/reference/cli/ssh-config.md +++ b/src/content/docs/reference/cli/ssh-config.md @@ -16,6 +16,10 @@ reaches a workspace as the host spin.: ssh, scp, rsync, VS Code's Rem Each connection gets a fresh five-minute certificate for your key, and is carried through the platform like 'spin ssh'. A workspace has to be running: a start under way is waited for. +The block asks for the one key exchange a workspace accepts, mlkem768x25519-sha256, which +OpenSSH has from 9.9. An older ssh refuses the whole of ~/.ssh/config over it, for every host, +so nothing is written for one. + Examples: spin ssh-config ssh spin.my-workspace diff --git a/src/content/docs/reference/cli/ssh.md b/src/content/docs/reference/cli/ssh.md index 621b110..08e5199 100644 --- a/src/content/docs/reference/cli/ssh.md +++ b/src/content/docs/reference/cli/ssh.md @@ -15,6 +15,10 @@ Connect to a workspace via SSH. The connection is tunneled through the platform using WebSocket, so no direct network access to the workspace is required. +It runs your ssh, which has to be OpenSSH 9.9 or newer: a workspace accepts only the +post-quantum key exchange mlkem768x25519-sha256, and an older ssh is refused before anything +is asked of the workspace. + Examples: spin ssh my-workspace spin ssh my-workspace -- -L 8080:localhost:80 diff --git a/src/content/docs/reference/cli/vscode.md b/src/content/docs/reference/cli/vscode.md index 7afc900..5a3a489 100644 --- a/src/content/docs/reference/cli/vscode.md +++ b/src/content/docs/reference/cli/vscode.md @@ -17,7 +17,7 @@ Usage: spin vscode [command] Available Commands: - install Install the Spinbox extension that carries this CLI's release into VS Code + install Install the Spinbox extension that carries this CLI's release, and Spinbox Remote, into VS Code Flags: -h, --help[=true|false] help for vscode @@ -36,7 +36,13 @@ Use "spin vscode [command] --help" for more information about a command. ``` Install the Spinbox extension (spin-stack.spinbox) into VS Code, from the release of spin-stack/vscode-spinbox whose VSIXs carry this CLI's release, pulled anonymously from its -public package ghcr.io/spin-stack/vscode-spinbox-release (tag spin-). +public package ghcr.io/spin-stack/vscode-spinbox-release (tag spin-), and beside +it Spinbox Remote (spin-stack.spinbox-remote), which opens the spin+ remote it opens +workspaces through. + +Spinbox Remote uses VS Code's proposed resolvers API, which the Marketplace refuses, so it is +downloaded from the spin-stack/open-remote-ssh release this CLI's release pins, and held to the +SHA-256 pinned with it. Both VSIXs are checked before either is given to the editor. The extension runs a spin CLI it carries, and an installation serves a CLI of its own release alone, so the extension installed is the one carrying this CLI's release - not the newest. Its diff --git a/src/content/docs/reference/configuration/file.md b/src/content/docs/reference/configuration/file.md index e9ddd33..8ff4fd0 100644 --- a/src/content/docs/reference/configuration/file.md +++ b/src/content/docs/reference/configuration/file.md @@ -25,8 +25,9 @@ The file an installation starts from - `installation_config` in Terraform. It se # Every block is optional. settings: - # What a workspace may be created from. Declared and empty is refused: it would allow - # nothing at all. Leave the key out for the installation's default. + # What a workspace may be created from. Empty, or left out, is the release's default image + # alone. Every key under settings is one of internal/controlplane/settings/registry.go, checked + # by the same definition the dashboard and `spin admin settings set` are. allowed_images: - ghcr.io/acme/sandbox:v1 # Zero is no limit. @@ -67,6 +68,10 @@ settings: # image. It is built again when its ref moves to another commit, or when an admin runs it. # Counted from its build for one never opened. 0 or unset retires none for this; at most 365. # prebuild_retire_unused_days: 30 + # A root shell on a host: whether a second administrator approves each before its host is + # touched (off where unset), and the most minutes one may be open, 1 to 1440 (30 where unset). + # host_shell_needs_approval: true + # host_shell_max_minutes: 30 # What a workspace created without a tag of its own runs under, by policy name - the # network_policies block below, or a host's own. Unset, it is "offline", the policy every # host has and which allows nothing: a workspace nobody chose a policy for reaches nothing @@ -129,10 +134,11 @@ registration: - contractor@example.com # What a person may choose when creating a workspace. One preset may be the default. Left out, -# the installation is given the release's: one, "default", 1 core and 512 MB that grow to what its -# host has, on 10 GB. `presets: []` gives it none. +# the installation is given the release's: one, "default", 1 core and 2048 MB that grow to what +# its host has, on 10 GB. `presets: []` gives it none. A preset starts its workspaces at 2048 MB or +# more, which an editor's server needs beside what runs in the workspace. # burst says how a workspace of it grows: fixed (the default) runs at its size; to_preset -# starts at the floor (burst_floor_cpu, burst_floor_memory_mb; 1 and 1024 if left out) and +# starts at the floor (burst_floor_cpu, burst_floor_memory_mb; 1 and 2048 if left out) and # grows to its size; to_host starts at the floor and grows to what its host has. # bandwidth_mbps is what its network carries each way (left out: 100; 0: unshaped, a bill with # no bound that has to be asked for); ssh, the editor and forwarded ports are not held to it. diff --git a/src/content/docs/reference/security/known-gaps.md b/src/content/docs/reference/security/known-gaps.md index 05a884a..56cdfc5 100644 --- a/src/content/docs/reference/security/known-gaps.md +++ b/src/content/docs/reference/security/known-gaps.md @@ -9,7 +9,7 @@ sidebar: -The open items of Spinbox's security model, from spin's [`docs/security/README.md`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/security/README.md) at `b7ba5206f`. The numbers are [its rules](/reference/security/rules/). +The open items of Spinbox's security model, from spin's [`docs/security/README.md`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/security/README.md) at `911f516bc`. The numbers are [its rules](/reference/security/rules/). What no line keeps yet. Each is a design change, not a patch, which is why it is here. @@ -149,13 +149,16 @@ What no line keeps yet. Each is a design change, not a patch, which is why it is frames, is the release's signed files on the proxy image's verity root, under a Caddyfile spin-boot carries, so the control plane does not change it; a proxy running other code serves any code in that frame. -- **The published key set is the identity key's first version, as the proxy serves it.** - `id.` is written on the proxy from the first version of `identity/issuer.json` and its CA, - which no role may delete, so another key the control plane writes publishes - nothing; a proxy running other code publishes any key set. A self-hosted installation +- **The published key set is the identity key's first version and the keys the proxy's document + lists, as the proxy serves it.** `id.` is written on the proxy from the first version of + `identity/issuer.json`, each key under `identity/keys/` whose kid the document Terraform writes + lists (`identity.KeySet`), and each one's CA, signed by its key; so a key the control plane makes + and the apply does not list publishes nothing, and the control plane's exchange verifies against + the same set. A proxy running other code publishes any key set; an apply listing a kid the + control plane printed trusts what the control plane made then. A self-hosted installation serves none. - **I19 holds the identity key to the image a host booted, not to the host.** Every host shares - the key, because the published key set is one key: a runner running other code signs for any + the key it signs with, the newest listed: a runner running other code signs for any subject. A self-hosted installation has no key service, and its control plane keeps the key it grants. diff --git a/src/content/docs/reference/security/rules.md b/src/content/docs/reference/security/rules.md index c14fdc1..8c7aed1 100644 --- a/src/content/docs/reference/security/rules.md +++ b/src/content/docs/reference/security/rules.md @@ -9,7 +9,7 @@ sidebar: -The rules every entry point must keep, from spin's [`docs/security/README.md`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/security/README.md) at `b7ba5206f`. Code cites a rule's number at the line that keeps it. **Gate** is what fails a build when the rule is broken; *none* marks a rule kept only by each place remembering it. What is still open is on [Known gaps](/reference/security/known-gaps/). +The rules every entry point must keep, from spin's [`docs/security/README.md`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/security/README.md) at `911f516bc`. Code cites a rule's number at the line that keeps it. **Gate** is what fails a build when the rule is broken; *none* marks a rule kept only by each place remembering it. What is still open is on [Known gaps](/reference/security/known-gaps/). | # | Rule | Gate | |---|---|---| @@ -19,8 +19,8 @@ The rules every entry point must keep, from spin's [`docs/security/README.md`](h | I4 | Membership and role are asked at the time of the action, not only when the resource was made. | `TestOnlyTheListedProceduresSkipTheTeam`, `TestAnOwnerRemovedFromTheTeamCannotUseItsWorkspace`, `TestAPortOfATeamsWorkspaceIsReachedOnlyByWhoIsInTheTeam`, `TestAStreamIntoATeamsWorkspaceEndsWithItsViewersMembership` - the gate reads handlers that resolve a workspace, and a port's decision is not one, so a new way in that decides for itself is held by a test of its own and not by the gate | | I5 | A cookie-authenticated request that changes state or opens a socket names the dashboard's origin (or none). | `TestAPageOnAnotherSiteCannotSpendTheVisitorsCookie`, `TestEveryHTTPRouteIsMountedBehindAuth`, `TestOnlyTheDashboardIsTheDashboardsOrigin` | | I6 | A credential is minted from `crypto/rand`, stored only hashed or sealed with its resource as AAD, compared in constant time, and revoked with what issued it. A guest's supervisor holds none to the control plane: its host carries the supervisor's stream under the host's own (I8), and revoking the host cuts every stream it carries. A workload may hold a key it exchanged its identity for (I26), a client's credential, which the host carries as a client's call and never as its own. | `TestAGuestsStreamIsCarriedByItsHostAndCutWithIt`; otherwise *none* | -| I7 | Bytes a guest controls are bounded (size, count, time) and made valid (UTF-8, paths) where they cross into the host. | the `CHECK`s; `TestWhatAGuestWritesIsBoundedBeforeItIsARow`, `TestAnAgentsThreadIndexIsBoundedAndReplacedWhole` | -| I8 | A host is trusted only for what is placed on it, and says only what a host may say - its guests' supervisors included, whose streams it carries under its own token. | `runnerMessageBinding` and its two tests; `TestAHostCarriesOnlyTheSupervisorsOfWorkspacesPlacedOnIt`, `TestWhatReachesTheControlPlaneIsTheSupervisorsCall`; `TestAHostsMintedCredentialWritesOnlyItsOwnSubtree`, against the store; `TestAHostsCredentialsLastAQuarterOfAnHour`, `TestAKeyForgottenIsAskedForAgainAndRefused`, `TestARevokedHostsVolumesArePendingRekey`, `TestARekeyedVolumeOpensWithItsOwnKeyAlone`, `TestAHeadWrittenByAHostThatLeftIsPassedOverAndSaid`, the DST's `a-share-opens-only-its-snapshot` and `a-rekeyed-volume-depends-on-no-other-key`; `TestAMovedVolumeIsRestoredFromItsHoldersHistory`, `TestAMoveGrantsTheVolumeOverItsHistory`, the DST's `a-moved-volume-restores-its-new-holders-history`, the storage lane's `TestAMoveFencesTheHostTheVolumeLeft` | +| I7 | Bytes a guest controls are bounded (size, count, time) and made valid (UTF-8, paths) where they cross into the host. | the `CHECK`s; `TestWhatAGuestWritesIsBoundedBeforeItIsARow`, `TestAnAgentsThreadIndexIsBoundedAndReplacedWhole`, `TestAFloodOfNoticesIsAReadingAnInterval` | +| I8 | A host is trusted only for what is placed on it, and says only what a host may say - its guests' supervisors included, whose streams it carries under its own token. | `runnerMessageBinding` and its two tests; `TestAHostCarriesOnlyTheSupervisorsOfWorkspacesPlacedOnIt`, `TestWhatReachesTheControlPlaneIsTheSupervisorsCall`; `TestAHostsMintedCredentialWritesOnlyItsOwnSubtree`, against the store; `TestAHostsCredentialsLastAQuarterOfAnHour`, `TestAKeyForgottenIsAskedForAgainAndRefused`, `TestARevokedHostsVolumesArePendingRekey`, `TestAGroupsRunnerRemovedAsTakenHasItsDisksMarkedForRekeying`, `TestARekeyedVolumeOpensWithItsOwnKeyAlone`, `TestAHeadWrittenByAHostThatLeftIsPassedOverAndSaid`, the DST's `a-share-opens-only-its-snapshot` and `a-rekeyed-volume-depends-on-no-other-key`; `TestAMovedVolumeIsRestoredFromItsHoldersHistory`, `TestAMoveGrantsTheVolumeOverItsHistory`, the DST's `a-moved-volume-restores-its-new-holders-history`, the storage lane's `TestAMoveFencesTheHostTheVolumeLeft` | | I9 | Object-store bytes are checked against the database-anchored digest before use; a shared key is read at its published version. | `TestALayerWrittenOverIsStillReadAsPublished` | | I10 | An effect outside the database happens before the row that names it is forgotten. | *none* | | I11 | Every third-party action is pinned by commit. | `task lint:workflows` | @@ -30,7 +30,7 @@ The rules every entry point must keep, from spin's [`docs/security/README.md`](h | I15 | A program that parses a tenant's bytes on the host opens only the files of its operation. | `TestOnlyARootRunnerOnANewLandlockSaysItHoldsAProgramToItsFiles`, `TestAProductionInstallationPlacesOnlyOnIsolatedHosts`, `TestQemuImgDoesItsWorkWithOnlyItsFiles`, `TestAProgramReadsOnlyWhatItIsGiven`, `TestAProgramWritesOnlyWhatItIsGiven`, `TestAProgramMakesNoSocket`; the VM lane's `TestARootRunnersProgramHoldsNothingOfRoots`; the storage fakes refuse a file a run was not given, as the kernel does | | I16 | A store on the control plane's machine is reached by the installation's administrators only through the control plane, and by its processes only for what each is. | `TestOnlyTheControlPlaneSaysWhoTheAdministratorIs`, `TestGrafanaShowsTheStoreToTheAdministratorItIsTold`, `TestNothingWithoutTheStoresKeyTakesItsMetrics`, `TestTheStoresRunWhileTheirControlPlaneDoes` (each account to each port, against the kernel), `TestTheStoresGuardNamesEveryPortAndOnlyAccountsTheImageHas`; the dashboard lane's `Monitoring access` and `Admin Metrics` | | I17 | Only a workspace's owner and the guests they let in log in to it, each with a key that logs in as them - one they added, or a session's within its twelve hours - under the installation's certificate, never into a prebuild's build; a key, a passkey past the first and a guest are added only with an assertion of the person's passkey over a challenge naming that change, so their session alone adds none; the control plane holds no key that logs in and is never an SSH client of a guest (ADR-0031). | `TestATunnelCertifiesOnlyAKeyThatLogsInAsTheCaller`, `TestAKeyIsAddedOnlyWithThePasskeysAssertionForIt`, `TestLettingAGuestInTakesTheOwnersPasskey`, `TestAKeyLogsInWhileItIsLive`, `TestAPasskeyIsRegisteredOnlyAsItsRelyingPartysOverItsChallenge`, `TestAnAssertionHoldsOnlyAsItsPasskeysOverItsChallenge`, `TestAPreparedRootLetsInOnlyByTheInstallationsCertificate`, `TestOnlyTheListedProceduresAdmitAGuest`; the sshd lanes' `TestASessionKeyLogsInWithACertificateOverIt`, `TestTheTerminalLogsInToTheClaimedGuest`; the VM lane's `TestOnlyTheOwnerAndTheirGuestsLogInToAWorkspace`, `TestAGuestTakenOutStaysOutOfAWorkspaceRestoredToBeforeIt`, `TestTheOwnerLogsInToACloneOfTheirOwnSnapshot`; `TestAGuestIsConnectedToOnlyByAClaimThatHolds`, `TestASessionIsRefusedBeforeTheRelayIsDialled` | -| I19 | A workspace's identity is signed by the host that runs it, for the machine that asked, named as the control plane's set says it - its team while its owner is in it, its owner, image and parent - never for a prebuild's build, and given only for an audience the installation allows; the control plane holds no key that signs one. | `TestATokenIsTheAskingMachinesWorkspace`, `TestATokenIsForAnAudienceTheInstallationAllows`, `TestARunIsHeldToTheFirstKeyItPresents`, `TestAMachineIsNamedAsItsSetSays`, `TestAMachineNamedByNobodyIsGivenNoIdentity`, `TestACloneIsNamedAsMadeFromItsSnapshot`, `TestAStartNamesTheNewestSupervisorRelease`, `TestAHostOpensTheKeyThroughAnAttestedDecrypt`, `TestAHostWritesOnlyItsOwnIdentityRecords`; the VM lane's `TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet` | +| I19 | A workspace's identity is signed by the host that runs it, for the machine that asked, named as the control plane's set says it - its team while its owner is in it, its owner, image and parent - never for a prebuild's build, and given only for an audience the installation allows; the control plane holds no key that signs one. | `TestATokenIsTheAskingMachinesWorkspace`, `TestAMachinesCIDIsUnnamedBeforeAnotherMachineIsGivenIt`, `TestAMachineIsNamedOnlyAtTheCIDTheRuntimeRunsItAt`, `TestTheSameCIDNamedAgainIsAnotherNaming`, `TestARestartedMachineIsNeverSignedForAsTheRunBefore`, `TestASessionReachesOnlyTheMachineItsWorkspaceWasNamedAt`, `TestAWorkspacesCIDIsItsRunningMachines`, `TestAMachinesCIDIsTakenBackBeforeAnotherMachineCanHaveIt`, `TestATokenIsForAnAudienceTheInstallationAllows`, `TestARunIsHeldToTheFirstKeyItPresents`, `TestAMachineIsNamedAsItsSetSays`, `TestAMachineNamedByNobodyIsGivenNoIdentity`, `TestACloneIsNamedAsMadeFromItsSnapshot`, `TestAStartNamesTheNewestSupervisorRelease`, `TestAHostOpensTheKeyThroughAnAttestedDecrypt`, `TestAHostWritesOnlyItsOwnIdentityRecords`; the VM lane's `TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet`, `TestAMachineKilledUnderItsWorkspaceIsNoticedAndPutAway`, `TestAMachinesCIDIsHeardOfOnceHoweverItIsEnded`, `TestAnAdoptedMachinesCIDIsHeardOfOnceHoweverItEnds` | | I18 | What a workspace applies of its owner's - dotfiles, shell, environment, editor settings, Taskfile, the workspace's own environment - is what they saved, with no value that hides what it runs; and the owner's own go only where they alone are root: not to a workspace a guest was asked into, a copy of another person's snapshot, or a prebuild's build. A guest is given its own workspace's environment, never another's. | `TestTheGuestIsHandedTheSettingsItsOwnerSaved`, `TestTheOwnersNewlySavedSettingsReachARunningGuest`, `TestAPrebuildsBuildIsTheProjectsAndNobodysOwn`, `TestTheOwnersSettingsGoOnceAGuestIsLetIn`, `TestSettingsHandedToARunningGuestApplyAsTheyCome`, `TestABuildChecksOutItsCommit`, `TestAPrebuildsCheckoutIsHeldToItsCommit`, `TestSettingsThatHideWhatTheyRunAreRefused`, `TestSettingsThatHideWhatTheyRunAreNotSaved`, `TestAProjectOpensFromItsNewestPrebuild`; the e2e lane's `TestTheOwnersSettingsArriveInTheGuest` | | I20 | No commit holds a credential, in any file. | `task lint:secrets` | | I21 | A team's secret kept as a reference is read only as the team whose secret it is, so a team's admin cannot name another team's secret, or the installation's. | `TestASecretIsReadInASessionTaggedWithItsTeam`, `TestASecretSetAsAReferenceIsReadAsTheTeamBeforeItIsKept`, `TestASecretKeptAsAReferenceIsReadAsTheTeamThatKeepsIt`; spin-terraform-aws's posture run `a_teams_secret_is_read_only_as_the_team` | @@ -39,3 +39,4 @@ The rules every entry point must keep, from spin's [`docs/security/README.md`](h | I24 | A command run or a file read or written in a workspace through the API is the owner's, on a key made with their passkey that holds `workspace:exec`, and is recorded with who asked and the key it came on before it reaches the workspace. | `TestEveryProcedureHasAScope`, `TestOnlyTheListedProceduresAdmitAGuest`, `TestAStrangerGetsNothingOfSomebodyElses`; the e2e lane's `TestAKeyWithExecRunsACommandInAWorkspaceAndOneWithoutIsRefused` | | I25 | A key delegated from another holds no more than it - scopes, team, projects, end, the workspaces and cores its tree holds, and how far down the chain - reaches no workspace outside its team and projects, and goes when the key it came from does. | `TestADelegatedKeyHoldsNoMoreThanItsParent`, `TestAKeyDelegatesNoMoreThanItHolds`, `TestAKeyReachesOnlyItsTeamsAndProjectsWorkspaces`; the CLI lane's `TestE2E_AnAgentDrivesAWorkspaceThroughMCPOnADelegatedKey` | | I26 | A workload's identity buys a key only as the SPIFFE spec has it - a JWT-SVID go-spiffe validates against the trust domain's bundle, for the control plane's own audience - and only a child of the live key that made its workspace, within it (I25), for an hour at most, gone when the workspace goes to the trash. | `TestATokenForTheControlPlaneIsAJWTSVIDGoSpiffeValidates`, `TestAWorkloadExchangesItsIdentityForAChildOfTheKeyThatMadeItsWorkspace`; the e2e lane's `TestAnAgentInAWorkspaceDrivesAnotherOnTheIdentityOfItsWorkspace` | +| I27 | An installation's administrator acts on a workspace that is not theirs only through the procedures that say so, each from a session and audited with whose workspace it was: held in quarantine and released, its bandwidth bounded, its disk re-keyed, stopped, moved off a host that is gone - with a reason, which its owner reads on the workspace's history - and where it sends, read. Being an administrator opens no session or command in it, no snapshot of its disk, no start, and no say over who is let in: those are its owner's procedures, which answer an administrator as they answer a stranger. Nor does any of these acts read its env: each answers which workspace it acted on and what the act left, never the workspace itself. | `TestOnlyTheListedProceduresActOnAnybodysWorkspace`, `TestNoAdministratorsActOnAnybodysWorkspaceAnswersItsEnv`, `TestAnAdministratorsActsOnSomebodyElsesWorkspaceAnswerNoneOfItsEnv`, `TestAnAdministratorIsStillRefusedWhatIsTheOwnersAlone`, `TestAnAdministratorActsOnAWorkspaceThatIsNotTheirsAndItIsAudited`, `TestAnAdministratorsRecoverIsRefusedWithoutAReasonOrOfAHeldWorkspace`, `TestAStrangerGetsNothingOfSomebodyElses` | diff --git a/src/content/docs/reference/status/index.md b/src/content/docs/reference/status/index.md index 28e6bbe..50c37b8 100644 --- a/src/content/docs/reference/status/index.md +++ b/src/content/docs/reference/status/index.md @@ -9,7 +9,7 @@ sidebar: -Every row of this page is a line of `internal/features` in spin, and a test there resolves it against the tree. A row that says **built** names what shows the feature exists - a procedure the control plane serves, a command the CLI has, or a test - and, under **End to end**, the tests that drive a real runner and its guest and check what the feature did, or what no test checks yet. spin's build checks that each of those tests is there and not quarantined; whether it checks the whole feature is its review's. A row that says **not built** is an item of [`docs/ROADMAP.md`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/ROADMAP.md). The page is written by `task reference` from `spin features` at spin `b7ba5206f`, and cannot say a feature the binary does not. spin is a private repository: a link to its code opens only for someone with access to it. +Every row of this page is a line of `internal/features` in spin, and a test there resolves it against the tree. A row that says **built** names what shows the feature exists - a procedure the control plane serves, a command the CLI has, or a test - and, under **End to end**, the tests that drive a real runner and its guest and check what the feature did, or what no test checks yet. spin's build checks that each of those tests is there and not quarantined; whether it checks the whole feature is its review's. A row that says **not built** is an item of [`docs/ROADMAP.md`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/ROADMAP.md). The page is written by `task reference` from `spin features` at spin `911f516bc`, and cannot say a feature the binary does not. spin is a private repository: a link to its code opens only for someone with access to it. ## Built @@ -17,132 +17,137 @@ Every row of this page is a line of `internal/features` in spin, and a test ther | Feature | Exists | End to end | |---|---|---| -| A machine per workspace: KVM, its own kernel, root, a persistent disk, SSH | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vm_test.go#L190), a test of `internal/e2e` | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vm_test.go#L190) | -| Create, start, stop and delete | [`WorkspaceService/CreateWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_WorkspaceCRUD`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L424) | -| A trash: a deleted workspace stays whole, for its owner to undelete, until its days are up | [`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L389), a test of `internal/e2e` | [`TestATrashedWorkspaceIsWholeWhenItsOwnerUndeletesIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L421)
[`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L389) | -| Idle suspend, with a question before it that a keep answers | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L108), a test of `internal/e2e` | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L108) | -| A workspace that ran as long as the installation allows is suspended | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L70), a test of `internal/e2e` | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L70) | -| A process inside holds its workspace awake while it lives | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L199), a test of `internal/e2e` | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L199) | -| Suspend with the processes where they are; a start resumes them | [`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L36), a test of `internal/e2e` | [`TestE2E_ASuspendedWorkspaceResumesWhereItWas`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L288)
[`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L36) | -| Cores and memory lent while the host has them, and taken back
*A loan refused while the host is full has no test against the stack: the budget is the host's own cores and half its memory, which no lane's workspaces fill.* | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L352), a test of `internal/e2e` | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L352)
[`TestABusyWorkspaceIsLentMemoryAndGivesItBack`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/memory_test.go#L109) | -| A grant of cores and memory of its own, for a while | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L298), a test of `internal/e2e` | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/background_test.go#L298)
[`TestAResumedWorkspaceIsGivenWhatItIsGrantedWhileItRuns`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/resume_grant_test.go#L20) | -| An administrator changes a running workspace's bandwidth, its connections open | [`TestAnAdministratorChangesARunningWorkspacesBandwidth`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/bandwidth_test.go#L118), a test of `internal/e2e` | [`TestAnAdministratorChangesARunningWorkspacesBandwidth`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/bandwidth_test.go#L118)
[`TestAWorkspacesBandwidthHoldsAcrossItsRunsAndItsRunner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/bandwidth_test.go#L172) | -| Move a running workspace to another host, its processes as they are | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/hosts_flows_test.go#L65), a test of `internal/e2e` | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/hosts_flows_test.go#L65) | -| Clone a workspace: its disk, team, project, size, policies and environment, in one | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/clone_test.go#L64), a test of `internal/e2e` | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/clone_test.go#L64) | -| Recover a workspace whose host is gone, at a cost its owner accepts | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/hosts_flows_test.go#L100), a test of `internal/e2e` | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/hosts_flows_test.go#L100) | -| A workspace outlives its runner | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vm_test.go#L341), a test of `internal/e2e` | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vm_test.go#L341) | -| Quarantine, by an administrator | [`WorkspaceService/QuarantineWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L337) | -| Why a workspace is not behaving: status, console, history | [`spin workspace status`](/reference/cli/workspace/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L337)
[`TestE2E_AWorkspacesConsoleIsWhatItsMachinePrinted`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L370) | -| The owner's shell, environment, editor settings, Taskfile and dotfiles reach the guest as they save them | [`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/env_test.go#L22), a test of `internal/e2e` | [`TestTheOwnersSettingsArriveInTheGuest`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/env_test.go#L74)
[`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/env_test.go#L22) | -| Reset a disk to what it was created from | [`spin workspace reset`](/reference/cli/workspace/) | [`TestE2E_ResetPutsAWorkspaceBackToWhatItWasCreatedFrom`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1230) | -| Claude Code, Codex and T3 Code on every workspace's PATH, at the versions the release pins, with their own defaults
*Where an administrator accepted their terms (Admin → Extensions) or an operator publishes them: no image carries Claude Code, which only Anthropic may distribute.* | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/agentclis_test.go#L24), a test of `internal/e2e` | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/agentclis_test.go#L24) | -| An agent UI: T3 Code in a workspace as its agent user, over the owner's SSH session - from `spin agent` on the person's computer, or in the workspace's Agent tab in the dashboard, which frames the client and carries it over a passkey's session
*The lane has no provider credential, so no test has a model answer through the brokered egress: the server is reached, as the agent's user, and nothing is asked of it. The browser's test drives the page with a virtual authenticator, which no real passkey's maker vouches for.* | [`spin agent --port`](/reference/cli/agent/) | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/t3_test.go#L79)
[`TestE2E_TheAgentIsServedOnThisComputerWhileTheSessionIsOpen`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L94)
[`agent.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/agent.spec.ts#L62): a passkey signs the person in, and the agent runs in their workspace from the dashboard | -| Claude Code and Codex signed in from the agent's page: each CLI's own login, run by the supervisor as the agent's user, its credentials kept in the agent's home and never passing through the page
*The lane has no provider account: it begins a login and sees the provider's page, and finishing one is the unit tests' on each CLI's own output.* | `AgentService/BeginLogin` | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/t3_test.go#L79) | -| A person signs Claude Code and Codex in once: every workspace of theirs, plain or a project's, is handed the same credentials and hands back each refresh and logout, and a workspace that names a guest holds none
*The lane has no provider account: its login is the file the CLI writes, written as the agent's user. Only a workspace its owner's alone through all it came from holds them: a project opened from its prebuild does, but not a prebuild's build, a copy of another person's snapshot, or one made before the release that keeps the mark.* | [`TestAPersonSignsInToTheAgentOnceForEveryWorkspaceOfTheirs`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/agentcredentials_test.go#L29), a test of `internal/e2e` | [`TestAPersonSignsInToTheAgentOnceForEveryWorkspaceOfTheirs`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/agentcredentials_test.go#L29) | -| The supervisor runs the agent for its owner while it is used: started, paired and listed over the owner's own session on a socket only root opens, and stopped once nobody has used it for half an hour and no chat is at work
*That it stops once idle is the sweep's unit test on a clock the test moves: no lane waits half an hour.* | `AgentService/Start` | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/t3_test.go#L79) | -| The agent's chats listed on the workspace - titles, paths and times, never a message - reported by its supervisor | [`WorkspaceService/ListAgentThreads`](/reference/api/workspaceservice/) | [`TestTheAgentsThreadsAreListed`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/t3_test.go#L184) | -| The agent's home is a volume of its own: the chats and sessions outlive the workspace, kept in the installation's store as every disk is, and a new workspace mounts them at its creation
*Sealed with the workspace's checkpoints, claimed with the launch beside the disk, and moved with the workspace.* | [`spin workspace create --agent-home`](/reference/cli/workspace/) | [`TestAnAgentsHomeOutlivesItsWorkspace`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/agenthome_test.go#L78) | +| A machine per workspace: KVM, its own kernel, root, a persistent disk, SSH | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vm_test.go#L190), a test of `internal/e2e` | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vm_test.go#L190) | +| Create, start, stop and delete | [`WorkspaceService/CreateWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_WorkspaceCRUD`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L426) | +| A trash: a deleted workspace stays whole, for its owner to undelete, until its days are up | [`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L507), a test of `internal/e2e` | [`TestATrashedWorkspaceIsWholeWhenItsOwnerUndeletesIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L539)
[`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L507) | +| Idle suspend, with a question before it that a keep answers | [`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36), a test of `internal/e2e` | [`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36) | +| A workspace that ran as long as the installation allows is suspended | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L172), a test of `internal/e2e` | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L172) | +| A process inside holds its workspace awake while it lives | [`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36), a test of `internal/e2e` | [`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36) | +| Suspend with the processes where they are; a start resumes them | [`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36), a test of `internal/e2e` | [`TestE2E_ASuspendedWorkspaceResumesWhereItWas`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L289)
[`TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L36) | +| Cores and memory lent while the host has them, and taken back
*A loan refused while the host is full has no test against the stack: the budget is the host's own cores and half its memory, which no lane's workspaces fill.* | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L470), a test of `internal/e2e` | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L470)
[`TestABusyWorkspaceIsLentMemoryAndGivesItBack`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/memory_test.go#L110)
[`TestAnEditorAtTheFloorIsNotKilledForMemory`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/memory_test.go#L162) | +| A grant of cores and memory of its own, for a while | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L416), a test of `internal/e2e` | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/background_test.go#L416)
[`TestAResumedWorkspaceIsGivenWhatItIsGrantedWhileItRuns`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/resume_grant_test.go#L20) | +| An administrator changes a running workspace's bandwidth, its connections open | [`TestAnAdministratorChangesARunningWorkspacesBandwidth`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/bandwidth_test.go#L118), a test of `internal/e2e` | [`TestAnAdministratorChangesARunningWorkspacesBandwidth`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/bandwidth_test.go#L118)
[`TestAWorkspacesBandwidthHoldsAcrossItsRunsAndItsRunner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/bandwidth_test.go#L172) | +| Move a running workspace to another host, its processes as they are | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/hosts_flows_test.go#L65), a test of `internal/e2e` | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/hosts_flows_test.go#L65) | +| Clone a workspace: its disk, team, project, size, policies and environment, in one | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/clone_test.go#L64), a test of `internal/e2e` | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/clone_test.go#L64) | +| Recover a workspace whose host is gone, at a cost its owner accepts | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/hosts_flows_test.go#L100), a test of `internal/e2e` | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/hosts_flows_test.go#L100) | +| A workspace outlives its runner | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vm_test.go#L343), a test of `internal/e2e` | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vm_test.go#L343) | +| Quarantine, by an administrator | [`WorkspaceService/QuarantineWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L338) | +| Why a workspace is not behaving: status, console, history | [`spin workspace status`](/reference/cli/workspace/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L338)
[`TestE2E_AWorkspacesConsoleIsWhatItsMachinePrinted`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L371) | +| The owner's shell, environment, editor settings, Taskfile and dotfiles reach the guest as they save them | [`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/env_test.go#L22), a test of `internal/e2e` | [`TestTheOwnersSettingsArriveInTheGuest`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/env_test.go#L74)
[`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/env_test.go#L22) | +| Reset a disk to what it was created from | [`spin workspace reset`](/reference/cli/workspace/) | [`TestE2E_ResetPutsAWorkspaceBackToWhatItWasCreatedFrom`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1352) | +| Claude Code, Codex and T3 Code on every workspace's PATH, at the versions the release pins, with their own defaults
*Where an administrator accepted their terms (Admin → Extensions) or an operator publishes them: no image carries Claude Code, which only Anthropic may distribute.* | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/agentclis_test.go#L24), a test of `internal/e2e` | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/agentclis_test.go#L24) | +| An agent UI: T3 Code in a workspace as its agent user, over the owner's SSH session - from `spin agent` on the person's computer, or in the workspace's Agent tab in the dashboard, which frames the client and carries it over a passkey's session
*The lane has no provider credential, so no test has a model answer through the brokered egress: the server is reached, as the agent's user, and nothing is asked of it. The browser's test drives the page with a virtual authenticator, which no real passkey's maker vouches for.* | [`spin agent --port`](/reference/cli/agent/) | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/t3_test.go#L79)
[`TestE2E_TheAgentIsServedOnThisComputerWhileTheSessionIsOpen`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L95)
[`agent.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/agent.spec.ts#L62): a passkey signs the person in, and the agent runs in their workspace from the dashboard | +| Claude Code and Codex signed in from the agent's page: each CLI's own login, run by the supervisor as the agent's user, its credentials kept in the agent's home and never passing through the page
*The lane has no provider account: it begins a login and sees the provider's page, and finishing one is the unit tests' on each CLI's own output.* | `AgentService/BeginLogin` | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/t3_test.go#L79) | +| A person signs Claude Code and Codex in once: every workspace of theirs, plain or a project's, is handed the same credentials and hands back each refresh and logout, and a workspace that names a guest holds none
*The lane has no provider account: its login is the file the CLI writes, written as the agent's user. Only a workspace its owner's alone through all it came from holds them: a project opened from its prebuild does, but not a prebuild's build, a copy of another person's snapshot, or one made before the release that keeps the mark.* | [`TestAPersonSignsInToTheAgentOnceForEveryWorkspaceOfTheirs`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/agentcredentials_test.go#L29), a test of `internal/e2e` | [`TestAPersonSignsInToTheAgentOnceForEveryWorkspaceOfTheirs`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/agentcredentials_test.go#L29) | +| The supervisor runs the agent for its owner while it is used: started, paired and listed over the owner's own session on a socket only root opens, and stopped once nobody has used it for half an hour and no chat is at work
*That it stops once idle is the sweep's unit test on a clock the test moves: no lane waits half an hour.* | `AgentService/Start` | [`TestTheSupervisorRunsTheAgentForItsOwner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/t3_test.go#L79) | +| The agent's chats listed on the workspace - titles, paths and times, never a message - reported by its supervisor | [`WorkspaceService/ListAgentThreads`](/reference/api/workspaceservice/) | [`TestTheAgentsThreadsAreListed`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/t3_test.go#L134) | +| Chat mode: the dashboard flips to the person's own chats across their workspaces, grouped by project, with the agent full height and a new chat that makes its workspace | [`WorkspaceService/ListOwnAgentThreads`](/reference/api/workspaceservice/) | not yet: The RPC is held to the caller's own chats by its unit test, the mode by the dashboard's; no lane opens chat mode in a browser against an agent that has reported. | +| The agent's home is a volume of its own: the chats and sessions outlive the workspace, kept in the installation's store as every disk is, and a new workspace mounts them at its creation
*Sealed with the workspace's checkpoints, claimed with the launch beside the disk, and moved with the workspace.* | [`spin workspace create --agent-home`](/reference/cli/workspace/) | [`TestAnAgentsHomeOutlivesItsWorkspace`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/agenthome_test.go#L78) | ### Teams and projects | Feature | Exists | End to end | |---|---|---| -| Open a repository, branch, commit or pull request from its URL, in the dashboard (/launch) and from the terminal
*From the terminal; the dashboard's /launch page makes the same create and has no test of its own against the stack.* | [`spin open --no-connect`](/reference/cli/open/) | [`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L222) | -| A workspace opened at a commit or a pull request, checked out as pr-<n> | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/project_test.go#L153), a test of `internal/e2e` | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/project_test.go#L153)
[`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L222) | -| Teams, with owner, admin and member | [`spin team member role`](/reference/cli/team/) | [`TestE2E_ATeamRoleIsWhatItsHolderMayDo`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L502) | -| A team's own identity providers | [`TeamService/CreateIdentityProvider`](/reference/api/teamservice/) | [`TestE2E_ATeamsIdentityProviderIsItsAdminsAndSignsNobodyIn`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L534) | +| Open a repository, branch, commit or pull request from its URL, in the dashboard (/launch) and from the terminal
*From the terminal; the dashboard's /launch page makes the same create and has no test of its own against the stack.* | [`spin open --no-connect`](/reference/cli/open/) | [`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L223) | +| A workspace opened at a commit or a pull request, checked out as pr-<n> | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/project_test.go#L156), a test of `internal/e2e` | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/project_test.go#L156)
[`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L223) | +| Teams, with owner, admin and member | [`spin team member role`](/reference/cli/team/) | [`TestE2E_ATeamRoleIsWhatItsHolderMayDo`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L504) | +| A team's own identity providers | [`TeamService/CreateIdentityProvider`](/reference/api/teamservice/) | [`TestE2E_ATeamsIdentityProviderIsItsAdminsAndSignsNobodyIn`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L536) | | Your GitHub account, connected once: private repositories cloned and pushed with nothing on the disk | [`ForgeService/CompleteConnect`](/reference/api/forgeservice/) | not yet: consent takes a person on GitHub's page, and revoking a grant would end the one authorization a lane could live on: checked by hand once per release | | Your GitHub account connected from the CLI, with a code entered on GitHub's page | [`spin git connect`](/reference/cli/git/) | not yet: the code is entered by a person on GitHub's page: checked by hand once per release | | A repository picked from the ones your account reaches | [`spin git repos`](/reference/cli/git/) | not yet: a real GitHub account the lane can search with | -| A team's own network policies, where the installation allows them | [`spin team policy set`](/reference/cli/team/) | [`TestATeamsOwnPolicyIsWhatItsWorkspacesReach`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/team_policy_test.go#L24) | -| Projects of the installation: a repository and the shape of the workspace that opens it, shared with everyone or some | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/project_test.go#L27), a test of `internal/e2e` | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/project_test.go#L27) | -| Prebuilds of a project's refs, as the floor of the workspaces that open them | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/prebuild_test.go#L24), a test of `internal/e2e` | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/prebuild_test.go#L24) | -| A team's workspaces run on the installation's hosts | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/team_test.go#L20), a test of `internal/e2e` | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/team_test.go#L20) | +| A team's own network policies, where the installation allows them | [`spin team policy set`](/reference/cli/team/) | [`TestATeamsOwnPolicyIsWhatItsWorkspacesReach`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/team_policy_test.go#L24) | +| Projects of the installation: a repository and the shape of the workspace that opens it, shared with everyone or some | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/project_test.go#L27), a test of `internal/e2e` | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/project_test.go#L27) | +| Prebuilds of a project's refs, as the floor of the workspaces that open them | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/prebuild_test.go#L24), a test of `internal/e2e` | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/prebuild_test.go#L24) | +| A team's workspaces run on the installation's hosts | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/team_test.go#L20), a test of `internal/e2e` | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/team_test.go#L20) | ### Snapshots | Feature | Exists | End to end | |---|---|---| -| Snapshots of a running workspace's disk | [`WorkspaceService/CreateSnapshot`](/reference/api/workspaceservice/) | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/clone_test.go#L64)
[`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/compaction_test.go#L19) | -| Snapshots with memory, resumed with their processes | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restore_test.go#L175), a test of `internal/e2e` | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restore_test.go#L175) | -| Restore a workspace to a snapshot | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restore_test.go#L29), a test of `internal/e2e` | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restore_test.go#L29)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/site_e2e_test.go#L24) | -| A diff of the files that changed between two snapshots | [`TestADiffOfTwoSnapshotsIsWhatChangedBetweenThem`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/diff_test.go#L28), a test of `internal/e2e` | [`TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/diff_test.go#L95)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/site_e2e_test.go#L24) | -| Share a snapshot with a person or make it public, and create a workspace from it | [`TestACloneOfASnapshotRegistersAsItself`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/clone_test.go#L23), a test of `internal/e2e` | [`TestASharedSnapshotIsCreatedFromByThePersonItIsSharedWith`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/share_test.go#L21) | -| What looks like a credential is found before a share, and the share refused unless acknowledged | [`ShareService/ShareSnapshot`](/reference/api/shareservice/) | [`TestE2E_ASharedSnapshotIsScannedForCredentials`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L171) | -| A long chain is compacted while the workspace runs | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/compaction_test.go#L19), a test of `internal/e2e` | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/compaction_test.go#L19) | +| Snapshots of a running workspace's disk | [`WorkspaceService/CreateSnapshot`](/reference/api/workspaceservice/) | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/clone_test.go#L64)
[`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/compaction_test.go#L19) | +| Snapshots with memory, resumed with their processes | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restore_test.go#L175), a test of `internal/e2e` | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restore_test.go#L175) | +| Restore a workspace to a snapshot | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restore_test.go#L29), a test of `internal/e2e` | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restore_test.go#L29)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/site_e2e_test.go#L24) | +| A diff of the files that changed between two snapshots | [`TestADiffOfTwoSnapshotsIsWhatChangedBetweenThem`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/diff_test.go#L28), a test of `internal/e2e` | [`TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/diff_test.go#L95)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/site_e2e_test.go#L24) | +| Share a snapshot with a person or make it public, and create a workspace from it | [`TestACloneOfASnapshotRegistersAsItself`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/clone_test.go#L23), a test of `internal/e2e` | [`TestASharedSnapshotIsCreatedFromByThePersonItIsSharedWith`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/share_test.go#L21) | +| What looks like a credential is found before a share, and the share refused unless acknowledged | [`ShareService/ShareSnapshot`](/reference/api/shareservice/) | [`TestE2E_ASharedSnapshotIsScannedForCredentials`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L172) | +| A long chain is compacted while the workspace runs | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/compaction_test.go#L19), a test of `internal/e2e` | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/compaction_test.go#L19) | ### Network | Feature | Exists | End to end | |---|---|---| -| Egress is closed by default; policies, by tag, name what a workspace may reach | [`TestAnOwnerMayOnlyChooseAPolicyTheInstallationDeclares`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L163), a test of `internal/e2e` | [`TestAWorkspaceNamedNoPolicyReachesNothing`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L71)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L24) | -| A workspace's DNS goes upstream only for what its policy names, so a policy that closes egress closes the tunnel through names | [`TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/resolver_test.go#L76), a test of `internal/e2e` | [`TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/resolver_test.go#L76) | -| A policy changed reaches the host that runs the workspace | [`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L24), a test of `internal/e2e` | [`TestAnEditedPolicyChangesWhatARunningGuestReaches`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L100)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/retag_test.go#L24) | -| Brokered names: the host's proxy stands in and adds what the workspace never holds - a team secret, a forge token, its identity, a cloud role
*The team secret's value is read at the origin; the forge token, identity and cloud role injections have no test against the stack.* | [`spin team secret set`](/reference/cli/team/) | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L99) | -| What a workspace reached, what it looked up and what it was refused | [`spin workspace egress`](/reference/cli/workspace/) | [`TestE2E_EgressIsAnsweredByTheHostTheWorkspaceRunsOn`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L848) | -| Each request to a brokered name as the proxy read it, served or refused and why; and whether each injection's secret is the team's to give | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L27), a test of `internal/e2e` | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L27) | -| The egress proxy is a process of its own, holding no key of the host's, started again by the runner when it ends | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/egress_process_test.go#L21), a test of `internal/e2e` | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/egress_process_test.go#L21) | -| A brokered rule names whose it is, the agent's or the person's: one policy says what each may reach and with what | [`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L71), a test of `internal/e2e` | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L99)
[`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L71) | -| A WebSocket to a brokered name: its handshake under the rule's headers and scope, then bytes each way
*The rule's header on the handshake is not read at the origin: the lane's WebSocket echo answers frames, not headers.* | [`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L167), a test of `internal/e2e` | [`TestAWebSocketToABrokeredNameCarriesTheGuestsBytesAndRefusesAPathOutOfScope`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L195)
[`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L167) | -| HTTP/2 to a brokered name, the proxy's ALPN the real host's: a rule's headers set stream by stream, gRPC's trailers both ways
*gRPC's trailers are egressproxy's h2 test against an origin of its own; the lane has no gRPC origin to reach.* | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L230), a test of `internal/e2e` | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L230)
[`TestEachStreamOfAnHTTP2ConnectionToABrokeredNameCarriesTheRulesHeader`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L250) | -| Watch where a workspace connects, into its logs | [`spin workspace watch`](/reference/cli/workspace/) | [`TestE2E_AWatchedWorkspacesConnectionsAreInItsLogs`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L388) | -| An agent the supervisor starts is marked, and reaches only brokered names | [`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/seal_test.go#L21), a test of `internal/e2e` | [`TestAnAgentReachesOnlyBrokeredNames`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/brokered_test.go#L137)
[`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/seal_test.go#L21) | -| Published ports at https://{port}-{workspace}.{domain}: private, whitelisted or public | [`spin port expose`](/reference/cli/port/) | [`TestAPublicPortIsReachedWithNoSessionAtAll`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/proxy/lane_test.go#L144)
[`workspace-sign-in.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/workspace-sign-in.spec.ts#L31): a visitor signed in to the dashboard reaches their private port | +| Egress is closed by default; policies, by tag, name what a workspace may reach | [`TestAnOwnerMayOnlyChooseAPolicyTheInstallationDeclares`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L163), a test of `internal/e2e` | [`TestAWorkspaceNamedNoPolicyReachesNothing`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L71)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L24) | +| A workspace's DNS goes upstream only for what its policy names, so a policy that closes egress closes the tunnel through names | [`TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/resolver_test.go#L76), a test of `internal/e2e` | [`TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/resolver_test.go#L76) | +| A policy changed reaches the host that runs the workspace | [`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L24), a test of `internal/e2e` | [`TestAnEditedPolicyChangesWhatARunningGuestReaches`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L100)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/retag_test.go#L24) | +| Brokered names: the host's proxy stands in and adds what the workspace never holds - a team secret, a forge token, its identity, a cloud role
*The team secret's value is read at the origin; the forge token, identity and cloud role injections have no test against the stack.* | [`spin team secret set`](/reference/cli/team/) | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L99) | +| What a workspace reached, what it looked up and what it was refused | [`spin workspace egress`](/reference/cli/workspace/) | [`TestE2E_EgressIsAnsweredByTheHostTheWorkspaceRunsOn`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L970) | +| Each request to a brokered name as the proxy read it, served or refused and why; and whether each injection's secret is the team's to give | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L27), a test of `internal/e2e` | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L27) | +| The egress proxy is a process of its own, holding no key of the host's, started again by the runner when it ends | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/egress_process_test.go#L21), a test of `internal/e2e` | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/egress_process_test.go#L21) | +| A brokered rule names whose it is, the agent's or the person's: one policy says what each may reach and with what | [`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L71), a test of `internal/e2e` | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L99)
[`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L71) | +| A WebSocket to a brokered name: its handshake under the rule's headers and scope, then bytes each way
*The rule's header on the handshake is not read at the origin: the lane's WebSocket echo answers frames, not headers.* | [`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L167), a test of `internal/e2e` | [`TestAWebSocketToABrokeredNameCarriesTheGuestsBytesAndRefusesAPathOutOfScope`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L195)
[`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L167) | +| HTTP/2 to a brokered name, the proxy's ALPN the real host's: a rule's headers set stream by stream, gRPC's trailers both ways
*gRPC's trailers are egressproxy's h2 test against an origin of its own; the lane has no gRPC origin to reach.* | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L230), a test of `internal/e2e` | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L230)
[`TestEachStreamOfAnHTTP2ConnectionToABrokeredNameCarriesTheRulesHeader`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L250) | +| Watch where a workspace connects, into its logs | [`spin workspace watch`](/reference/cli/workspace/) | [`TestE2E_AWatchedWorkspacesConnectionsAreInItsLogs`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L389) | +| An agent the supervisor starts is marked, and reaches only brokered names | [`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/seal_test.go#L21), a test of `internal/e2e` | [`TestAnAgentReachesOnlyBrokeredNames`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/brokered_test.go#L137)
[`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/seal_test.go#L21) | +| Published ports at https://{port}-{workspace}.{domain}: private, whitelisted or public | [`spin port expose`](/reference/cli/port/) | [`TestAPublicPortIsReachedWithNoSessionAtAll`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/proxy/lane_test.go#L144)
[`workspace-sign-in.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/workspace-sign-in.spec.ts#L31): a visitor signed in to the dashboard reaches their private port | ### Access | Feature | Exists | End to end | |---|---|---| | Sign in with GitHub or an OIDC provider | [`AuthService/StartLogin`](/reference/api/authservice/) | not yet: a GitHub provider: the sign-in through it and the verified email it gives | -| Device login for a terminal | [`AuthService/StartDeviceLogin`](/reference/api/authservice/) | [`TestE2E_Logout`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1217)
[`auth.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/auth.spec.ts#L18): the CLI's device page stands alone and keeps its code through a sign-in | -| API keys for scripts and agents | [`spin api-key create`](/reference/cli/api-key/) | [`TestE2E_APIKeyCRUD`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1172) | -| SSH keys, and plain ssh to spin.<workspace> | [`spin ssh-config`](/reference/cli/ssh-config/) | [`TestE2E_SSHConfigCarriesSSHToAWorkspace`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L719) | -| A key logs in only once its person adds it with a passkey: their own for good, a browser's for twelve hours | [`PasskeyService/BeginPasskeyCeremony`](/reference/api/passkeyservice/) | [`TestOnlyTheOwnerAndTheirGuestsLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/login_test.go#L174) | -| Guests of a workspace, let in by its owner with a passkey | [`spin workspace guests`](/reference/cli/workspace/) | [`TestOnlyTheOwnerAndTheirGuestsLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/login_test.go#L174) | -| A workspace's identity, signed by its host, for the audiences its owner allows | [`TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/identity_test.go#L58), a test of `internal/e2e` | [`TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/identity_test.go#L58) | -| VS Code over SSH, with the server the installation publishes and nothing downloaded
*The server is Microsoft's, for Remote-SSH; spin code also opens an editor with Open Remote - SSH, which downloads its own. spin code itself is run by its unit tests only: it opens a desktop editor, which no lane has.* | [`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vscodehotplug_test.go#L23), a test of `internal/e2e` | [`TestTheCarriedVSCodeServerRunsOfflineAndAnswersOverTheSSHTunnel`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vscodeserver_test.go#L86)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vscodehotplug_test.go#L23) | -| Run a command in a workspace, copy files to and from it | [`spin exec`](/reference/cli/exec/) | [`TestE2E_ADetachedRunIsFollowedToItsEndAndFilesGoBothWays`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L306) | -| A program runs a command and reads and writes files in a workspace through the API, with a key that holds workspace:exec, every call recorded with the key it came on | [`ExecService/Exec`](/reference/api/execservice/) | [`TestAKeyWithExecRunsACommandInAWorkspaceAndOneWithoutIsRefused`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/exec_test.go#L36) | -| An agent works in workspaces through `spin mcp` on a key delegated to it: never more than the key it came from - scopes, team, projects, end, how many workspaces and cores its tree holds - and revoked with it | [`spin mcp`](/reference/cli/mcp/) | [`TestE2E_AnAgentDrivesAWorkspaceThroughMCPOnADelegatedKey`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/mcp_e2e_test.go#L62) | -| An agent in a workspace drives others with spin, on a key it gets for the workspace's own SPIFFE identity - a child of the key that made the workspace, through its host, whatever the workspace's network
*No forge in the lane: a workspace reaches no private or host address, and no test repository has a credential in CI yet, so an agent opening a pull request is not run.* | [`AuthService/ExchangeWorkloadToken`](/reference/api/authservice/) | [`TestAnAgentInAWorkspaceDrivesAnotherOnTheIdentityOfItsWorkspace`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/inception_test.go#L97) | +| Device login for a terminal | [`AuthService/StartDeviceLogin`](/reference/api/authservice/) | [`TestE2E_Logout`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1339)
[`auth.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/auth.spec.ts#L18): the CLI's device page stands alone and keeps its code through a sign-in | +| API keys for scripts and agents | [`spin api-key create`](/reference/cli/api-key/) | [`TestE2E_APIKeyCRUD`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1294) | +| SSH keys, and plain ssh to spin.<workspace> | [`spin ssh-config`](/reference/cli/ssh-config/) | [`TestE2E_SSHConfigCarriesSSHToAWorkspace`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L721) | +| A key logs in only once its person adds it with a passkey: their own for good, a browser's for twelve hours | [`PasskeyService/BeginPasskeyCeremony`](/reference/api/passkeyservice/) | [`TestOnlyTheOwnerAndTheirGuestsLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/login_test.go#L175) | +| Guests of a workspace, let in by its owner with a passkey | [`spin workspace guests`](/reference/cli/workspace/) | [`TestOnlyTheOwnerAndTheirGuestsLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/login_test.go#L175) | +| A workspace's identity, signed by its host, for the audiences its installation allows | [`TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/identity_test.go#L58), a test of `internal/e2e` | [`TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/identity_test.go#L58) | +| VS Code over SSH, with the server the installation publishes and nothing downloaded
*The server is Microsoft's, for Remote-SSH; spin code also opens an editor with Open Remote - SSH, which downloads its own. spin code itself is run by its unit tests only: it opens a desktop editor, which no lane has.* | [`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vscodehotplug_test.go#L23), a test of `internal/e2e` | [`TestTheCarriedVSCodeServerRunsOfflineAndAnswersOverTheSSHTunnel`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vscodeserver_test.go#L86)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vscodehotplug_test.go#L23) | +| Run a command in a workspace, copy files to and from it | [`spin exec`](/reference/cli/exec/) | [`TestE2E_ADetachedRunIsFollowedToItsEndAndFilesGoBothWays`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L307) | +| A program runs a command and reads and writes files in a workspace through the API, with a key that holds workspace:exec, every call recorded with the key it came on | [`ExecService/Exec`](/reference/api/execservice/) | [`TestAKeyWithExecRunsACommandInAWorkspaceAndOneWithoutIsRefused`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/exec_test.go#L36) | +| An agent works in workspaces through `spin mcp` on a key delegated to it: never more than the key it came from - scopes, team, projects, end, how many workspaces and cores its tree holds - and revoked with it | [`spin mcp`](/reference/cli/mcp/) | [`TestE2E_AnAgentDrivesAWorkspaceThroughMCPOnADelegatedKey`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/mcp_e2e_test.go#L62) | +| An agent in a workspace drives others with spin, on a key it gets for the workspace's own SPIFFE identity - a child of the key that made the workspace, through its host, whatever the workspace's network
*No forge in the lane: a workspace reaches no private or host address, and no test repository has a credential in CI yet, so an agent opening a pull request is not run.* | [`AuthService/ExchangeWorkloadToken`](/reference/api/authservice/) | [`TestAnAgentInAWorkspaceDrivesAnotherOnTheIdentityOfItsWorkspace`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/inception_test.go#L97) | ### Administration | Feature | Exists | End to end | |---|---|---| -| Installation settings seeded from a file; a change in the file is a proposal to apply or dismiss | [`AdminService/DecideConfigChange`](/reference/api/adminservice/) | [`TestTheInstallationFileSeedsAndItsChangesAreAnAdministratorsToDecide`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/config_file_test.go#L92) | -| Resource presets | [`AdminService/CreateResourcePreset`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L444): an admin's preset sizes a person's workspace, and is gone once deleted | -| Registration: open, by allowlist, or closed | [`AdminService/SetRegistrationMode`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L240): closed, open and a domain on the allowlist decide who registers
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L173): the allowlist lets in who it names, and nobody else | -| An audit log | [`AdminService/ListAuditLog`](/reference/api/adminservice/) | [`TestE2E_AnAdministratorsActionIsAuditedWithItsActorAndTarget`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L609)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L972): admin reads the log and can narrow it to one kind of event | -| The control plane's jobs, and running one now | [`spin admin jobs`](/reference/cli/admin/) | [`TestE2E_Admin`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1075)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L740): admin runs a job by hand and sees how it ended | +| Installation settings seeded from a file; a change in the file is a proposal to apply or dismiss | [`AdminService/DecideConfigChange`](/reference/api/adminservice/) | [`TestTheInstallationFileSeedsAndItsChangesAreAnAdministratorsToDecide`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/config_file_test.go#L92) | +| Every installation setting read and changed from the terminal, by the definitions the dashboard and the file use | [`spin admin settings set`](/reference/cli/admin/) | not yet: the RPC's round trip of every definition is tested against a real database; no lane runs the command against a stack yet | +| Resource presets | [`AdminService/CreateResourcePreset`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L438): an admin's preset sizes a person's workspace, and is gone once deleted | +| Registration: open, by allowlist, or closed | [`AdminService/SetSettings`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L244): closed, open and a domain on the allowlist decide who registers
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L177): the allowlist lets in who it names, and nobody else | +| An audit log | [`AdminService/ListAuditLog`](/reference/api/adminservice/) | [`TestE2E_AnAdministratorsActionIsAuditedWithItsActorAndTarget`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L611)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L959): admin reads the log and can narrow it to one kind of event | +| A timeline: what happened to a workspace, a runner, a team, a project or the installation, by whom, in one read | [`AdminService/ListTimeline`](/reference/api/adminservice/) | not yet: no lane reads it yet: its reading is held against a real database by internal/controlplane/domain/timeline's tests, and a host's loss and return by hostserver's over a real stream | +| The control plane's jobs, and running one now | [`spin admin jobs`](/reference/cli/admin/) | [`TestE2E_Admin`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1197)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L728): admin runs a job by hand and sees how it ended | | The month's bill, its forecast, and what each workspace cost | [`spin admin costs`](/reference/cli/admin/) | not yet: the cloud's real bill read: no lane has an AWS account, so the bill a test gives the reader stands in for it | -| The store: every volume, whose it is, and taking out what nothing needs | [`spin admin storage list`](/reference/cli/admin/) | [`TestAVolumeNothingNeedsIsTakenOutOfTheStore`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/orphan_test.go#L26) | -| Re-key a workspace's disk | [`spin admin volume rekey`](/reference/cli/admin/) | [`TestARekeyedDiskBootsWithWhatItHeldUnderANewKey`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/rekey_test.go#L24) | -| Runners: registration tokens, drain, activate, revoke, and a policy per host | [`spin admin runner drain`](/reference/cli/admin/) | [`TestARunnerRegisteredWithATokenTheAPIMadeIsRevokedByTheAdmin`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/runner_token_test.go#L99)
[`TestARegistrationTokenExpiredOrRevokedRegistersNothing`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/runner_token_test.go#L148) | +| The store: every volume, whose it is, and taking out what nothing needs | [`spin admin storage list`](/reference/cli/admin/) | [`TestAVolumeNothingNeedsIsTakenOutOfTheStore`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/orphan_test.go#L26) | +| Re-key a workspace's disk | [`spin admin volume rekey`](/reference/cli/admin/) | [`TestARekeyedDiskBootsWithWhatItHeldUnderANewKey`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/rekey_test.go#L24) | +| An administrator stops a workspace that is not theirs, moves it off a lost host with a reason its owner reads on its history, reads where it sends, and lists the disks hosts refuse: each audited | [`spin admin workspace recover`](/reference/cli/admin/) | not yet: no lane asks these as an administrator of a real runner: each runs what its owner's stop, recover and egress run, which the lanes cover, and who may ask, the audit entry and what the owner's history says are tested at the RPC against the database (workspaceserver's adminacts tests) | +| Runners: registration tokens, drain, activate, revoke, and a policy per host | [`spin admin runner drain`](/reference/cli/admin/) | [`TestARunnerRegisteredWithATokenTheAPIMadeIsRevokedByTheAdmin`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/runner_token_test.go#L99)
[`TestARegistrationTokenExpiredOrRevokedRegistersNothing`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/runner_token_test.go#L148) | | Runners start when a workspace waits for one and stop when nothing runs | [`HostService/GetFleet`](/reference/api/hostservice/) | not yet: an autoscaling group to start and stop: the one group is AWS's, and no lane has an account | -| A root shell on a runner, for a while and with a reason: the runner emptied first, every session recorded under Object Lock, the runner retired after | [`spin admin shell open`](/reference/cli/admin/) | [`TestHostsARootShellOnAHostIsRecordedAndItsHostRetired`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/hosts_test.go#L420) | -| A host that reboots brings its workspaces back | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restart_test.go#L38), a test of `internal/e2e` | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/restart_test.go#L38) | -| A cloud's reclaim warning empties the host before the machine goes | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/preemption_test.go#L26), a test of `internal/e2e` | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/preemption_test.go#L26) | -| A writeback bound per host, measured on that host before it is taken | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/writeback_test.go#L28), a test of `internal/e2e` | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/writeback_test.go#L28) | -| What is running where: the control plane, each runner and its machine | [`spin admin versions`](/reference/cli/admin/) | [`TestE2E_Admin`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1075) | +| A root shell on a runner, for a while and with a reason: the runner emptied first, every session recorded under Object Lock, the runner retired after | [`spin admin shell open`](/reference/cli/admin/) | [`TestHostsARootShellOnAHostIsRecordedAndItsHostRetired`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/hosts_test.go#L420) | +| A host that reboots brings its workspaces back | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restart_test.go#L38), a test of `internal/e2e` | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/restart_test.go#L38) | +| A cloud's reclaim warning empties the host before the machine goes | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/preemption_test.go#L26), a test of `internal/e2e` | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/preemption_test.go#L26) | +| A writeback bound per host, measured on that host before it is taken | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/writeback_test.go#L28), a test of `internal/e2e` | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/writeback_test.go#L28) | +| What is running where: the control plane, each runner and its machine | [`spin admin versions`](/reference/cli/admin/) | [`TestE2E_Admin`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1197) | | A workspace whose host never comes back is moved when little would be lost, and its owner is told when it waits for them | [`WorkspaceService/RecoverWorkspace`](/reference/api/workspaceservice/) | not yet: the move without asking and the wait it records are tested against the database (workspaceserver's autorehome tests); the hosts lane recovers by hand, and no lane has an SMTP server to receive the owner's email | ### Observability | Feature | Exists | End to end | |---|---|---| +| A workspace's cost this month and its rate, as an estimate with its day, for its owner; a team's workspaces summed for its admins, and a project's with its prebuilds' storage for its admins
*A share of days already billed, never of this hour; nothing is said where the cloud sends no bill.* | [`WorkspaceService/GetWorkspaceCost`](/reference/api/workspaceservice/) | not yet: the cloud's real bill read: no lane has an AWS account, so the bill a test gives the reader stands in for it | | A workspace's owner is told by email when it waits for them or something happened to it, each event switchable
*Held after its host was lost, a stop that could not be taken, stopped for not responding, moved off a lost host. A disk almost full is not an event yet.* | [`SettingsService/UpdateNotificationPreferences`](/reference/api/settingsservice/) | not yet: no lane has an SMTP server: delivery is tested against a generated mail server, with every attempt and retry recorded (internal/controlplane/notify) | -| Platform logs, workspace logs and traces, searched in the product | [`LogService/SearchPlatformLogs`](/reference/api/logservice/) | [`TestAWorkspacesLinesAndTracesAreFoundBySearchingForIt`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/observe_test.go#L179)
[`TestE2E_TheInstallationsLogsAreSearchable`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1012) | -| Metrics, with Grafana on the control plane | [`AdminService/GetMonitoring`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L1312): a series of the lane's host is in the metric store, read through Grafana
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/dashboard/e2e/admin.spec.ts#L1281): admin reads the installation's dashboards without leaving the page | -| A workspace's journal and console are collected as the installation's policy says | [`spin workspace logs`](/reference/cli/workspace/) | [`TestE2E_AWorkspacesConsoleIsKeptAndSearchable`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/e2e_test.go#L1033) | -| A workspace's last hour of CPU, memory and network against what it holds, for its owner
*Kept on the host, so it starts again with a restart of it or a move; the disk's use is not sampled.* | [`WorkspaceService/GetWorkspaceUsage`](/reference/api/workspaceservice/) | [`TestAWorkspacesUsageIsWhatItsHostSampled`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/host_report_test.go#L98) | +| Platform logs, workspace logs and traces, searched in the product | [`LogService/SearchPlatformLogs`](/reference/api/logservice/) | [`TestAWorkspacesLinesAndTracesAreFoundBySearchingForIt`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/observe_test.go#L190)
[`TestE2E_TheInstallationsLogsAreSearchable`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1134) | +| Metrics, with Grafana on the control plane | [`AdminService/GetMonitoring`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L1309): a series of the lane's host is in the metric store, read through Grafana
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/dashboard/e2e/admin.spec.ts#L1278): admin reads the installation's dashboards without leaving the page | +| A workspace's journal and console are collected as the installation's policy says | [`spin workspace logs`](/reference/cli/workspace/) | [`TestE2E_AWorkspacesConsoleIsKeptAndSearchable`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/e2e_test.go#L1155) | +| A workspace's last hour of CPU, memory and network against what it holds, for its owner
*Kept on the host, so it starts again with a restart of it or a move; the disk's use is not sampled.* | [`WorkspaceService/GetWorkspaceUsage`](/reference/api/workspaceservice/) | [`TestAWorkspacesUsageIsWhatItsHostSampled`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/host_report_test.go#L99) | ### Platform | Feature | Exists | End to end | |---|---|---| -| A smoke test of what an installation's API promises, with a workspace of its own | [`spin smoke`](/reference/cli/smoke/) | [`TestE2E_SmokePassesAndLeavesNothingBehind`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/cmd/cli/journeys_e2e_test.go#L437) | -| A host serves only the releases it carries
*A resume whose recorded release was collected since has no test against the stack.* | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/suspend_test.go#L126), a test of `internal/e2e` | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/suspend_test.go#L126)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/internal/e2e/vscodehotplug_test.go#L23) | +| A smoke test of what an installation's API promises, with a workspace of its own | [`spin smoke`](/reference/cli/smoke/) | [`TestE2E_SmokePassesAndLeavesNothingBehind`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/cmd/cli/journeys_e2e_test.go#L438) | +| A host serves only the releases it carries
*A resume whose recorded release was collected since has no test against the stack.* | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/suspend_test.go#L126), a test of `internal/e2e` | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/suspend_test.go#L126)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/internal/e2e/vscodehotplug_test.go#L23) | ## Partly there @@ -161,9 +166,9 @@ Not planned: a limit of the platform or of where it runs today. [Limitations](/o - **AWS only** (Platform): The Terraform module is the one supported install; nothing else is packaged or tested. - **x86-64 only** (Platform): Runners are x86-64 machines with nested virtualization. -- **The CLI runs on Linux x86-64 and macOS Apple silicon** (Platform) +- **The CLI runs on Linux and macOS** (Platform): x86-64 and Arm of each; there is no Windows build. - **One region, one control plane** (Platform): A machine that fails is replaced in minutes, not seconds. -- **No GPUs** (Workspaces): There is no way to ask for a runner of a particular kind. +- **No GPUs** (Workspaces): A preset names the class of runner its workspaces run on, and no class hands a workspace a GPU. - **Linux guests only** (Workspaces): Booted with the kernel spin provides. - **No IPv6 inside a workspace** (Network) - **TCP only for published ports** (Network) diff --git a/src/content/docs/use/access.md b/src/content/docs/use/access.md index 9f3faee..c712d1f 100644 --- a/src/content/docs/use/access.md +++ b/src/content/docs/use/access.md @@ -16,7 +16,7 @@ the audit log. Give it `keys:delegate` and it makes keys of its own for the agen (`spin mcp` serves them the API): each holds no more than it does - its scopes, team, projects, end, and how many workspaces and cores all of them hold - and goes when you revoke it. Why, and what it trades, is -[ADR-0031](https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/decisions/ADR-0031-a-passkey-buys-a-key-the-control-plane-certifies.md). +[ADR-0031](https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/decisions/ADR-0031-a-passkey-buys-a-key-the-control-plane-certifies.md). The control plane decides who logs in, at every session: it certifies a key of yours, for five minutes, into a workspace you own or are a guest of. It also holds what it needs to serve you: your diff --git a/src/content/docs/use/dashboard.md b/src/content/docs/use/dashboard.md index 7d4943e..b887ea1 100644 --- a/src/content/docs/use/dashboard.md +++ b/src/content/docs/use/dashboard.md @@ -7,7 +7,7 @@ editUrl: false -The dashboard is at `https://app.`. These screenshots are of spin v20261110.01, taken by spin's dashboard suite on its end-to-end stack - real workspaces on a real runner - each by a test that first checks the screen shows what its title says. Everything here is also in the [CLI](/reference/cli/) and the [API](/reference/api/). +The dashboard is at `https://app.`. These screenshots are of spin v20261113.01, taken by spin's dashboard suite on its end-to-end stack - real workspaces on a real runner - each by a test that first checks the screen shows what its title says. Everything here is also in the [CLI](/reference/cli/) and the [API](/reference/api/). ## A person's workspaces diff --git a/src/data/features.json b/src/data/features.json index 32fab04..1c17a72 100644 --- a/src/data/features.json +++ b/src/data/features.json @@ -34,10 +34,10 @@ "area": "Workspaces", "name": "Idle suspend, with a question before it that a keep answers", "state": "built", - "proof": "e2e:TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt", + "proof": "e2e:TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld", "note": "", "endToEnd": [ - "go:internal/e2e.TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt" + "go:internal/e2e.TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld" ] }, { @@ -54,21 +54,21 @@ "area": "Workspaces", "name": "A process inside holds its workspace awake while it lives", "state": "built", - "proof": "e2e:TestAProcessHoldsItsWorkspaceAwakeWhileItLives", + "proof": "e2e:TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld", "note": "", "endToEnd": [ - "go:internal/e2e.TestAProcessHoldsItsWorkspaceAwakeWhileItLives" + "go:internal/e2e.TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld" ] }, { "area": "Workspaces", "name": "Suspend with the processes where they are; a start resumes them", "state": "built", - "proof": "e2e:TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt", + "proof": "e2e:TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld", "note": "", "endToEnd": [ "go:cmd/cli.TestE2E_ASuspendedWorkspaceResumesWhereItWas", - "go:internal/e2e.TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt" + "go:internal/e2e.TestAWorkspaceNobodyUsesIsSuspendedUnlessKeptOrHeld" ] }, { @@ -79,7 +79,8 @@ "note": "A loan refused while the host is full has no test against the stack: the budget is the host's own cores and half its memory, which no lane's workspaces fill.", "endToEnd": [ "go:internal/e2e.TestABusyWorkspaceIsLentCoresAndGivesThemBack", - "go:internal/e2e.TestABusyWorkspaceIsLentMemoryAndGivesItBack" + "go:internal/e2e.TestABusyWorkspaceIsLentMemoryAndGivesItBack", + "go:internal/e2e.TestAnEditorAtTheFloorIsNotKilledForMemory" ] }, { @@ -492,7 +493,7 @@ }, { "area": "Access", - "name": "A workspace's identity, signed by its host, for the audiences its owner allows", + "name": "A workspace's identity, signed by its host, for the audiences its installation allows", "state": "built", "proof": "e2e:TestAWorkspacesIdentityIsTheHostsSignatureOfItsSet", "note": "", @@ -573,6 +574,14 @@ "go:internal/e2e.TestTheAgentsThreadsAreListed" ] }, + { + "area": "Workspaces", + "name": "Chat mode: the dashboard flips to the person's own chats across their workspaces, grouped by project, with the agent full height and a new chat that makes its workspace", + "state": "built", + "proof": "rpc:spin.v1.WorkspaceService/ListOwnAgentThreads", + "note": "", + "endToEndGap": "The RPC is held to the caller's own chats by its unit test, the mode by the dashboard's; no lane opens chat mode in a browser against an agent that has reported." + }, { "area": "Workspaces", "name": "The agent's home is a volume of its own: the chats and sessions outlive the workspace, kept in the installation's store as every disk is, and a new workspace mounts them at its creation", @@ -717,6 +726,14 @@ "go:internal/e2e.TestTheInstallationFileSeedsAndItsChangesAreAnAdministratorsToDecide" ] }, + { + "area": "Administration", + "name": "Every installation setting read and changed from the terminal, by the definitions the dashboard and the file use", + "state": "built", + "proof": "cli:admin settings set", + "note": "", + "endToEndGap": "the RPC's round trip of every definition is tested against a real database; no lane runs the command against a stack yet" + }, { "area": "Administration", "name": "Resource presets", @@ -731,7 +748,7 @@ "area": "Administration", "name": "Registration: open, by allowlist, or closed", "state": "built", - "proof": "rpc:spin.v1.AdminService/SetRegistrationMode", + "proof": "rpc:spin.v1.AdminService/SetSettings", "note": "", "endToEnd": [ "pw:admin.spec.ts:closed, open and a domain on the allowlist decide who registers", @@ -749,6 +766,14 @@ "pw:admin.spec.ts:admin reads the log and can narrow it to one kind of event" ] }, + { + "area": "Administration", + "name": "A timeline: what happened to a workspace, a runner, a team, a project or the installation, by whom, in one read", + "state": "built", + "proof": "rpc:spin.v1.AdminService/ListTimeline", + "note": "", + "endToEndGap": "no lane reads it yet: its reading is held against a real database by internal/controlplane/domain/timeline's tests, and a host's loss and return by hostserver's over a real stream" + }, { "area": "Administration", "name": "The control plane's jobs, and running one now", @@ -768,6 +793,14 @@ "note": "", "endToEndGap": "the cloud's real bill read: no lane has an AWS account, so the bill a test gives the reader stands in for it" }, + { + "area": "Observability", + "name": "A workspace's cost this month and its rate, as an estimate with its day, for its owner; a team's workspaces summed for its admins, and a project's with its prebuilds' storage for its admins", + "state": "built", + "proof": "rpc:spin.v1.WorkspaceService/GetWorkspaceCost", + "note": "A share of days already billed, never of this hour; nothing is said where the cloud sends no bill.", + "endToEndGap": "the cloud's real bill read: no lane has an AWS account, so the bill a test gives the reader stands in for it" + }, { "area": "Administration", "name": "The store: every volume, whose it is, and taking out what nothing needs", @@ -788,6 +821,14 @@ "go:internal/e2e.TestARekeyedDiskBootsWithWhatItHeldUnderANewKey" ] }, + { + "area": "Administration", + "name": "An administrator stops a workspace that is not theirs, moves it off a lost host with a reason its owner reads on its history, reads where it sends, and lists the disks hosts refuse: each audited", + "state": "built", + "proof": "cli:admin workspace recover", + "note": "", + "endToEndGap": "no lane asks these as an administrator of a real runner: each runs what its owner's stop, recover and egress run, which the lanes cover, and who may ask, the audit entry and what the owner's history says are tested at the RPC against the database (workspaceserver's adminacts tests)" + }, { "area": "Administration", "name": "Runners: registration tokens, drain, activate, revoke, and a policy per host", @@ -952,10 +993,10 @@ }, { "area": "Platform", - "name": "The CLI runs on Linux x86-64 and macOS Apple silicon", + "name": "The CLI runs on Linux and macOS", "state": "limit", "proof": "", - "note": "" + "note": "x86-64 and Arm of each; there is no Windows build." }, { "area": "Platform", @@ -969,7 +1010,7 @@ "name": "No GPUs", "state": "limit", "proof": "", - "note": "There is no way to ask for a runner of a particular kind." + "note": "A preset names the class of runner its workspaces run on, and no class hands a workspace a GPU." }, { "area": "Workspaces", diff --git a/src/data/figures.json b/src/data/figures.json index 627c827..600d034 100644 --- a/src/data/figures.json +++ b/src/data/figures.json @@ -1,6 +1,6 @@ { "datapath": { - "source": "https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/network/datapath", + "source": "https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/network/datapath", "width": 552, "words": [ "How a packet leaves a workspace", @@ -56,7 +56,7 @@ ] }, "egress-policy": { - "source": "https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/network/egress-policy", + "source": "https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/network/egress-policy", "width": 923, "words": [ "What the forward chain decides, in order", @@ -173,7 +173,7 @@ ] }, "isolation": { - "source": "https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/network/isolation", + "source": "https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/network/isolation", "width": 499, "words": [ "Same address, different segment", @@ -220,7 +220,7 @@ ] }, "traffic": { - "source": "https://github.com/spin-stack/spin/blob/b7ba5206f12ba93a829a48e354be7fce6bc65db4/docs/security/traffic", + "source": "https://github.com/spin-stack/spin/blob/911f516bc89ae266c013681247d72c40990fe62c/docs/security/traffic", "width": 1889, "words": [ "Traffic: every arrow is a connection opened in its direction, and nothing else is let in", diff --git a/src/data/pricing.json b/src/data/pricing.json index 196e10f..856f3d7 100644 --- a/src/data/pricing.json +++ b/src/data/pricing.json @@ -1,6 +1,6 @@ { - "generated": "2026-11-13", - "published": "2026-11-10T17:38:52Z", + "generated": "2026-11-14", + "published": "2026-11-14T01:29:26Z", "source": "https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/ec2/USD/current/ec2-ondemand-without-sec-sel/\u003cregion\u003e/Linux/index.json", "machines": { "controlPlane": "t8i.medium", diff --git a/src/data/reference.json b/src/data/reference.json index 993e8f7..4a7053f 100644 --- a/src/data/reference.json +++ b/src/data/reference.json @@ -1,8 +1,8 @@ { "spin": { "installs": "v20261027.01", - "commit": "b7ba5206f12ba93a829a48e354be7fce6bc65db4", - "tag": "v20261110.01", + "commit": "911f516bc89ae266c013681247d72c40990fe62c", + "tag": "v20261113.01", "machine": "v20261026.01" }, "terraform": { @@ -13,12 +13,12 @@ "commit": "e5d547cff92ba84137e7d3af0eae5eee04c63cd8" }, "counts": { - "built": 91, + "built": 96, "partial": 0, "notBuilt": 2, "limits": 17, "endToEnd": 83, - "gap": 8 + "gap": 13 }, - "openapiSha256": "df426012d8424746aed6a141797c96a4e0495082d4dad613dac78bad6b738929" + "openapiSha256": "bb8efbad5b1f1de3ae3301b78c9c50d62d65a987dd05dc12594bc1e7ad2cf0a9" } diff --git a/src/data/shots.json b/src/data/shots.json index 61a6918..e919a0b 100644 --- a/src/data/shots.json +++ b/src/data/shots.json @@ -1,6 +1,6 @@ { - "release": "v20261110.01", - "commit": "b7ba5206f12ba93a829a48e354be7fce6bc65db4", + "release": "v20261113.01", + "commit": "911f516bc89ae266c013681247d72c40990fe62c", "shots": [ { "name": "workspaces", diff --git a/versions.yaml b/versions.yaml index 9c4ca6e..0413d60 100644 --- a/versions.yaml +++ b/versions.yaml @@ -7,8 +7,8 @@ - name: spin kind: git source: https://github.com/spin-stack/spin - version: v20261110.01 - pin: b7ba5206f12ba93a829a48e354be7fce6bc65db4 + version: v20261113.01 + pin: 911f516bc89ae266c013681247d72c40990fe62c track: github-release note: >- The release the site documents (D2: the newest, exactly). The daily sync moves it to the next