diff --git a/public/openapi.json b/public/openapi.json
index d281da7..3bc7206 100644
--- a/public/openapi.json
+++ b/public/openapi.json
@@ -68,6 +68,67 @@
}
}
},
+ "/spin.v1.AdminService/CreateHostClass": {
+ "post": {
+ "tags": [
+ "AdminService"
+ ],
+ "summary": "CreateHostClass",
+ "operationId": "spin.v1.AdminService.CreateHostClass",
+ "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.CreateHostClassRequest"
+ }
+ }
+ },
+ "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.CreateHostClassResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.AdminService/CreateNetworkPolicy": {
"post": {
"tags": [
@@ -253,6 +314,67 @@
}
}
},
+ "/spin.v1.AdminService/DeleteHostClass": {
+ "post": {
+ "tags": [
+ "AdminService"
+ ],
+ "summary": "DeleteHostClass",
+ "operationId": "spin.v1.AdminService.DeleteHostClass",
+ "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.DeleteHostClassRequest"
+ }
+ }
+ },
+ "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.DeleteHostClassResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.AdminService/DeleteNetworkPolicy": {
"post": {
"tags": [
@@ -1369,6 +1491,67 @@
}
}
},
+ "/spin.v1.AdminService/ListHostClasses": {
+ "post": {
+ "tags": [
+ "AdminService"
+ ],
+ "summary": "ListHostClasses",
+ "operationId": "spin.v1.AdminService.ListHostClasses",
+ "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.ListHostClassesRequest"
+ }
+ }
+ },
+ "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.ListHostClassesResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.AdminService/ListJobRuns": {
"post": {
"tags": [
@@ -2299,6 +2482,67 @@
}
}
},
+ "/spin.v1.AdminService/UpdateHostClass": {
+ "post": {
+ "tags": [
+ "AdminService"
+ ],
+ "summary": "UpdateHostClass",
+ "operationId": "spin.v1.AdminService.UpdateHostClass",
+ "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.UpdateHostClassRequest"
+ }
+ }
+ },
+ "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.UpdateHostClassResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.AdminService/UpdateInstallationSettings": {
"post": {
"tags": [
@@ -3598,14 +3842,13 @@
}
}
},
- "/spin.v1.HostService/ActivateHost": {
+ "/spin.v1.ForgeService/CompleteAppCreation": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "ActivateHost",
- "description": "Activate a drained host (set to healthy, admin only)",
- "operationId": "spin.v1.HostService.ActivateHost",
+ "summary": "CompleteAppCreation",
+ "operationId": "spin.v1.ForgeService.CompleteAppCreation",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3630,7 +3873,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ActivateHostRequest"
+ "$ref": "#/components/schemas/spin.v1.CompleteAppCreationRequest"
}
}
},
@@ -3652,7 +3895,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ActivateHostResponse"
+ "$ref": "#/components/schemas/spin.v1.CompleteAppCreationResponse"
}
}
}
@@ -3660,14 +3903,13 @@
}
}
},
- "/spin.v1.HostService/DrainHost": {
+ "/spin.v1.ForgeService/CompleteConnect": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "DrainHost",
- "description": "Set host to draining (admin only)",
- "operationId": "spin.v1.HostService.DrainHost",
+ "summary": "CompleteConnect",
+ "operationId": "spin.v1.ForgeService.CompleteConnect",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3692,7 +3934,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DrainHostRequest"
+ "$ref": "#/components/schemas/spin.v1.CompleteConnectRequest"
}
}
},
@@ -3714,7 +3956,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DrainHostResponse"
+ "$ref": "#/components/schemas/spin.v1.CompleteConnectResponse"
}
}
}
@@ -3722,14 +3964,14 @@
}
}
},
- "/spin.v1.HostService/GetFleet": {
+ "/spin.v1.ForgeService/DeleteForgeApp": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "GetFleet",
- "description": "The fleet as a whole (admin only): how many hosts there are against how many the\n installation has asked its cloud for, and what it is waiting for. A fleet that scales from\n none looks broken without this - no hosts, no explanation, and a workspace that waits\n minutes for a machine to boot with nothing saying so.",
- "operationId": "spin.v1.HostService.GetFleet",
+ "summary": "DeleteForgeApp",
+ "description": "Takes the app out of use at once. Every connection through it is revoked at the forge, and the\n app is gone when that is done.",
+ "operationId": "spin.v1.ForgeService.DeleteForgeApp",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3754,7 +3996,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetFleetRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteForgeAppRequest"
}
}
},
@@ -3776,7 +4018,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetFleetResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteForgeAppResponse"
}
}
}
@@ -3784,14 +4026,14 @@
}
}
},
- "/spin.v1.HostService/GetHost": {
+ "/spin.v1.ForgeService/Disconnect": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "GetHost",
- "description": "Get host details (admin only)",
- "operationId": "spin.v1.HostService.GetHost",
+ "summary": "Disconnect",
+ "description": "Forgets the token and revokes it at the forge.",
+ "operationId": "spin.v1.ForgeService.Disconnect",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3816,7 +4058,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetHostRequest"
+ "$ref": "#/components/schemas/spin.v1.DisconnectRequest"
}
}
},
@@ -3838,7 +4080,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetHostResponse"
+ "$ref": "#/components/schemas/spin.v1.DisconnectResponse"
}
}
}
@@ -3846,14 +4088,13 @@
}
}
},
- "/spin.v1.HostService/GetHostPolicy": {
+ "/spin.v1.ForgeService/ListConnections": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "GetHostPolicy",
- "description": "What a host may do, as against what it is (admin only). A host reads what it *is* from\n its own environment, before it can reach this; everything else it is told, on its\n heartbeat, and it reports back the revision it is running under.",
- "operationId": "spin.v1.HostService.GetHostPolicy",
+ "summary": "ListConnections",
+ "operationId": "spin.v1.ForgeService.ListConnections",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3878,7 +4119,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetHostPolicyRequest"
+ "$ref": "#/components/schemas/spin.v1.ListConnectionsRequest"
}
}
},
@@ -3900,7 +4141,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetHostPolicyResponse"
+ "$ref": "#/components/schemas/spin.v1.ListConnectionsResponse"
}
}
}
@@ -3908,14 +4149,14 @@
}
}
},
- "/spin.v1.HostService/ListHosts": {
+ "/spin.v1.ForgeService/ListForgeApps": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "ListHosts",
- "description": "List all hosts (admin only)",
- "operationId": "spin.v1.HostService.ListHosts",
+ "summary": "ListForgeApps",
+ "description": "The installation's app on each forge: whether a person can connect, and where. Its secrets are\n never answered with.",
+ "operationId": "spin.v1.ForgeService.ListForgeApps",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -3940,7 +4181,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListHostsRequest"
+ "$ref": "#/components/schemas/spin.v1.ListForgeAppsRequest"
}
}
},
@@ -3962,7 +4203,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListHostsResponse"
+ "$ref": "#/components/schemas/spin.v1.ListForgeAppsResponse"
}
}
}
@@ -3970,13 +4211,13 @@
}
}
},
- "/spin.v1.HostService/SetHostPolicy": {
+ "/spin.v1.ForgeService/PollDeviceConnect": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "SetHostPolicy",
- "operationId": "spin.v1.HostService.SetHostPolicy",
+ "summary": "PollDeviceConnect",
+ "operationId": "spin.v1.ForgeService.PollDeviceConnect",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4001,7 +4242,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetHostPolicyRequest"
+ "$ref": "#/components/schemas/spin.v1.PollDeviceConnectRequest"
}
}
},
@@ -4023,7 +4264,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetHostPolicyResponse"
+ "$ref": "#/components/schemas/spin.v1.PollDeviceConnectResponse"
}
}
}
@@ -4031,14 +4272,14 @@
}
}
},
- "/spin.v1.HostService/TestHostWriteback": {
+ "/spin.v1.ForgeService/SearchRepositories": {
"post": {
"tags": [
- "HostService"
+ "ForgeService"
],
- "summary": "TestHostWriteback",
- "description": "Measures a host's disk under writeback thresholds no preset names, against the ones it holds\n (admin only). SetHostPolicy refuses such thresholds for a host until it has been tested with\n them; the test loads that host's disk for about forty seconds.",
- "operationId": "spin.v1.HostService.TestHostWriteback",
+ "summary": "SearchRepositories",
+ "description": "The repositories the person's account reaches that match query, as the forge searches them\n with the person's token: what they can see there is what they can pick here.",
+ "operationId": "spin.v1.ForgeService.SearchRepositories",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4063,7 +4304,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.TestHostWritebackRequest"
+ "$ref": "#/components/schemas/spin.v1.SearchRepositoriesRequest"
}
}
},
@@ -4085,7 +4326,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.TestHostWritebackResponse"
+ "$ref": "#/components/schemas/spin.v1.SearchRepositoriesResponse"
}
}
}
@@ -4093,14 +4334,14 @@
}
}
},
- "/spin.v1.LogService/GetTrace": {
+ "/spin.v1.ForgeService/SetForgeApp": {
"post": {
"tags": [
- "LogService"
+ "ForgeService"
],
- "summary": "GetTrace",
- "description": "GetTrace is every span of one trace, in the order they began.",
- "operationId": "spin.v1.LogService.GetTrace",
+ "summary": "SetForgeApp",
+ "description": "Keeping an app that was made elsewhere. Refused while the forge has one: an app is never\n edited, because another client id could not revoke what this one granted.",
+ "operationId": "spin.v1.ForgeService.SetForgeApp",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4125,7 +4366,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetTraceRequest"
+ "$ref": "#/components/schemas/spin.v1.SetForgeAppRequest"
}
}
},
@@ -4147,7 +4388,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetTraceResponse"
+ "$ref": "#/components/schemas/spin.v1.SetForgeAppResponse"
}
}
}
@@ -4155,14 +4396,14 @@
}
}
},
- "/spin.v1.LogService/HistogramPlatformLogs": {
+ "/spin.v1.ForgeService/StartAppCreation": {
"post": {
"tags": [
- "LogService"
+ "ForgeService"
],
- "summary": "HistogramPlatformLogs",
- "description": "HistogramPlatformLogs is how many of those records each interval of the window holds, by\n severity: the chart above them.",
- "operationId": "spin.v1.LogService.HistogramPlatformLogs",
+ "summary": "StartAppCreation",
+ "description": "Making the app, by an instance admin, through the forge's manifest flow: StartAppCreation\n answers the form the browser posts to the forge, which makes the app and sends the admin back\n with a code; CompleteAppCreation exchanges it for the app's credentials. Nobody copies a key.",
+ "operationId": "spin.v1.ForgeService.StartAppCreation",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4187,7 +4428,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.HistogramPlatformLogsRequest"
+ "$ref": "#/components/schemas/spin.v1.StartAppCreationRequest"
}
}
},
@@ -4209,7 +4450,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.HistogramLogsResponse"
+ "$ref": "#/components/schemas/spin.v1.StartAppCreationResponse"
}
}
}
@@ -4217,14 +4458,14 @@
}
}
},
- "/spin.v1.LogService/HistogramWorkspaceLogs": {
+ "/spin.v1.ForgeService/StartConnect": {
"post": {
"tags": [
- "LogService"
+ "ForgeService"
],
- "summary": "HistogramWorkspaceLogs",
- "description": "HistogramWorkspaceLogs is how many of those records each interval of the window holds.",
- "operationId": "spin.v1.LogService.HistogramWorkspaceLogs",
+ "summary": "StartConnect",
+ "description": "Connecting: StartConnect answers where the person authorizes the app and a binding only this\n client keeps; the forge sends the browser back to redirect_uri with a code and the state, and\n CompleteConnect exchanges them, with the binding, for the person's token.",
+ "operationId": "spin.v1.ForgeService.StartConnect",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4249,7 +4490,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.HistogramWorkspaceLogsRequest"
+ "$ref": "#/components/schemas/spin.v1.StartConnectRequest"
}
}
},
@@ -4271,7 +4512,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.HistogramWorkspaceLogsResponse"
+ "$ref": "#/components/schemas/spin.v1.StartConnectResponse"
}
}
}
@@ -4279,14 +4520,14 @@
}
}
},
- "/spin.v1.LogService/ListLogIndexes": {
+ "/spin.v1.ForgeService/StartDeviceConnect": {
"post": {
"tags": [
- "LogService"
+ "ForgeService"
],
- "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": "StartDeviceConnect",
+ "description": "Connecting from somewhere with no browser to send back to, the CLI: the control plane asks the\n forge for a device code (RFC 8628) and keeps it, and answers with the code the person types on\n the forge's page and a handle only the caller can poll with. PollDeviceConnect asks the forge,\n at most once an interval, and on the person's approval keeps their token as CompleteConnect\n does: once, for the person who began it.",
+ "operationId": "spin.v1.ForgeService.StartDeviceConnect",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4311,7 +4552,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListLogIndexesRequest"
+ "$ref": "#/components/schemas/spin.v1.StartDeviceConnectRequest"
}
}
},
@@ -4333,7 +4574,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListLogIndexesResponse"
+ "$ref": "#/components/schemas/spin.v1.StartDeviceConnectResponse"
}
}
}
@@ -4341,14 +4582,14 @@
}
}
},
- "/spin.v1.LogService/PlatformLogFieldValues": {
+ "/spin.v1.HostService/ActivateHost": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "PlatformLogFieldValues",
- "description": "PlatformLogFieldValues is what one field most often is in the records a search of the\n platform's logs finds: service_name, severity_text, attributes.* or resource_attributes.*.",
- "operationId": "spin.v1.LogService.PlatformLogFieldValues",
+ "summary": "ActivateHost",
+ "description": "Activate a drained host (set to healthy, admin only)",
+ "operationId": "spin.v1.HostService.ActivateHost",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4373,7 +4614,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.PlatformLogFieldValuesRequest"
+ "$ref": "#/components/schemas/spin.v1.ActivateHostRequest"
}
}
},
@@ -4395,7 +4636,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.PlatformLogFieldValuesResponse"
+ "$ref": "#/components/schemas/spin.v1.ActivateHostResponse"
}
}
}
@@ -4403,14 +4644,14 @@
}
}
},
- "/spin.v1.LogService/SearchPlatformLogs": {
+ "/spin.v1.HostService/DrainHost": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "SearchPlatformLogs",
- "description": "SearchPlatformLogs is what the installation's own processes logged - the control plane, the\n proxy, the runners. An administrator's: a platform's log names every workspace it touched.",
- "operationId": "spin.v1.LogService.SearchPlatformLogs",
+ "summary": "DrainHost",
+ "description": "Set host to draining (admin only)",
+ "operationId": "spin.v1.HostService.DrainHost",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4435,7 +4676,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchPlatformLogsRequest"
+ "$ref": "#/components/schemas/spin.v1.DrainHostRequest"
}
}
},
@@ -4457,7 +4698,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchLogsResponse"
+ "$ref": "#/components/schemas/spin.v1.DrainHostResponse"
}
}
}
@@ -4465,14 +4706,14 @@
}
}
},
- "/spin.v1.LogService/SearchTraces": {
+ "/spin.v1.HostService/GetFleet": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "SearchTraces",
- "description": "SearchTraces is the installation's traces whose first span matches, newest first. An\n administrator's, as the platform's logs are.",
- "operationId": "spin.v1.LogService.SearchTraces",
+ "summary": "GetFleet",
+ "description": "The fleet as a whole (admin only): how many hosts there are against how many the\n installation has asked its cloud for, and what it is waiting for. A fleet that scales from\n none looks broken without this - no hosts, no explanation, and a workspace that waits\n minutes for a machine to boot with nothing saying so.",
+ "operationId": "spin.v1.HostService.GetFleet",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4497,7 +4738,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchTracesRequest"
+ "$ref": "#/components/schemas/spin.v1.GetFleetRequest"
}
}
},
@@ -4519,7 +4760,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchTracesResponse"
+ "$ref": "#/components/schemas/spin.v1.GetFleetResponse"
}
}
}
@@ -4527,14 +4768,14 @@
}
}
},
- "/spin.v1.LogService/SearchWorkspaceLogs": {
+ "/spin.v1.HostService/GetHost": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "SearchWorkspaceLogs",
- "description": "SearchWorkspaceLogs is what one workspace's machine wrote to its console, where the\n installation collects it: whoever may read the workspace's console may search it.",
- "operationId": "spin.v1.LogService.SearchWorkspaceLogs",
+ "summary": "GetHost",
+ "description": "Get host details (admin only)",
+ "operationId": "spin.v1.HostService.GetHost",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4559,7 +4800,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchWorkspaceLogsRequest"
+ "$ref": "#/components/schemas/spin.v1.GetHostRequest"
}
}
},
@@ -4581,7 +4822,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SearchWorkspaceLogsResponse"
+ "$ref": "#/components/schemas/spin.v1.GetHostResponse"
}
}
}
@@ -4589,14 +4830,14 @@
}
}
},
- "/spin.v1.LogService/ServiceHealth": {
+ "/spin.v1.HostService/GetHostPolicy": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "ServiceHealth",
- "description": "ServiceHealth is how each of the installation's services did over a window, as its traces\n and logs say it. An administrator's, as the traces are.",
- "operationId": "spin.v1.LogService.ServiceHealth",
+ "summary": "GetHostPolicy",
+ "description": "What a host may do, as against what it is (admin only). A host reads what it *is* from\n its own environment, before it can reach this; everything else it is told, on its\n heartbeat, and it reports back the revision it is running under.",
+ "operationId": "spin.v1.HostService.GetHostPolicy",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4621,7 +4862,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ServiceHealthRequest"
+ "$ref": "#/components/schemas/spin.v1.GetHostPolicyRequest"
}
}
},
@@ -4643,7 +4884,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ServiceHealthResponse"
+ "$ref": "#/components/schemas/spin.v1.GetHostPolicyResponse"
}
}
}
@@ -4651,14 +4892,14 @@
}
}
},
- "/spin.v1.LogService/WorkspaceLogFieldValues": {
+ "/spin.v1.HostService/ListHosts": {
"post": {
"tags": [
- "LogService"
+ "HostService"
],
- "summary": "WorkspaceLogFieldValues",
- "description": "WorkspaceLogFieldValues is the same of one workspace's, for whoever may read it.",
- "operationId": "spin.v1.LogService.WorkspaceLogFieldValues",
+ "summary": "ListHosts",
+ "description": "List all hosts (admin only)",
+ "operationId": "spin.v1.HostService.ListHosts",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4683,7 +4924,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.WorkspaceLogFieldValuesRequest"
+ "$ref": "#/components/schemas/spin.v1.ListHostsRequest"
}
}
},
@@ -4705,7 +4946,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.WorkspaceLogFieldValuesResponse"
+ "$ref": "#/components/schemas/spin.v1.ListHostsResponse"
}
}
}
@@ -4713,14 +4954,13 @@
}
}
},
- "/spin.v1.RunnerTokenService/CreateRegistrationToken": {
+ "/spin.v1.HostService/SetHostPolicy": {
"post": {
"tags": [
- "RunnerTokenService"
+ "HostService"
],
- "summary": "CreateRegistrationToken",
- "description": "Generate a one-time registration token (admin only)",
- "operationId": "spin.v1.RunnerTokenService.CreateRegistrationToken",
+ "summary": "SetHostPolicy",
+ "operationId": "spin.v1.HostService.SetHostPolicy",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4745,7 +4985,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateRegistrationTokenRequest"
+ "$ref": "#/components/schemas/spin.v1.SetHostPolicyRequest"
}
}
},
@@ -4767,7 +5007,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateRegistrationTokenResponse"
+ "$ref": "#/components/schemas/spin.v1.SetHostPolicyResponse"
}
}
}
@@ -4775,14 +5015,14 @@
}
}
},
- "/spin.v1.RunnerTokenService/ListRegistrationTokens": {
+ "/spin.v1.HostService/TestHostWriteback": {
"post": {
"tags": [
- "RunnerTokenService"
+ "HostService"
],
- "summary": "ListRegistrationTokens",
- "description": "List active registration tokens (admin only)",
- "operationId": "spin.v1.RunnerTokenService.ListRegistrationTokens",
+ "summary": "TestHostWriteback",
+ "description": "Measures a host's disk under writeback thresholds no preset names, against the ones it holds\n (admin only). SetHostPolicy refuses such thresholds for a host until it has been tested with\n them; the test loads that host's disk for about forty seconds.",
+ "operationId": "spin.v1.HostService.TestHostWriteback",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4807,7 +5047,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListRegistrationTokensRequest"
+ "$ref": "#/components/schemas/spin.v1.TestHostWritebackRequest"
}
}
},
@@ -4829,7 +5069,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListRegistrationTokensResponse"
+ "$ref": "#/components/schemas/spin.v1.TestHostWritebackResponse"
}
}
}
@@ -4837,14 +5077,14 @@
}
}
},
- "/spin.v1.RunnerTokenService/RevokeRegistrationToken": {
+ "/spin.v1.HostShellService/ApproveHostShell": {
"post": {
"tags": [
- "RunnerTokenService"
+ "HostShellService"
],
- "summary": "RevokeRegistrationToken",
- "description": "Revoke a registration token (admin only)",
- "operationId": "spin.v1.RunnerTokenService.RevokeRegistrationToken",
+ "summary": "ApproveHostShell",
+ "description": "Approves a shell another administrator asked for.",
+ "operationId": "spin.v1.HostShellService.ApproveHostShell",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4869,7 +5109,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokeRegistrationTokenRequest"
+ "$ref": "#/components/schemas/spin.v1.ApproveHostShellRequest"
}
}
},
@@ -4891,7 +5131,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokeRegistrationTokenResponse"
+ "$ref": "#/components/schemas/spin.v1.ApproveHostShellResponse"
}
}
}
@@ -4899,14 +5139,14 @@
}
}
},
- "/spin.v1.RunnerTokenService/RevokeRunner": {
+ "/spin.v1.HostShellService/CloseHostShell": {
"post": {
"tags": [
- "RunnerTokenService"
+ "HostShellService"
],
- "summary": "RevokeRunner",
- "description": "Revoke a runner (admin only)",
- "operationId": "spin.v1.RunnerTokenService.RevokeRunner",
+ "summary": "CloseHostShell",
+ "description": "Ends a shell: one not yet approved is refused, any other is ended and its host retired.",
+ "operationId": "spin.v1.HostShellService.CloseHostShell",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4931,7 +5171,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokeRunnerRequest"
+ "$ref": "#/components/schemas/spin.v1.CloseHostShellRequest"
}
}
},
@@ -4953,7 +5193,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokeRunnerResponse"
+ "$ref": "#/components/schemas/spin.v1.CloseHostShellResponse"
}
}
}
@@ -4961,14 +5201,14 @@
}
}
},
- "/spin.v1.SSHKeyService/AddSSHKey": {
+ "/spin.v1.HostShellService/GetHostShellRecording": {
"post": {
"tags": [
- "SSHKeyService"
+ "HostShellService"
],
- "summary": "AddSSHKey",
- "description": "Upload a new SSH public key to user's account",
- "operationId": "spin.v1.SSHKeyService.AddSSHKey",
+ "summary": "GetHostShellRecording",
+ "description": "What a shell's sessions recorded, asciicast v2, each session's in turn.",
+ "operationId": "spin.v1.HostShellService.GetHostShellRecording",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -4993,7 +5233,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.AddSSHKeyRequest"
+ "$ref": "#/components/schemas/spin.v1.GetHostShellRecordingRequest"
}
}
},
@@ -5015,7 +5255,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.AddSSHKeyResponse"
+ "$ref": "#/components/schemas/spin.v1.GetHostShellRecordingResponse"
}
}
}
@@ -5023,14 +5263,14 @@
}
}
},
- "/spin.v1.SSHKeyService/DeleteSSHKey": {
+ "/spin.v1.HostShellService/ListHostShells": {
"post": {
"tags": [
- "SSHKeyService"
+ "HostShellService"
],
- "summary": "DeleteSSHKey",
- "description": "Delete an SSH key from user's account",
- "operationId": "spin.v1.SSHKeyService.DeleteSSHKey",
+ "summary": "ListHostShells",
+ "description": "The installation's host shells, newest first: all of them, or one host's.",
+ "operationId": "spin.v1.HostShellService.ListHostShells",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5055,7 +5295,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteSSHKeyRequest"
+ "$ref": "#/components/schemas/spin.v1.ListHostShellsRequest"
}
}
},
@@ -5077,7 +5317,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteSSHKeyResponse"
+ "$ref": "#/components/schemas/spin.v1.ListHostShellsResponse"
}
}
}
@@ -5085,14 +5325,14 @@
}
}
},
- "/spin.v1.SSHKeyService/ListSSHKeys": {
+ "/spin.v1.HostShellService/RequestHostShell": {
"post": {
"tags": [
- "SSHKeyService"
+ "HostShellService"
],
- "summary": "ListSSHKeys",
- "description": "List user's SSH public keys",
- "operationId": "spin.v1.SSHKeyService.ListSSHKeys",
+ "summary": "RequestHostShell",
+ "description": "Asks for a shell on a host. Where the installation has a second administrator approve\n (host_shell_needs_approval), it waits for one; otherwise the host is emptied at once.",
+ "operationId": "spin.v1.HostShellService.RequestHostShell",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5117,7 +5357,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSSHKeysRequest"
+ "$ref": "#/components/schemas/spin.v1.RequestHostShellRequest"
}
}
},
@@ -5139,7 +5379,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSSHKeysResponse"
+ "$ref": "#/components/schemas/spin.v1.RequestHostShellResponse"
}
}
}
@@ -5147,14 +5387,14 @@
}
}
},
- "/spin.v1.SettingsService/GetUserSettings": {
+ "/spin.v1.LogService/GetTrace": {
"post": {
"tags": [
- "SettingsService"
+ "LogService"
],
- "summary": "GetUserSettings",
- "description": "User settings",
- "operationId": "spin.v1.SettingsService.GetUserSettings",
+ "summary": "GetTrace",
+ "description": "GetTrace is every span of one trace, in the order they began.",
+ "operationId": "spin.v1.LogService.GetTrace",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5179,7 +5419,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetUserSettingsRequest"
+ "$ref": "#/components/schemas/spin.v1.GetTraceRequest"
}
}
},
@@ -5201,7 +5441,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetUserSettingsResponse"
+ "$ref": "#/components/schemas/spin.v1.GetTraceResponse"
}
}
}
@@ -5209,13 +5449,14 @@
}
}
},
- "/spin.v1.SettingsService/UpdateUserSettings": {
+ "/spin.v1.LogService/HistogramPlatformLogs": {
"post": {
"tags": [
- "SettingsService"
+ "LogService"
],
- "summary": "UpdateUserSettings",
- "operationId": "spin.v1.SettingsService.UpdateUserSettings",
+ "summary": "HistogramPlatformLogs",
+ "description": "HistogramPlatformLogs is how many of those records each interval of the window holds, by\n severity: the chart above them.",
+ "operationId": "spin.v1.LogService.HistogramPlatformLogs",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5240,7 +5481,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateUserSettingsRequest"
+ "$ref": "#/components/schemas/spin.v1.HistogramPlatformLogsRequest"
}
}
},
@@ -5262,7 +5503,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateUserSettingsResponse"
+ "$ref": "#/components/schemas/spin.v1.HistogramLogsResponse"
}
}
}
@@ -5270,14 +5511,14 @@
}
}
},
- "/spin.v1.SettingsService/ValidateTaskfile": {
+ "/spin.v1.LogService/HistogramWorkspaceLogs": {
"post": {
"tags": [
- "SettingsService"
+ "LogService"
],
- "summary": "ValidateTaskfile",
- "description": "Validate Taskfile syntax",
- "operationId": "spin.v1.SettingsService.ValidateTaskfile",
+ "summary": "HistogramWorkspaceLogs",
+ "description": "HistogramWorkspaceLogs is how many of those records each interval of the window holds.",
+ "operationId": "spin.v1.LogService.HistogramWorkspaceLogs",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5302,7 +5543,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ValidateTaskfileRequest"
+ "$ref": "#/components/schemas/spin.v1.HistogramWorkspaceLogsRequest"
}
}
},
@@ -5324,7 +5565,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ValidateTaskfileResponse"
+ "$ref": "#/components/schemas/spin.v1.HistogramWorkspaceLogsResponse"
}
}
}
@@ -5332,14 +5573,14 @@
}
}
},
- "/spin.v1.SetupService/ChangePassword": {
+ "/spin.v1.LogService/ListLogIndexes": {
"post": {
"tags": [
- "SetupService"
+ "LogService"
],
- "summary": "ChangePassword",
- "description": "Change admin password (requires auth)\n Called after initial login when force_password_change is true",
- "operationId": "spin.v1.SetupService.ChangePassword",
+ "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",
@@ -5364,7 +5605,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ChangePasswordRequest"
+ "$ref": "#/components/schemas/spin.v1.ListLogIndexesRequest"
}
}
},
@@ -5386,7 +5627,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ChangePasswordResponse"
+ "$ref": "#/components/schemas/spin.v1.ListLogIndexesResponse"
}
}
}
@@ -5394,14 +5635,938 @@
}
}
},
- "/spin.v1.SetupService/CompleteSetup": {
+ "/spin.v1.LogService/PlatformLogFieldValues": {
"post": {
"tags": [
- "SetupService"
+ "LogService"
],
- "summary": "CompleteSetup",
- "description": "Mark setup wizard as complete (requires admin)",
- "operationId": "spin.v1.SetupService.CompleteSetup",
+ "summary": "PlatformLogFieldValues",
+ "description": "PlatformLogFieldValues is what one field most often is in the records a search of the\n platform's logs finds: service_name, severity_text, attributes.* or resource_attributes.*.",
+ "operationId": "spin.v1.LogService.PlatformLogFieldValues",
+ "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.PlatformLogFieldValuesRequest"
+ }
+ }
+ },
+ "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.PlatformLogFieldValuesResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.LogService/SearchPlatformLogs": {
+ "post": {
+ "tags": [
+ "LogService"
+ ],
+ "summary": "SearchPlatformLogs",
+ "description": "SearchPlatformLogs is what the installation's own processes logged - the control plane, the\n proxy, the runners. An administrator's: a platform's log names every workspace it touched.",
+ "operationId": "spin.v1.LogService.SearchPlatformLogs",
+ "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.SearchPlatformLogsRequest"
+ }
+ }
+ },
+ "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.SearchLogsResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.LogService/SearchTraces": {
+ "post": {
+ "tags": [
+ "LogService"
+ ],
+ "summary": "SearchTraces",
+ "description": "SearchTraces is the installation's traces whose first span matches, newest first. An\n administrator's, as the platform's logs are.",
+ "operationId": "spin.v1.LogService.SearchTraces",
+ "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.SearchTracesRequest"
+ }
+ }
+ },
+ "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.SearchTracesResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.LogService/SearchWorkspaceLogs": {
+ "post": {
+ "tags": [
+ "LogService"
+ ],
+ "summary": "SearchWorkspaceLogs",
+ "description": "SearchWorkspaceLogs is what one workspace's machine wrote to its console, where the\n installation collects it: whoever may read the workspace's console may search it.",
+ "operationId": "spin.v1.LogService.SearchWorkspaceLogs",
+ "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.SearchWorkspaceLogsRequest"
+ }
+ }
+ },
+ "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.SearchWorkspaceLogsResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.LogService/ServiceHealth": {
+ "post": {
+ "tags": [
+ "LogService"
+ ],
+ "summary": "ServiceHealth",
+ "description": "ServiceHealth is how each of the installation's services did over a window, as its traces\n and logs say it. An administrator's, as the traces are.",
+ "operationId": "spin.v1.LogService.ServiceHealth",
+ "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.ServiceHealthRequest"
+ }
+ }
+ },
+ "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.ServiceHealthResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.LogService/WorkspaceLogFieldValues": {
+ "post": {
+ "tags": [
+ "LogService"
+ ],
+ "summary": "WorkspaceLogFieldValues",
+ "description": "WorkspaceLogFieldValues is the same of one workspace's, for whoever may read it.",
+ "operationId": "spin.v1.LogService.WorkspaceLogFieldValues",
+ "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.WorkspaceLogFieldValuesRequest"
+ }
+ }
+ },
+ "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.WorkspaceLogFieldValuesResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/CreateProject": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "CreateProject",
+ "operationId": "spin.v1.ProjectService.CreateProject",
+ "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.CreateProjectRequest"
+ }
+ }
+ },
+ "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.CreateProjectResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/DeleteProject": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "DeleteProject",
+ "operationId": "spin.v1.ProjectService.DeleteProject",
+ "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.DeleteProjectRequest"
+ }
+ }
+ },
+ "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.DeleteProjectResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/GetProject": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "GetProject",
+ "operationId": "spin.v1.ProjectService.GetProject",
+ "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.GetProjectRequest"
+ }
+ }
+ },
+ "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.GetProjectResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/ListPrebuilds": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "ListPrebuilds",
+ "description": "A project's prebuilds, to whoever sees it; and a build of one of its refs now, by its admins,\n rather than at the next look.",
+ "operationId": "spin.v1.ProjectService.ListPrebuilds",
+ "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.ListPrebuildsRequest"
+ }
+ }
+ },
+ "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.ListPrebuildsResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/ListProjects": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "ListProjects",
+ "operationId": "spin.v1.ProjectService.ListProjects",
+ "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.ListProjectsRequest"
+ }
+ }
+ },
+ "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.ListProjectsResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/RunPrebuild": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "RunPrebuild",
+ "operationId": "spin.v1.ProjectService.RunPrebuild",
+ "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.RunPrebuildRequest"
+ }
+ }
+ },
+ "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.RunPrebuildResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/ShareProject": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "ShareProject",
+ "operationId": "spin.v1.ProjectService.ShareProject",
+ "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.ShareProjectRequest"
+ }
+ }
+ },
+ "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.ShareProjectResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.ProjectService/UpdateProject": {
+ "post": {
+ "tags": [
+ "ProjectService"
+ ],
+ "summary": "UpdateProject",
+ "description": "Changed, shared and deleted by its admins. A project with a workspace open on it is not\n deleted.",
+ "operationId": "spin.v1.ProjectService.UpdateProject",
+ "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.UpdateProjectRequest"
+ }
+ }
+ },
+ "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.UpdateProjectResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.RunnerTokenService/CreateRegistrationToken": {
+ "post": {
+ "tags": [
+ "RunnerTokenService"
+ ],
+ "summary": "CreateRegistrationToken",
+ "description": "Generate a one-time registration token (admin only)",
+ "operationId": "spin.v1.RunnerTokenService.CreateRegistrationToken",
+ "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.CreateRegistrationTokenRequest"
+ }
+ }
+ },
+ "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.CreateRegistrationTokenResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.RunnerTokenService/ListRegistrationTokens": {
+ "post": {
+ "tags": [
+ "RunnerTokenService"
+ ],
+ "summary": "ListRegistrationTokens",
+ "description": "List active registration tokens (admin only)",
+ "operationId": "spin.v1.RunnerTokenService.ListRegistrationTokens",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5426,7 +6591,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CompleteSetupRequest"
+ "$ref": "#/components/schemas/spin.v1.ListRegistrationTokensRequest"
}
}
},
@@ -5448,7 +6613,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CompleteSetupResponse"
+ "$ref": "#/components/schemas/spin.v1.ListRegistrationTokensResponse"
}
}
}
@@ -5456,14 +6621,14 @@
}
}
},
- "/spin.v1.SetupService/ConfigureGitHubOAuth": {
+ "/spin.v1.RunnerTokenService/RevokeRegistrationToken": {
"post": {
"tags": [
- "SetupService"
+ "RunnerTokenService"
],
- "summary": "ConfigureGitHubOAuth",
- "description": "Configure GitHub OAuth credentials (requires admin)\n Encrypts and stores client_secret in the database",
- "operationId": "spin.v1.SetupService.ConfigureGitHubOAuth",
+ "summary": "RevokeRegistrationToken",
+ "description": "Revoke a registration token (admin only)",
+ "operationId": "spin.v1.RunnerTokenService.RevokeRegistrationToken",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5488,7 +6653,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ConfigureGitHubOAuthRequest"
+ "$ref": "#/components/schemas/spin.v1.RevokeRegistrationTokenRequest"
}
}
},
@@ -5510,7 +6675,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ConfigureGitHubOAuthResponse"
+ "$ref": "#/components/schemas/spin.v1.RevokeRegistrationTokenResponse"
}
}
}
@@ -5518,14 +6683,14 @@
}
}
},
- "/spin.v1.SetupService/DisableBootstrapAdmin": {
+ "/spin.v1.RunnerTokenService/RevokeRunner": {
"post": {
"tags": [
- "SetupService"
+ "RunnerTokenService"
],
- "summary": "DisableBootstrapAdmin",
- "description": "Disable the bootstrap admin account (requires admin + OAuth must be configured)\n This removes password login capability for the bootstrap user",
- "operationId": "spin.v1.SetupService.DisableBootstrapAdmin",
+ "summary": "RevokeRunner",
+ "description": "Revoke a runner (admin only)",
+ "operationId": "spin.v1.RunnerTokenService.RevokeRunner",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5550,7 +6715,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DisableBootstrapAdminRequest"
+ "$ref": "#/components/schemas/spin.v1.RevokeRunnerRequest"
}
}
},
@@ -5572,7 +6737,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DisableBootstrapAdminResponse"
+ "$ref": "#/components/schemas/spin.v1.RevokeRunnerResponse"
}
}
}
@@ -5580,14 +6745,14 @@
}
}
},
- "/spin.v1.SetupService/EnableDevMode": {
+ "/spin.v1.SSHKeyService/AddSSHKey": {
"post": {
"tags": [
- "SetupService"
+ "SSHKeyService"
],
- "summary": "EnableDevMode",
- "description": "Enable dev mode (requires admin)\n Creates a \"dev\" identity provider that allows login without real OAuth\n Only use for local development - not for production",
- "operationId": "spin.v1.SetupService.EnableDevMode",
+ "summary": "AddSSHKey",
+ "description": "Upload a new SSH public key to user's account",
+ "operationId": "spin.v1.SSHKeyService.AddSSHKey",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5612,7 +6777,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.EnableDevModeRequest"
+ "$ref": "#/components/schemas/spin.v1.AddSSHKeyRequest"
}
}
},
@@ -5634,7 +6799,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.EnableDevModeResponse"
+ "$ref": "#/components/schemas/spin.v1.AddSSHKeyResponse"
}
}
}
@@ -5642,14 +6807,14 @@
}
}
},
- "/spin.v1.SetupService/GetSetupStatus": {
+ "/spin.v1.SSHKeyService/DeleteSSHKey": {
"post": {
"tags": [
- "SetupService"
+ "SSHKeyService"
],
- "summary": "GetSetupStatus",
- "description": "Get current setup status (public - no auth required)\n Returns whether setup wizard needs to be completed",
- "operationId": "spin.v1.SetupService.GetSetupStatus",
+ "summary": "DeleteSSHKey",
+ "description": "Delete an SSH key from user's account",
+ "operationId": "spin.v1.SSHKeyService.DeleteSSHKey",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5674,7 +6839,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetSetupStatusRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteSSHKeyRequest"
}
}
},
@@ -5696,7 +6861,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetSetupStatusResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteSSHKeyResponse"
}
}
}
@@ -5704,14 +6869,14 @@
}
}
},
- "/spin.v1.SetupService/PasswordLogin": {
+ "/spin.v1.SSHKeyService/ListSSHKeys": {
"post": {
"tags": [
- "SetupService"
+ "SSHKeyService"
],
- "summary": "PasswordLogin",
- "description": "Login with email/password (public - for admin bootstrap)\n Only works for users with admin_credentials",
- "operationId": "spin.v1.SetupService.PasswordLogin",
+ "summary": "ListSSHKeys",
+ "description": "List user's SSH public keys",
+ "operationId": "spin.v1.SSHKeyService.ListSSHKeys",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5736,7 +6901,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.PasswordLoginRequest"
+ "$ref": "#/components/schemas/spin.v1.ListSSHKeysRequest"
}
}
},
@@ -5758,7 +6923,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.PasswordLoginResponse"
+ "$ref": "#/components/schemas/spin.v1.ListSSHKeysResponse"
}
}
}
@@ -5766,14 +6931,14 @@
}
}
},
- "/spin.v1.ShareService/ListSharedSnapshots": {
+ "/spin.v1.SettingsService/GetUserSettings": {
"post": {
"tags": [
- "ShareService"
+ "SettingsService"
],
- "summary": "ListSharedSnapshots",
- "description": "List snapshots shared with the current user (or public)",
- "operationId": "spin.v1.ShareService.ListSharedSnapshots",
+ "summary": "GetUserSettings",
+ "description": "User settings",
+ "operationId": "spin.v1.SettingsService.GetUserSettings",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5798,7 +6963,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSharedSnapshotsRequest"
+ "$ref": "#/components/schemas/spin.v1.GetUserSettingsRequest"
}
}
},
@@ -5820,7 +6985,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSharedSnapshotsResponse"
+ "$ref": "#/components/schemas/spin.v1.GetUserSettingsResponse"
}
}
}
@@ -5828,14 +6993,13 @@
}
}
},
- "/spin.v1.ShareService/ListSnapshotShares": {
+ "/spin.v1.SettingsService/UpdateUserSettings": {
"post": {
"tags": [
- "ShareService"
+ "SettingsService"
],
- "summary": "ListSnapshotShares",
- "description": "List users who have access to a snapshot",
- "operationId": "spin.v1.ShareService.ListSnapshotShares",
+ "summary": "UpdateUserSettings",
+ "operationId": "spin.v1.SettingsService.UpdateUserSettings",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5860,7 +7024,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSnapshotSharesRequest"
+ "$ref": "#/components/schemas/spin.v1.UpdateUserSettingsRequest"
}
}
},
@@ -5882,7 +7046,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListSnapshotSharesResponse"
+ "$ref": "#/components/schemas/spin.v1.UpdateUserSettingsResponse"
}
}
}
@@ -5890,14 +7054,14 @@
}
}
},
- "/spin.v1.ShareService/SetSnapshotVisibility": {
+ "/spin.v1.SettingsService/ValidateTaskfile": {
"post": {
"tags": [
- "ShareService"
+ "SettingsService"
],
- "summary": "SetSnapshotVisibility",
- "description": "Set snapshot visibility (public/private)",
- "operationId": "spin.v1.ShareService.SetSnapshotVisibility",
+ "summary": "ValidateTaskfile",
+ "description": "Validate Taskfile syntax",
+ "operationId": "spin.v1.SettingsService.ValidateTaskfile",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5922,7 +7086,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetSnapshotVisibilityRequest"
+ "$ref": "#/components/schemas/spin.v1.ValidateTaskfileRequest"
}
}
},
@@ -5944,7 +7108,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetSnapshotVisibilityResponse"
+ "$ref": "#/components/schemas/spin.v1.ValidateTaskfileResponse"
}
}
}
@@ -5952,14 +7116,14 @@
}
}
},
- "/spin.v1.ShareService/ShareSnapshot": {
+ "/spin.v1.SetupService/ChangePassword": {
"post": {
"tags": [
- "ShareService"
+ "SetupService"
],
- "summary": "ShareSnapshot",
- "description": "Share a snapshot with a specific user",
- "operationId": "spin.v1.ShareService.ShareSnapshot",
+ "summary": "ChangePassword",
+ "description": "Change admin password (requires auth)\n Called after initial login when force_password_change is true",
+ "operationId": "spin.v1.SetupService.ChangePassword",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -5984,7 +7148,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ShareSnapshotRequest"
+ "$ref": "#/components/schemas/spin.v1.ChangePasswordRequest"
}
}
},
@@ -6006,7 +7170,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ShareSnapshotResponse"
+ "$ref": "#/components/schemas/spin.v1.ChangePasswordResponse"
}
}
}
@@ -6014,14 +7178,14 @@
}
}
},
- "/spin.v1.ShareService/UnshareSnapshot": {
+ "/spin.v1.SetupService/CompleteSetup": {
"post": {
"tags": [
- "ShareService"
+ "SetupService"
],
- "summary": "UnshareSnapshot",
- "description": "Remove a user's access to a snapshot",
- "operationId": "spin.v1.ShareService.UnshareSnapshot",
+ "summary": "CompleteSetup",
+ "description": "Mark setup wizard as complete (requires admin)",
+ "operationId": "spin.v1.SetupService.CompleteSetup",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6046,7 +7210,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UnshareSnapshotRequest"
+ "$ref": "#/components/schemas/spin.v1.CompleteSetupRequest"
}
}
},
@@ -6068,7 +7232,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UnshareSnapshotResponse"
+ "$ref": "#/components/schemas/spin.v1.CompleteSetupResponse"
}
}
}
@@ -6076,14 +7240,14 @@
}
}
},
- "/spin.v1.SignatureService/ListPendingSignatures": {
+ "/spin.v1.SetupService/ConfigureGitHubOAuth": {
"post": {
"tags": [
- "SignatureService"
+ "SetupService"
],
- "summary": "ListPendingSignatures",
- "description": "What the caller is asked to sign: their keyring when their keys changed, and the roster of\n each team they own when its members changed.",
- "operationId": "spin.v1.SignatureService.ListPendingSignatures",
+ "summary": "ConfigureGitHubOAuth",
+ "description": "Configure GitHub OAuth credentials (requires admin)\n Encrypts and stores client_secret in the database",
+ "operationId": "spin.v1.SetupService.ConfigureGitHubOAuth",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6108,7 +7272,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListPendingSignaturesRequest"
+ "$ref": "#/components/schemas/spin.v1.ConfigureGitHubOAuthRequest"
}
}
},
@@ -6130,7 +7294,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListPendingSignaturesResponse"
+ "$ref": "#/components/schemas/spin.v1.ConfigureGitHubOAuthResponse"
}
}
}
@@ -6138,14 +7302,14 @@
}
}
},
- "/spin.v1.SignatureService/StartKeyringAnew": {
+ "/spin.v1.SetupService/DisableBootstrapAdmin": {
"post": {
"tags": [
- "SignatureService"
+ "SetupService"
],
- "summary": "StartKeyringAnew",
- "description": "Start the caller's keyring anew: for a person who lost every key its newest version lists.\n The next keyring proposed lists the keys they registered since and is signed with one of\n them. Every workspace they own and every team that names them knows the old keyring, not the\n new one: their workspaces are recovered by cloning a snapshot of each into a new one, and a\n team's owner signs them in again.",
- "operationId": "spin.v1.SignatureService.StartKeyringAnew",
+ "summary": "DisableBootstrapAdmin",
+ "description": "Disable the bootstrap admin account (requires admin + OAuth must be configured)\n This removes password login capability for the bootstrap user",
+ "operationId": "spin.v1.SetupService.DisableBootstrapAdmin",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6170,7 +7334,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.StartKeyringAnewRequest"
+ "$ref": "#/components/schemas/spin.v1.DisableBootstrapAdminRequest"
}
}
},
@@ -6192,7 +7356,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.StartKeyringAnewResponse"
+ "$ref": "#/components/schemas/spin.v1.DisableBootstrapAdminResponse"
}
}
}
@@ -6200,14 +7364,76 @@
}
}
},
- "/spin.v1.SignatureService/SubmitSignature": {
+ "/spin.v1.SetupService/EnableDevMode": {
"post": {
"tags": [
- "SignatureService"
+ "SetupService"
],
- "summary": "SubmitSignature",
- "description": "Take a document a person signed: refused unless it is the one proposed, signed by a key that\n may sign it.",
- "operationId": "spin.v1.SignatureService.SubmitSignature",
+ "summary": "EnableDevMode",
+ "description": "Enable dev mode (requires admin)\n Creates a \"dev\" identity provider that allows login without real OAuth\n Only use for local development - not for production",
+ "operationId": "spin.v1.SetupService.EnableDevMode",
+ "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.EnableDevModeRequest"
+ }
+ }
+ },
+ "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.EnableDevModeResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.SetupService/GetSetupStatus": {
+ "post": {
+ "tags": [
+ "SetupService"
+ ],
+ "summary": "GetSetupStatus",
+ "description": "Get current setup status (public - no auth required)\n Returns whether setup wizard needs to be completed",
+ "operationId": "spin.v1.SetupService.GetSetupStatus",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6232,7 +7458,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SubmitSignatureRequest"
+ "$ref": "#/components/schemas/spin.v1.GetSetupStatusRequest"
}
}
},
@@ -6254,7 +7480,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SubmitSignatureResponse"
+ "$ref": "#/components/schemas/spin.v1.GetSetupStatusResponse"
}
}
}
@@ -6262,13 +7488,14 @@
}
}
},
- "/spin.v1.TeamService/AddMember": {
+ "/spin.v1.SetupService/PasswordLogin": {
"post": {
"tags": [
- "TeamService"
+ "SetupService"
],
- "summary": "AddMember",
- "operationId": "spin.v1.TeamService.AddMember",
+ "summary": "PasswordLogin",
+ "description": "Login with email/password (public - for admin bootstrap)\n Only works for users with admin_credentials",
+ "operationId": "spin.v1.SetupService.PasswordLogin",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6293,7 +7520,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.AddTeamMemberRequest"
+ "$ref": "#/components/schemas/spin.v1.PasswordLoginRequest"
}
}
},
@@ -6315,7 +7542,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.AddTeamMemberResponse"
+ "$ref": "#/components/schemas/spin.v1.PasswordLoginResponse"
}
}
}
@@ -6323,13 +7550,14 @@
}
}
},
- "/spin.v1.TeamService/CreateIdentityProvider": {
+ "/spin.v1.ShareService/ListSharedSnapshots": {
"post": {
"tags": [
- "TeamService"
+ "ShareService"
],
- "summary": "CreateIdentityProvider",
- "operationId": "spin.v1.TeamService.CreateIdentityProvider",
+ "summary": "ListSharedSnapshots",
+ "description": "List snapshots shared with the current user (or public)",
+ "operationId": "spin.v1.ShareService.ListSharedSnapshots",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6354,7 +7582,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateTeamIdentityProviderRequest"
+ "$ref": "#/components/schemas/spin.v1.ListSharedSnapshotsRequest"
}
}
},
@@ -6376,7 +7604,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateTeamIdentityProviderResponse"
+ "$ref": "#/components/schemas/spin.v1.ListSharedSnapshotsResponse"
}
}
}
@@ -6384,14 +7612,14 @@
}
}
},
- "/spin.v1.TeamService/CreateProject": {
+ "/spin.v1.ShareService/ListSnapshotShares": {
"post": {
"tags": [
- "TeamService"
+ "ShareService"
],
- "summary": "CreateProject",
- "description": "A team's projects: a repository and the shape of the workspace that opens it\n (CreateWorkspaceRequest.project_id). Made, changed and deleted by the team's admins, listed\n and opened by its members. A project with a workspace open on it is not deleted.",
- "operationId": "spin.v1.TeamService.CreateProject",
+ "summary": "ListSnapshotShares",
+ "description": "List users who have access to a snapshot",
+ "operationId": "spin.v1.ShareService.ListSnapshotShares",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6416,7 +7644,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateProjectRequest"
+ "$ref": "#/components/schemas/spin.v1.ListSnapshotSharesRequest"
}
}
},
@@ -6438,7 +7666,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateProjectResponse"
+ "$ref": "#/components/schemas/spin.v1.ListSnapshotSharesResponse"
}
}
}
@@ -6446,14 +7674,14 @@
}
}
},
- "/spin.v1.TeamService/CreateTeam": {
+ "/spin.v1.ShareService/SetSnapshotVisibility": {
"post": {
"tags": [
- "TeamService"
+ "ShareService"
],
- "summary": "CreateTeam",
- "description": "Create a new team (can be nested under parent_id)",
- "operationId": "spin.v1.TeamService.CreateTeam",
+ "summary": "SetSnapshotVisibility",
+ "description": "Set snapshot visibility (public/private)",
+ "operationId": "spin.v1.ShareService.SetSnapshotVisibility",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6478,7 +7706,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateTeamRequest"
+ "$ref": "#/components/schemas/spin.v1.SetSnapshotVisibilityRequest"
}
}
},
@@ -6500,7 +7728,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.CreateTeamResponse"
+ "$ref": "#/components/schemas/spin.v1.SetSnapshotVisibilityResponse"
}
}
}
@@ -6508,13 +7736,14 @@
}
}
},
- "/spin.v1.TeamService/DeleteForge": {
+ "/spin.v1.ShareService/ShareSnapshot": {
"post": {
"tags": [
- "TeamService"
+ "ShareService"
],
- "summary": "DeleteForge",
- "operationId": "spin.v1.TeamService.DeleteForge",
+ "summary": "ShareSnapshot",
+ "description": "Share a snapshot with a specific user",
+ "operationId": "spin.v1.ShareService.ShareSnapshot",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6539,7 +7768,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamForgeRequest"
+ "$ref": "#/components/schemas/spin.v1.ShareSnapshotRequest"
}
}
},
@@ -6561,7 +7790,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamForgeResponse"
+ "$ref": "#/components/schemas/spin.v1.ShareSnapshotResponse"
}
}
}
@@ -6569,13 +7798,14 @@
}
}
},
- "/spin.v1.TeamService/DeleteIdentityProvider": {
+ "/spin.v1.ShareService/UnshareSnapshot": {
"post": {
"tags": [
- "TeamService"
+ "ShareService"
],
- "summary": "DeleteIdentityProvider",
- "operationId": "spin.v1.TeamService.DeleteIdentityProvider",
+ "summary": "UnshareSnapshot",
+ "description": "Remove a user's access to a snapshot",
+ "operationId": "spin.v1.ShareService.UnshareSnapshot",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6600,7 +7830,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamIdentityProviderRequest"
+ "$ref": "#/components/schemas/spin.v1.UnshareSnapshotRequest"
}
}
},
@@ -6622,7 +7852,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamIdentityProviderResponse"
+ "$ref": "#/components/schemas/spin.v1.UnshareSnapshotResponse"
}
}
}
@@ -6630,13 +7860,14 @@
}
}
},
- "/spin.v1.TeamService/DeleteNetworkPolicy": {
+ "/spin.v1.SignatureService/ListPendingSignatures": {
"post": {
"tags": [
- "TeamService"
+ "SignatureService"
],
- "summary": "DeleteNetworkPolicy",
- "operationId": "spin.v1.TeamService.DeleteNetworkPolicy",
+ "summary": "ListPendingSignatures",
+ "description": "What the caller is asked to sign: their keyring when their keys changed, and the roster of\n each team they own when its members changed.",
+ "operationId": "spin.v1.SignatureService.ListPendingSignatures",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6661,7 +7892,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamNetworkPolicyRequest"
+ "$ref": "#/components/schemas/spin.v1.ListPendingSignaturesRequest"
}
}
},
@@ -6683,7 +7914,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamNetworkPolicyResponse"
+ "$ref": "#/components/schemas/spin.v1.ListPendingSignaturesResponse"
}
}
}
@@ -6691,13 +7922,14 @@
}
}
},
- "/spin.v1.TeamService/DeleteProject": {
+ "/spin.v1.SignatureService/StartKeyringAnew": {
"post": {
"tags": [
- "TeamService"
+ "SignatureService"
],
- "summary": "DeleteProject",
- "operationId": "spin.v1.TeamService.DeleteProject",
+ "summary": "StartKeyringAnew",
+ "description": "Start the caller's keyring anew: for a person who lost every key its newest version lists.\n The next keyring proposed lists the keys they registered since and is signed with one of\n them. Every workspace they own and every team that names them knows the old keyring, not the\n new one: their workspaces are recovered by cloning a snapshot of each into a new one, and a\n team's owner signs them in again.",
+ "operationId": "spin.v1.SignatureService.StartKeyringAnew",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6722,7 +7954,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteProjectRequest"
+ "$ref": "#/components/schemas/spin.v1.StartKeyringAnewRequest"
}
}
},
@@ -6744,7 +7976,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteProjectResponse"
+ "$ref": "#/components/schemas/spin.v1.StartKeyringAnewResponse"
}
}
}
@@ -6752,13 +7984,14 @@
}
}
},
- "/spin.v1.TeamService/DeleteSecret": {
+ "/spin.v1.SignatureService/SubmitSignature": {
"post": {
"tags": [
- "TeamService"
+ "SignatureService"
],
- "summary": "DeleteSecret",
- "operationId": "spin.v1.TeamService.DeleteSecret",
+ "summary": "SubmitSignature",
+ "description": "Take a document a person signed: refused unless it is the one proposed, signed by a key that\n may sign it.",
+ "operationId": "spin.v1.SignatureService.SubmitSignature",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6783,7 +8016,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamSecretRequest"
+ "$ref": "#/components/schemas/spin.v1.SubmitSignatureRequest"
}
}
},
@@ -6805,7 +8038,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamSecretResponse"
+ "$ref": "#/components/schemas/spin.v1.SubmitSignatureResponse"
}
}
}
@@ -6813,14 +8046,13 @@
}
}
},
- "/spin.v1.TeamService/DeleteTeam": {
+ "/spin.v1.TeamService/AddMember": {
"post": {
"tags": [
"TeamService"
],
- "summary": "DeleteTeam",
- "description": "Delete team (owner only, must have no children)",
- "operationId": "spin.v1.TeamService.DeleteTeam",
+ "summary": "AddMember",
+ "operationId": "spin.v1.TeamService.AddMember",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6845,7 +8077,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamRequest"
+ "$ref": "#/components/schemas/spin.v1.AddTeamMemberRequest"
}
}
},
@@ -6867,7 +8099,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.DeleteTeamResponse"
+ "$ref": "#/components/schemas/spin.v1.AddTeamMemberResponse"
}
}
}
@@ -6875,14 +8107,13 @@
}
}
},
- "/spin.v1.TeamService/GetTeam": {
+ "/spin.v1.TeamService/CreateIdentityProvider": {
"post": {
"tags": [
"TeamService"
],
- "summary": "GetTeam",
- "description": "Get team by ID or path",
- "operationId": "spin.v1.TeamService.GetTeam",
+ "summary": "CreateIdentityProvider",
+ "operationId": "spin.v1.TeamService.CreateIdentityProvider",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6907,7 +8138,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetTeamRequest"
+ "$ref": "#/components/schemas/spin.v1.CreateTeamIdentityProviderRequest"
}
}
},
@@ -6929,7 +8160,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.GetTeamResponse"
+ "$ref": "#/components/schemas/spin.v1.CreateTeamIdentityProviderResponse"
}
}
}
@@ -6937,13 +8168,14 @@
}
}
},
- "/spin.v1.TeamService/ListForges": {
+ "/spin.v1.TeamService/CreateTeam": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListForges",
- "operationId": "spin.v1.TeamService.ListForges",
+ "summary": "CreateTeam",
+ "description": "Create a new team (can be nested under parent_id)",
+ "operationId": "spin.v1.TeamService.CreateTeam",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -6968,7 +8200,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamForgesRequest"
+ "$ref": "#/components/schemas/spin.v1.CreateTeamRequest"
}
}
},
@@ -6990,7 +8222,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamForgesResponse"
+ "$ref": "#/components/schemas/spin.v1.CreateTeamResponse"
}
}
}
@@ -6998,14 +8230,13 @@
}
}
},
- "/spin.v1.TeamService/ListIdentityProviders": {
+ "/spin.v1.TeamService/DeleteIdentityProvider": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListIdentityProviders",
- "description": "Identity provider management (admin only)",
- "operationId": "spin.v1.TeamService.ListIdentityProviders",
+ "summary": "DeleteIdentityProvider",
+ "operationId": "spin.v1.TeamService.DeleteIdentityProvider",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7030,7 +8261,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamIdentityProvidersRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamIdentityProviderRequest"
}
}
},
@@ -7052,7 +8283,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamIdentityProvidersResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamIdentityProviderResponse"
}
}
}
@@ -7060,14 +8291,13 @@
}
}
},
- "/spin.v1.TeamService/ListMembers": {
+ "/spin.v1.TeamService/DeleteNetworkPolicy": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListMembers",
- "description": "Member management",
- "operationId": "spin.v1.TeamService.ListMembers",
+ "summary": "DeleteNetworkPolicy",
+ "operationId": "spin.v1.TeamService.DeleteNetworkPolicy",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7092,7 +8322,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamMembersRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamNetworkPolicyRequest"
}
}
},
@@ -7114,7 +8344,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamMembersResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamNetworkPolicyResponse"
}
}
}
@@ -7122,14 +8352,13 @@
}
}
},
- "/spin.v1.TeamService/ListPrebuilds": {
+ "/spin.v1.TeamService/DeleteSecret": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListPrebuilds",
- "description": "A project's prebuilds, to its team's members; and a build of one of its refs now, by its\n admins, rather than at the next look.",
- "operationId": "spin.v1.TeamService.ListPrebuilds",
+ "summary": "DeleteSecret",
+ "operationId": "spin.v1.TeamService.DeleteSecret",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7154,7 +8383,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListPrebuildsRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamSecretRequest"
}
}
},
@@ -7176,7 +8405,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListPrebuildsResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamSecretResponse"
}
}
}
@@ -7184,13 +8413,14 @@
}
}
},
- "/spin.v1.TeamService/ListProjects": {
+ "/spin.v1.TeamService/DeleteTeam": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListProjects",
- "operationId": "spin.v1.TeamService.ListProjects",
+ "summary": "DeleteTeam",
+ "description": "Delete team (owner only, must have no children)",
+ "operationId": "spin.v1.TeamService.DeleteTeam",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7215,7 +8445,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListProjectsRequest"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamRequest"
}
}
},
@@ -7237,7 +8467,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListProjectsResponse"
+ "$ref": "#/components/schemas/spin.v1.DeleteTeamResponse"
}
}
}
@@ -7245,13 +8475,14 @@
}
}
},
- "/spin.v1.TeamService/ListSecrets": {
+ "/spin.v1.TeamService/GetTeam": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListSecrets",
- "operationId": "spin.v1.TeamService.ListSecrets",
+ "summary": "GetTeam",
+ "description": "Get team by ID or path",
+ "operationId": "spin.v1.TeamService.GetTeam",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7276,7 +8507,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamSecretsRequest"
+ "$ref": "#/components/schemas/spin.v1.GetTeamRequest"
}
}
},
@@ -7298,7 +8529,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamSecretsResponse"
+ "$ref": "#/components/schemas/spin.v1.GetTeamResponse"
}
}
}
@@ -7306,14 +8537,14 @@
}
}
},
- "/spin.v1.TeamService/ListTeams": {
+ "/spin.v1.TeamService/ListIdentityProviders": {
"post": {
"tags": [
"TeamService"
],
- "summary": "ListTeams",
- "description": "List teams (optionally filtered by parent)",
- "operationId": "spin.v1.TeamService.ListTeams",
+ "summary": "ListIdentityProviders",
+ "description": "Identity provider management (admin only)",
+ "operationId": "spin.v1.TeamService.ListIdentityProviders",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7338,7 +8569,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamsRequest"
+ "$ref": "#/components/schemas/spin.v1.ListTeamIdentityProvidersRequest"
}
}
},
@@ -7360,7 +8591,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.ListTeamsResponse"
+ "$ref": "#/components/schemas/spin.v1.ListTeamIdentityProvidersResponse"
}
}
}
@@ -7368,13 +8599,14 @@
}
}
},
- "/spin.v1.TeamService/RemoveMember": {
+ "/spin.v1.TeamService/ListMembers": {
"post": {
"tags": [
"TeamService"
],
- "summary": "RemoveMember",
- "operationId": "spin.v1.TeamService.RemoveMember",
+ "summary": "ListMembers",
+ "description": "Member management",
+ "operationId": "spin.v1.TeamService.ListMembers",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7399,7 +8631,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RemoveTeamMemberRequest"
+ "$ref": "#/components/schemas/spin.v1.ListTeamMembersRequest"
}
}
},
@@ -7421,7 +8653,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RemoveTeamMemberResponse"
+ "$ref": "#/components/schemas/spin.v1.ListTeamMembersResponse"
}
}
}
@@ -7429,13 +8661,13 @@
}
}
},
- "/spin.v1.TeamService/RunPrebuild": {
+ "/spin.v1.TeamService/ListSecrets": {
"post": {
"tags": [
"TeamService"
],
- "summary": "RunPrebuild",
- "operationId": "spin.v1.TeamService.RunPrebuild",
+ "summary": "ListSecrets",
+ "operationId": "spin.v1.TeamService.ListSecrets",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7460,7 +8692,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RunPrebuildRequest"
+ "$ref": "#/components/schemas/spin.v1.ListTeamSecretsRequest"
}
}
},
@@ -7482,7 +8714,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RunPrebuildResponse"
+ "$ref": "#/components/schemas/spin.v1.ListTeamSecretsResponse"
}
}
}
@@ -7490,14 +8722,14 @@
}
}
},
- "/spin.v1.TeamService/SetForge": {
+ "/spin.v1.TeamService/ListTeams": {
"post": {
"tags": [
"TeamService"
],
- "summary": "SetForge",
- "description": "A team's forges: the app it installed on its organization, from which the control plane mints\n the credential a brokered rule injecting that forge has the egress proxy set - an hour's, so a\n workspace of the team clones and pushes its private repositories with nothing of it on the disk.\n Set and deleted by the team's admins, after the forge has minted one with what was given; listed\n to its members. The app's key is never answered with again.",
- "operationId": "spin.v1.TeamService.SetForge",
+ "summary": "ListTeams",
+ "description": "List teams (optionally filtered by parent)",
+ "operationId": "spin.v1.TeamService.ListTeams",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7522,7 +8754,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamForgeRequest"
+ "$ref": "#/components/schemas/spin.v1.ListTeamsRequest"
}
}
},
@@ -7544,7 +8776,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamForgeResponse"
+ "$ref": "#/components/schemas/spin.v1.ListTeamsResponse"
}
}
}
@@ -7552,14 +8784,13 @@
}
}
},
- "/spin.v1.TeamService/SetNetworkPolicy": {
+ "/spin.v1.TeamService/RemoveMember": {
"post": {
"tags": [
"TeamService"
],
- "summary": "SetNetworkPolicy",
- "description": "A team's egress policies: named, and only allowing - no rule brokers - for the workspaces of\n the team and the teams below it. Set and deleted by its admins where the installation lets\n teams declare them (team_network_policies); listed by WorkspaceService.ListNetworkPolicies.",
- "operationId": "spin.v1.TeamService.SetNetworkPolicy",
+ "summary": "RemoveMember",
+ "operationId": "spin.v1.TeamService.RemoveMember",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7584,7 +8815,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamNetworkPolicyRequest"
+ "$ref": "#/components/schemas/spin.v1.RemoveTeamMemberRequest"
}
}
},
@@ -7606,7 +8837,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamNetworkPolicyResponse"
+ "$ref": "#/components/schemas/spin.v1.RemoveTeamMemberResponse"
}
}
}
@@ -7614,14 +8845,14 @@
}
}
},
- "/spin.v1.TeamService/SetSecret": {
+ "/spin.v1.TeamService/SetNetworkPolicy": {
"post": {
"tags": [
"TeamService"
],
- "summary": "SetSecret",
- "description": "A team's secrets: what a brokered network rule has the host's egress proxy add to a request,\n so a workspace of the team uses a key it never holds. Set and deleted by the team's admins,\n listed by name to its members; a value is never answered with again.",
- "operationId": "spin.v1.TeamService.SetSecret",
+ "summary": "SetNetworkPolicy",
+ "description": "A team's egress policies: named, and only allowing - no rule brokers - for the workspaces of\n the team and the teams below it. Set and deleted by its admins where the installation lets\n teams declare them (team_network_policies); listed by WorkspaceService.ListNetworkPolicies.",
+ "operationId": "spin.v1.TeamService.SetNetworkPolicy",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7646,7 +8877,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamSecretRequest"
+ "$ref": "#/components/schemas/spin.v1.SetTeamNetworkPolicyRequest"
}
}
},
@@ -7668,7 +8899,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetTeamSecretResponse"
+ "$ref": "#/components/schemas/spin.v1.SetTeamNetworkPolicyResponse"
}
}
}
@@ -7676,13 +8907,14 @@
}
}
},
- "/spin.v1.TeamService/UpdateIdentityProvider": {
+ "/spin.v1.TeamService/SetSecret": {
"post": {
"tags": [
"TeamService"
],
- "summary": "UpdateIdentityProvider",
- "operationId": "spin.v1.TeamService.UpdateIdentityProvider",
+ "summary": "SetSecret",
+ "description": "A team's secrets: what a brokered network rule has the host's egress proxy add to a request,\n so a workspace of the team uses a key it never holds. Set and deleted by the team's admins,\n listed by name to its members; a value is never answered with again.",
+ "operationId": "spin.v1.TeamService.SetSecret",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7707,7 +8939,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateTeamIdentityProviderRequest"
+ "$ref": "#/components/schemas/spin.v1.SetTeamSecretRequest"
}
}
},
@@ -7729,7 +8961,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateTeamIdentityProviderResponse"
+ "$ref": "#/components/schemas/spin.v1.SetTeamSecretResponse"
}
}
}
@@ -7737,13 +8969,13 @@
}
}
},
- "/spin.v1.TeamService/UpdateMemberRole": {
+ "/spin.v1.TeamService/UpdateIdentityProvider": {
"post": {
"tags": [
"TeamService"
],
- "summary": "UpdateMemberRole",
- "operationId": "spin.v1.TeamService.UpdateMemberRole",
+ "summary": "UpdateIdentityProvider",
+ "operationId": "spin.v1.TeamService.UpdateIdentityProvider",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7768,7 +9000,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateTeamMemberRoleRequest"
+ "$ref": "#/components/schemas/spin.v1.UpdateTeamIdentityProviderRequest"
}
}
},
@@ -7790,7 +9022,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateTeamMemberRoleResponse"
+ "$ref": "#/components/schemas/spin.v1.UpdateTeamIdentityProviderResponse"
}
}
}
@@ -7798,13 +9030,13 @@
}
}
},
- "/spin.v1.TeamService/UpdateProject": {
+ "/spin.v1.TeamService/UpdateMemberRole": {
"post": {
"tags": [
"TeamService"
],
- "summary": "UpdateProject",
- "operationId": "spin.v1.TeamService.UpdateProject",
+ "summary": "UpdateMemberRole",
+ "operationId": "spin.v1.TeamService.UpdateMemberRole",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -7829,7 +9061,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateProjectRequest"
+ "$ref": "#/components/schemas/spin.v1.UpdateTeamMemberRoleRequest"
}
}
},
@@ -7851,7 +9083,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.UpdateProjectResponse"
+ "$ref": "#/components/schemas/spin.v1.UpdateTeamMemberRoleResponse"
}
}
}
@@ -8355,6 +9587,68 @@
}
}
},
+ "/spin.v1.WorkspaceService/DeleteAgentVolume": {
+ "post": {
+ "tags": [
+ "WorkspaceService"
+ ],
+ "summary": "DeleteAgentVolume",
+ "description": "Erase an agent home no workspace mounts: its volume, its key and its bytes.",
+ "operationId": "spin.v1.WorkspaceService.DeleteAgentVolume",
+ "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.DeleteAgentVolumeRequest"
+ }
+ }
+ },
+ "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.DeleteAgentVolumeResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.WorkspaceService/DeleteSnapshot": {
"post": {
"tags": [
@@ -9222,6 +10516,130 @@
}
}
},
+ "/spin.v1.WorkspaceService/ListAgentThreads": {
+ "post": {
+ "tags": [
+ "WorkspaceService"
+ ],
+ "summary": "ListAgentThreads",
+ "description": "The agent's threads - T3 Code's chats - as the workspace's supervisor last reported them,\n which it does only while the owner's access consents (collect_logs). Titles, paths and\n times, never a message: the chat itself is read in T3 Code, over the owner's own session\n (`spin agent`).",
+ "operationId": "spin.v1.WorkspaceService.ListAgentThreads",
+ "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.ListAgentThreadsRequest"
+ }
+ }
+ },
+ "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.ListAgentThreadsResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.WorkspaceService/ListAgentVolumes": {
+ "post": {
+ "tags": [
+ "WorkspaceService"
+ ],
+ "summary": "ListAgentVolumes",
+ "description": "The caller's agent homes: the volume each workspace of theirs mounts at /home/spin-agent-t3,\n which outlives the workspace - its chats, its sessions - and which another workspace mounts\n at its creation (CreateWorkspaceRequest.agent_volume_id).",
+ "operationId": "spin.v1.WorkspaceService.ListAgentVolumes",
+ "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.ListAgentVolumesRequest"
+ }
+ }
+ },
+ "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.ListAgentVolumesResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/spin.v1.WorkspaceService/ListDeletedWorkspaces": {
"post": {
"tags": [
@@ -10121,7 +11539,68 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RestoreWorkspaceRequest"
+ "$ref": "#/components/schemas/spin.v1.RestoreWorkspaceRequest"
+ }
+ }
+ },
+ "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.RestoreWorkspaceResponse"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/spin.v1.WorkspaceService/RevokePortAccess": {
+ "post": {
+ "tags": [
+ "WorkspaceService"
+ ],
+ "summary": "RevokePortAccess",
+ "operationId": "spin.v1.WorkspaceService.RevokePortAccess",
+ "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.RevokePortAccessRequest"
}
}
},
@@ -10143,7 +11622,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RestoreWorkspaceResponse"
+ "$ref": "#/components/schemas/spin.v1.RevokePortAccessResponse"
}
}
}
@@ -10151,13 +11630,14 @@
}
}
},
- "/spin.v1.WorkspaceService/RevokePortAccess": {
+ "/spin.v1.WorkspaceService/SetOwnerWord": {
"post": {
"tags": [
"WorkspaceService"
],
- "summary": "RevokePortAccess",
- "operationId": "spin.v1.WorkspaceService.RevokePortAccess",
+ "summary": "SetOwnerWord",
+ "description": "Take an access the owner signed with a key of their keyring, and carry it to the guest:\n refused unless their newest keyring holds the key, of this workspace, at a counter past the\n one kept, saying what the first said of the workspace. The control plane writes no access;\n it only keeps and carries the owner's.",
+ "operationId": "spin.v1.WorkspaceService.SetOwnerWord",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -10182,7 +11662,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokePortAccessRequest"
+ "$ref": "#/components/schemas/spin.v1.SetOwnerWordRequest"
}
}
},
@@ -10204,7 +11684,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.RevokePortAccessResponse"
+ "$ref": "#/components/schemas/spin.v1.SetOwnerWordResponse"
}
}
}
@@ -10212,14 +11692,14 @@
}
}
},
- "/spin.v1.WorkspaceService/SetOwnerWord": {
+ "/spin.v1.WorkspaceService/SetPortAccess": {
"post": {
"tags": [
"WorkspaceService"
],
- "summary": "SetOwnerWord",
- "description": "Take an access the owner signed with a key of their keyring, and carry it to the guest:\n refused unless their newest keyring holds the key, of this workspace, at a counter past the\n one kept, saying what the first said of the workspace. The control plane writes no access;\n it only keeps and carries the owner's.",
- "operationId": "spin.v1.WorkspaceService.SetOwnerWord",
+ "summary": "SetPortAccess",
+ "description": "Who may reach a published port, and who it is shared with.",
+ "operationId": "spin.v1.WorkspaceService.SetPortAccess",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -10244,7 +11724,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetOwnerWordRequest"
+ "$ref": "#/components/schemas/spin.v1.SetPortAccessRequest"
}
}
},
@@ -10266,7 +11746,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetOwnerWordResponse"
+ "$ref": "#/components/schemas/spin.v1.SetPortAccessResponse"
}
}
}
@@ -10274,14 +11754,14 @@
}
}
},
- "/spin.v1.WorkspaceService/SetPortAccess": {
+ "/spin.v1.WorkspaceService/SetWorkspaceBandwidth": {
"post": {
"tags": [
"WorkspaceService"
],
- "summary": "SetPortAccess",
- "description": "Who may reach a published port, and who it is shared with.",
- "operationId": "spin.v1.WorkspaceService.SetPortAccess",
+ "summary": "SetWorkspaceBandwidth",
+ "description": "Set what a workspace's network carries each way, an instance admin's to change: the limit\n bounds the installation's egress bill. A running machine is brought to it where it runs, with\n its connections open; any other takes it at its next start.",
+ "operationId": "spin.v1.WorkspaceService.SetWorkspaceBandwidth",
"parameters": [
{
"name": "Connect-Protocol-Version",
@@ -10306,7 +11786,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetPortAccessRequest"
+ "$ref": "#/components/schemas/spin.v1.SetWorkspaceBandwidthRequest"
}
}
},
@@ -10328,7 +11808,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/spin.v1.SetPortAccessResponse"
+ "$ref": "#/components/schemas/spin.v1.SetWorkspaceBandwidthResponse"
}
}
}
@@ -11063,7 +12543,7 @@
"enum": [
"NULL_VALUE"
],
- "description": "Represents a JSON `null`.\n\n `NullValue` is a sentinel, using an enum with only one value to represent\n the null value for the `Value` type union.\n\n A field of type `NullValue` with any value other than `0` is considered\n invalid. Most ProtoJSON serializers will emit a Value with a `null_value` set\n as a JSON `null` regardless of the integer value, and so will round trip to\n a `0` value.\n\n- `NULL_VALUE`: Null value.",
+ "description": "Represents a JSON `null`.\n\n `NullValue` is a sentinel, using an enum with only one value to represent\n the null value for the `Value` type union.\n\n A field of type `NullValue` with any value other than `0` is considered\n invalid. Most ProtoJSON serializers will emit a `Value` with a `null_value`\n set as a JSON `null` regardless of the integer value, and so will round trip\n to a `0` value.\n\n- `NULL_VALUE`: Null value.",
"x-enum-descriptions": [
"Null value."
]
@@ -11336,6 +12816,78 @@
"title": "AddWorkspaceGuestResponse",
"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"
+ },
+ "archived": {
+ "type": "boolean",
+ "title": "archived"
+ }
+ },
+ "title": "AgentThread",
+ "additionalProperties": false,
+ "description": "AgentThread is one of the agent's threads as T3 Code names it: what a person is shown of a\n chat without opening it."
+ },
+ "spin.v1.AgentVolume": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ },
+ "sizeBytes": {
+ "type": "string",
+ "title": "size_bytes",
+ "format": "int64"
+ },
+ "createdAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "created_at"
+ },
+ "workspaceId": {
+ "type": "string",
+ "title": "workspace_id",
+ "description": "The workspace mounting it, or empty for one no workspace mounts - which a new workspace may."
+ },
+ "workspaceName": {
+ "type": "string",
+ "title": "workspace_name",
+ "description": "The name of the workspace that mounts it, or mounted it last."
+ }
+ },
+ "title": "AgentVolume",
+ "additionalProperties": false,
+ "description": "AgentVolume is one agent home of the caller's."
+ },
"spin.v1.AllowedEmail": {
"type": "object",
"properties": {
@@ -11389,6 +12941,32 @@
"Domain suffix match (@domain.com)"
]
},
+ "spin.v1.ApproveHostShellRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ }
+ },
+ "title": "ApproveHostShellRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ApproveHostShellResponse": {
+ "type": "object",
+ "properties": {
+ "shell": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostShell"
+ }
+ ],
+ "title": "shell"
+ }
+ },
+ "title": "ApproveHostShellResponse",
+ "additionalProperties": false
+ },
"spin.v1.ApproveWorkspaceSignInRequest": {
"type": "object",
"properties": {
@@ -11902,6 +13480,32 @@
"title": "CloneWorkspaceResponse",
"additionalProperties": false
},
+ "spin.v1.CloseHostShellRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ }
+ },
+ "title": "CloseHostShellRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.CloseHostShellResponse": {
+ "type": "object",
+ "properties": {
+ "shell": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostShell"
+ }
+ ],
+ "title": "shell"
+ }
+ },
+ "title": "CloseHostShellResponse",
+ "additionalProperties": false
+ },
"spin.v1.CloudAccess": {
"type": "object",
"properties": {
@@ -11919,6 +13523,74 @@
"additionalProperties": false,
"description": "CloudAccess is a principal on a cloud a brokered rule's requests are authorized as."
},
+ "spin.v1.CompleteAppCreationRequest": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "title": "code"
+ },
+ "state": {
+ "type": "string",
+ "title": "state"
+ },
+ "binding": {
+ "type": "string",
+ "title": "binding"
+ }
+ },
+ "title": "CompleteAppCreationRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.CompleteAppCreationResponse": {
+ "type": "object",
+ "properties": {
+ "app": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.ForgeApp"
+ }
+ ],
+ "title": "app"
+ }
+ },
+ "title": "CompleteAppCreationResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.CompleteConnectRequest": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "title": "code"
+ },
+ "state": {
+ "type": "string",
+ "title": "state"
+ },
+ "binding": {
+ "type": "string",
+ "title": "binding"
+ }
+ },
+ "title": "CompleteConnectRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.CompleteConnectResponse": {
+ "type": "object",
+ "properties": {
+ "connection": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.ForgeConnection"
+ }
+ ],
+ "title": "connection"
+ }
+ },
+ "title": "CompleteConnectResponse",
+ "additionalProperties": false
+ },
"spin.v1.CompleteLoginRequest": {
"type": "object",
"properties": {
@@ -12074,15 +13746,17 @@
"CONFIG_ITEM_KIND_SETTING",
"CONFIG_ITEM_KIND_EMAIL",
"CONFIG_ITEM_KIND_PRESET",
- "CONFIG_ITEM_KIND_POLICY"
+ "CONFIG_ITEM_KIND_POLICY",
+ "CONFIG_ITEM_KIND_HOST_CLASS"
],
- "description": "What one item of the installation's config file is.\n\n- `CONFIG_ITEM_KIND_SETTING`: A setting, by its key: registration_mode, default_network_tags, allowed_images...\n- `CONFIG_ITEM_KIND_EMAIL`: An address or domain registration admits, by itself.",
+ "description": "What one item of the installation's config file is.\n\n- `CONFIG_ITEM_KIND_SETTING`: A setting, by its key: registration_mode, default_network_tags, allowed_images...\n- `CONFIG_ITEM_KIND_EMAIL`: An address or domain registration admits, by itself.\n- `CONFIG_ITEM_KIND_HOST_CLASS`: A host class, by its name: the shape of a group of runners (HostClass).",
"x-enum-descriptions": [
"",
"A setting, by its key: registration_mode, default_network_tags, allowed_images...",
"An address or domain registration admits, by itself.",
"",
- ""
+ "",
+ "A host class, by its name: the shape of a group of runners (HostClass)."
]
},
"spin.v1.ConfigureGitHubOAuthRequest": {
@@ -12177,6 +13851,36 @@
"title": "CreateAPIKeyResponse",
"additionalProperties": false
},
+ "spin.v1.CreateHostClassRequest": {
+ "type": "object",
+ "properties": {
+ "class": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostClass"
+ }
+ ],
+ "title": "class"
+ }
+ },
+ "title": "CreateHostClassRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.CreateHostClassResponse": {
+ "type": "object",
+ "properties": {
+ "class": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostClass"
+ }
+ ],
+ "title": "class"
+ }
+ },
+ "title": "CreateHostClassResponse",
+ "additionalProperties": false
+ },
"spin.v1.CreateNetworkPolicyRequest": {
"type": "object",
"properties": {
@@ -12200,10 +13904,6 @@
"spin.v1.CreateProjectRequest": {
"type": "object",
"properties": {
- "teamId": {
- "type": "string",
- "title": "team_id"
- },
"name": {
"type": "string",
"title": "name"
@@ -12401,16 +14101,24 @@
"format": "int32"
},
"bandwidthMbps": {
- "type": "integer",
+ "type": [
+ "integer",
+ "null"
+ ],
"title": "bandwidth_mbps",
"format": "int32",
- "description": "What a workspace of it may carry each way, in megabits a second; 0 is unshaped."
+ "description": "What a workspace of it may carry each way, in megabits a second. Present, since 0 - unshaped -\n is an answer; absent, the installation's default."
},
"rpoSeconds": {
"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."
+ },
+ "hostClass": {
+ "type": "string",
+ "title": "host_class",
+ "description": "The host class its workspaces run on; empty is the default."
}
},
"title": "CreateResourcePresetRequest",
@@ -12627,7 +14335,7 @@
"projectId": {
"type": "string",
"title": "project_id",
- "description": "A project of a team the caller is in: the workspace is the project's - its team, image,\n preset and tags, which the request then names none of - and its guest clones the project's\n repository at ref, the project's own when empty."
+ "description": "A project the caller sees: the workspace is the project's - its image, preset and tags, which\n the request then names none of - in team_id, the caller's personal team when empty, and its\n guest clones the project's repository at ref, the project's own when empty. Refused while the\n repository is on a forge the installation connects to (ForgeService) and the caller has not\n connected their account there."
},
"ref": {
"type": "string",
@@ -12637,6 +14345,11 @@
"type": "string",
"title": "request_id",
"description": "Optional, at most 128 bytes: the client's key for this create, so a retry after an answer\n it never got is answered with the workspace the first attempt made. The same key with\n another request is refused (FAILED_PRECONDITION)."
+ },
+ "agentVolumeId": {
+ "type": "string",
+ "title": "agent_volume_id",
+ "description": "Optional: an agent's home of the caller's to mount (ListAgentVolumes), one no workspace\n mounts now - the history of a workspace since erased, found again in this one. Empty makes\n the workspace a new one. Refused while no host has room: a workspace that waits for a host\n gets its new home when it is placed, and mounts none it was named."
}
},
"title": "CreateWorkspaceRequest",
@@ -12683,12 +14396,61 @@
"description": "True writes what the file says; false keeps what the installation has."
}
},
- "title": "DecideConfigChangeRequest",
- "additionalProperties": false
+ "title": "DecideConfigChangeRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.DecideConfigChangeResponse": {
+ "type": "object",
+ "title": "DecideConfigChangeResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.DeleteAgentVolumeRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ }
+ },
+ "title": "DeleteAgentVolumeRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.DeleteAgentVolumeResponse": {
+ "type": "object",
+ "title": "DeleteAgentVolumeResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.DeleteForgeAppRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ }
+ },
+ "title": "DeleteForgeAppRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.DeleteForgeAppResponse": {
+ "type": "object",
+ "title": "DeleteForgeAppResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.DeleteHostClassRequest": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "name"
+ }
+ },
+ "title": "DeleteHostClassRequest",
+ "additionalProperties": false,
+ "description": "Refused while a preset or a workspace not deleted runs on it, and for the default. Its group is\n emptied and taken away by the autoscaler."
},
- "spin.v1.DecideConfigChangeResponse": {
+ "spin.v1.DeleteHostClassResponse": {
"type": "object",
- "title": "DecideConfigChangeResponse",
+ "title": "DeleteHostClassResponse",
"additionalProperties": false
},
"spin.v1.DeleteNetworkPolicyRequest": {
@@ -12777,26 +14539,6 @@
"title": "DeleteSnapshotResponse",
"additionalProperties": false
},
- "spin.v1.DeleteTeamForgeRequest": {
- "type": "object",
- "properties": {
- "teamId": {
- "type": "string",
- "title": "team_id"
- },
- "forge": {
- "type": "string",
- "title": "forge"
- }
- },
- "title": "DeleteTeamForgeRequest",
- "additionalProperties": false
- },
- "spin.v1.DeleteTeamForgeResponse": {
- "type": "object",
- "title": "DeleteTeamForgeResponse",
- "additionalProperties": false
- },
"spin.v1.DeleteTeamIdentityProviderRequest": {
"type": "object",
"properties": {
@@ -12938,6 +14680,11 @@
}
],
"title": "trashed_until"
+ },
+ "agentVolumeId": {
+ "type": "string",
+ "title": "agent_volume_id",
+ "description": "The agent's home it mounts, which its erase keeps for another workspace (ListAgentVolumes);\n empty for a workspace with none."
}
},
"title": "DeletedWorkspace",
@@ -13019,6 +14766,22 @@
"title": "DisableBootstrapAdminResponse",
"additionalProperties": false
},
+ "spin.v1.DisconnectRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ }
+ },
+ "title": "DisconnectRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.DisconnectResponse": {
+ "type": "object",
+ "title": "DisconnectResponse",
+ "additionalProperties": false
+ },
"spin.v1.DotfilesConfig": {
"type": "object",
"properties": {
@@ -13451,6 +15214,66 @@
"A device, a FIFO or a socket."
]
},
+ "spin.v1.FleetClass": {
+ "type": "object",
+ "properties": {
+ "class": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostClass"
+ }
+ ],
+ "title": "class"
+ },
+ "desired": {
+ "type": "integer",
+ "title": "desired",
+ "format": "int32",
+ "description": "What it last asked the group for, and the most it may ask now - the class's own limit, or\n what the installation's leaves it: zero of one is a group at rest."
+ },
+ "most": {
+ "type": "integer",
+ "title": "most",
+ "format": "int32"
+ },
+ "waiting": {
+ "type": "integer",
+ "title": "waiting",
+ "format": "int32",
+ "description": "Workspaces of the class that have nowhere to run yet, and when the first of them started\n waiting: this is what makes the control plane ask for a machine."
+ },
+ "oldestWaiting": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "oldest_waiting"
+ },
+ "busy": {
+ "type": "integer",
+ "title": "busy",
+ "format": "int32",
+ "description": "Workspaces of the class keeping its group up."
+ },
+ "launchFailure": {
+ "type": "string",
+ "title": "launch_failure",
+ "description": "Why the group has fewer machines running than it was asked for, in its cloud's own words -\n \"AWS could not start a runner: \u003cmessage\u003e\" - and when the cloud said it. Empty while it has\n as many, or while the cloud said nothing that explains it."
+ },
+ "launchFailedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "launch_failed_at"
+ }
+ },
+ "title": "FleetClass",
+ "additionalProperties": false,
+ "description": "One host class's group, as the control plane last saw it."
+ },
"spin.v1.FleetHostVersion": {
"type": "object",
"properties": {
@@ -13497,6 +15320,127 @@
"additionalProperties": false,
"description": "FleetHostVersion is one host's halves: the runner binary and the machine under it."
},
+ "spin.v1.ForgeApp": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge",
+ "description": "netpolicy.Forges: \"github\"."
+ },
+ "appId": {
+ "type": "string",
+ "title": "app_id",
+ "format": "int64"
+ },
+ "slug": {
+ "type": "string",
+ "title": "slug"
+ },
+ "clientId": {
+ "type": "string",
+ "title": "client_id"
+ },
+ "apiUrl": {
+ "type": "string",
+ "title": "api_url"
+ },
+ "webUrl": {
+ "type": "string",
+ "title": "web_url"
+ },
+ "hosts": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "hosts",
+ "description": "The exact names a person's token may be sent to."
+ },
+ "installUrl": {
+ "type": "string",
+ "title": "install_url",
+ "description": "Where the app is installed on an account, which is what lets a token reach its repositories."
+ },
+ "deleting": {
+ "type": "boolean",
+ "title": "deleting",
+ "description": "Being deleted: it takes no connection, and goes once what it granted has been revoked."
+ },
+ "updatedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "updated_at"
+ }
+ },
+ "title": "ForgeApp",
+ "additionalProperties": false
+ },
+ "spin.v1.ForgeConnection": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ },
+ "login": {
+ "type": "string",
+ "title": "login",
+ "description": "The account, as the forge names it."
+ },
+ "expiresAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "expires_at",
+ "description": "When the token ends: it is refreshed before then."
+ },
+ "connectedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "connected_at"
+ }
+ },
+ "title": "ForgeConnection",
+ "additionalProperties": false
+ },
+ "spin.v1.ForgeRepository": {
+ "type": "object",
+ "properties": {
+ "fullName": {
+ "type": "string",
+ "title": "full_name",
+ "description": "owner/name."
+ },
+ "cloneUrl": {
+ "type": "string",
+ "title": "clone_url",
+ "description": "What a project's repository is: https, with nothing of a credential in it."
+ },
+ "defaultBranch": {
+ "type": "string",
+ "title": "default_branch"
+ },
+ "private": {
+ "type": "boolean",
+ "title": "private"
+ },
+ "description": {
+ "type": "string",
+ "title": "description"
+ }
+ },
+ "title": "ForgeRepository",
+ "additionalProperties": false
+ },
"spin.v1.GetBillRequest": {
"type": "object",
"properties": {
@@ -13692,55 +15636,19 @@
"autoscaling": {
"type": "boolean",
"title": "autoscaling",
- "description": "False when no group is configured: the hosts of this installation are whatever somebody\n registered by hand, and none of the numbers below mean anything."
- },
- "group": {
- "type": "string",
- "title": "group",
- "description": "The group the control plane sizes, as its cloud names it."
- },
- "desired": {
- "type": "integer",
- "title": "desired",
- "format": "int32",
- "description": "What it last asked the group for, and the most it may ask: zero of one is a fleet at rest,\n which is what an idle installation looks like."
- },
- "most": {
- "type": "integer",
- "title": "most",
- "format": "int32"
+ "description": "False when no fleet is configured: the hosts of this installation are whatever somebody\n registered by hand, and none of the numbers below mean anything."
},
"connected": {
"type": "integer",
"title": "connected",
"format": "int32",
- "description": "The hosts that are connected now, which lags desired by a machine's boot: one desired and\n none connected is a machine on its way."
- },
- "waiting": {
- "type": "integer",
- "title": "waiting",
- "format": "int32",
- "description": "Workspaces that have nowhere to run yet, and when the first of them started waiting: this\n is what makes the control plane ask for a machine."
- },
- "oldestWaiting": {
- "allOf": [
- {
- "$ref": "#/components/schemas/google.protobuf.Timestamp"
- }
- ],
- "title": "oldest_waiting"
- },
- "busy": {
- "type": "integer",
- "title": "busy",
- "format": "int32",
- "description": "Workspaces that are keeping the fleet up."
+ "description": "The hosts that are connected now, of every class, which lags what was asked for by a\n machine's boot."
},
"idleMinutes": {
"type": "integer",
"title": "idle_minutes",
"format": "int32",
- "description": "How long the fleet may be idle before the group is emptied, and the window in which it is\n emptied at once. Empty quiet_hours is none."
+ "description": "How long a class's group may be idle before it is emptied, and the window in which it is\n emptied at once. Empty quiet_hours is none."
},
"quietHours": {
"type": "string",
@@ -13749,6 +15657,20 @@
"timeZone": {
"type": "string",
"title": "time_zone"
+ },
+ "maxHosts": {
+ "type": "integer",
+ "title": "max_hosts",
+ "format": "int32",
+ "description": "The most hosts of every class together; 0 is no limit of the installation's own."
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.FleetClass"
+ },
+ "title": "classes",
+ "description": "Each host class's group, the default first."
}
},
"title": "GetFleetResponse",
@@ -13859,6 +15781,38 @@
"title": "GetHostResponse",
"additionalProperties": false
},
+ "spin.v1.GetHostShellRecordingRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ }
+ },
+ "title": "GetHostShellRecordingRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.GetHostShellRecordingResponse": {
+ "type": "object",
+ "properties": {
+ "sessions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "byte"
+ },
+ "title": "sessions",
+ "description": "Each session's asciicast v2, in the order they were opened."
+ },
+ "truncated": {
+ "type": "boolean",
+ "title": "truncated",
+ "description": "Whether there was more than one answer carries: the rest is in the bucket, under\n host-shells/\u003cid\u003e/."
+ }
+ },
+ "title": "GetHostShellRecordingResponse",
+ "additionalProperties": false
+ },
"spin.v1.GetInstallationSettingsRequest": {
"type": "object",
"title": "GetInstallationSettingsRequest",
@@ -14075,10 +16029,41 @@
"$ref": "#/components/schemas/spin.v1.ExtensionRelease"
},
"title": "agent_releases",
- "description": "Every release of the agent CLIs (Claude Code and Codex, one extension), newest first."
+ "description": "Every release of the agents' programs (Claude Code, Codex and T3 Code, one extension), newest\n first."
+ }
+ },
+ "title": "GetPlatformImagesResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.GetProjectRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ },
+ "slug": {
+ "type": "string",
+ "title": "slug"
+ }
+ },
+ "title": "GetProjectRequest",
+ "additionalProperties": false,
+ "description": "By id, or by slug."
+ },
+ "spin.v1.GetProjectResponse": {
+ "type": "object",
+ "properties": {
+ "project": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.Project"
+ }
+ ],
+ "title": "project"
}
},
- "title": "GetPlatformImagesResponse",
+ "title": "GetProjectResponse",
"additionalProperties": false
},
"spin.v1.GetRegistrationModeRequest": {
@@ -14860,7 +16845,7 @@
"forge": {
"type": "string",
"title": "forge",
- "description": "The forge whose credential for the workspace's team goes in the header, whole: \"github\" is an\n installation token of the team's GitHub App, minted by the control plane (TeamService.SetForge)."
+ "description": "The forge whose credential goes in the header, whole: \"github\" is the workspace owner's own\n token, connected through the installation's OAuth App (ForgeService), which the control plane\n keeps and refreshes."
},
"methods": {
"type": "array",
@@ -15141,6 +17126,58 @@
"additionalProperties": false,
"description": "HostCheck is one thing a host asks itself about, at start and every few minutes: a clock that\n is not synchronised, a KVM it cannot open, a resolver that answers its workspaces nothing - each\n a host that looks healthy to every other signal and fails its workspaces."
},
+ "spin.v1.HostClass": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "title": "name",
+ "description": "Lower-case letters, digits and -, a letter first, up to 31: it is part of the group's name."
+ },
+ "description": {
+ "type": "string",
+ "title": "description"
+ },
+ "instanceTypes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "instance_types",
+ "description": "The instance types it may be, as the cloud names them, patterns allowed (\"c8id.*\"); none is\n any type the rest admits."
+ },
+ "vcpusMin": {
+ "type": "integer",
+ "title": "vcpus_min",
+ "format": "int32"
+ },
+ "vcpusMax": {
+ "type": "integer",
+ "title": "vcpus_max",
+ "format": "int32"
+ },
+ "memoryMibPerVcpu": {
+ "type": "integer",
+ "title": "memory_mib_per_vcpu",
+ "format": "int32",
+ "description": "The least memory a vCPU of it has, in MiB; 0 is 2048."
+ },
+ "spot": {
+ "type": "boolean",
+ "title": "spot",
+ "description": "A machine the cloud may take back (each host drains first), or an on-demand one."
+ },
+ "maxHosts": {
+ "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)."
+ }
+ },
+ "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.HostNetworkPolicy": {
"type": "object",
"properties": {
@@ -15294,6 +17331,130 @@
"additionalProperties": false,
"description": "HostPolicy is what an installation decides about one host. An unset field is the\n installation saying nothing about that setting, which leaves the host on its own default -\n not the same as setting it to nothing."
},
+ "spin.v1.HostShell": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ },
+ "hostId": {
+ "type": "string",
+ "title": "host_id"
+ },
+ "requestedBy": {
+ "type": "string",
+ "title": "requested_by",
+ "description": "Who asked, and who approved: an email, or empty for a person no longer on the installation.\n Approved by nobody where the installation asks for no second administrator."
+ },
+ "approvedBy": {
+ "type": "string",
+ "title": "approved_by"
+ },
+ "reason": {
+ "type": "string",
+ "title": "reason"
+ },
+ "incident": {
+ "type": "boolean",
+ "title": "incident",
+ "description": "An incident's shell opens on a host still running workspaces."
+ },
+ "minutes": {
+ "type": "integer",
+ "title": "minutes",
+ "format": "int32",
+ "description": "How long it is open once it opens."
+ },
+ "state": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostShellState"
+ }
+ ],
+ "title": "state"
+ },
+ "requestedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "requested_at"
+ },
+ "approvedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "approved_at"
+ },
+ "openedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "opened_at"
+ },
+ "closesAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "closes_at"
+ },
+ "endedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "ended_at"
+ },
+ "ended": {
+ "type": "string",
+ "title": "ended",
+ "description": "How it ended, in words."
+ },
+ "hostRetired": {
+ "type": "boolean",
+ "title": "host_retired",
+ "description": "Whether its host has been retired since: revoked, its disks marked to be re-keyed, and given\n back to its group where it has one."
+ },
+ "running": {
+ "type": "integer",
+ "title": "running",
+ "format": "int32",
+ "description": "How many workspaces the host still runs, while it is emptied."
+ }
+ },
+ "title": "HostShell",
+ "additionalProperties": false
+ },
+ "spin.v1.HostShellState": {
+ "type": "string",
+ "title": "HostShellState",
+ "enum": [
+ "HOST_SHELL_STATE_UNSPECIFIED",
+ "HOST_SHELL_STATE_REQUESTED",
+ "HOST_SHELL_STATE_EMPTYING",
+ "HOST_SHELL_STATE_OPEN",
+ "HOST_SHELL_STATE_ENDED",
+ "HOST_SHELL_STATE_REFUSED"
+ ],
+ "description": "- `HOST_SHELL_STATE_REQUESTED`: Asked for, waiting for a second administrator.\n- `HOST_SHELL_STATE_EMPTYING`: Approved: the host is cordoned and its workspaces are being suspended to run elsewhere.\n- `HOST_SHELL_STATE_OPEN`: Open: a terminal may attach until closes_at.\n- `HOST_SHELL_STATE_ENDED`: Over: closed, or past its time. Its host is retired.\n- `HOST_SHELL_STATE_REFUSED`: Refused, or withdrawn before it was approved. Its host was never touched.",
+ "x-enum-descriptions": [
+ "",
+ "Asked for, waiting for a second administrator.",
+ "Approved: the host is cordoned and its workspaces are being suspended to run elsewhere.",
+ "Open: a terminal may attach until closes_at.",
+ "Over: closed, or past its time. Its host is retired.",
+ "Refused, or withdrawn before it was approved. Its host was never touched."
+ ]
+ },
"spin.v1.HostState": {
"type": "string",
"title": "HostState",
@@ -15532,7 +17693,41 @@
"agentsFetch": {
"type": "boolean",
"title": "agents_fetch",
- "description": "Whether the control plane fetches the agent CLIs - Claude Code and Codex - from their\n publishers on its own: the pair it pins, at start, and the newest release of each, looked for\n daily. False, it fetches none, and a workspace is given a pair an operator published; no\n image carries one."
+ "description": "Whether the control plane fetches the agents' programs - Claude Code, Codex and T3 Code -\n from their publishers on its own: the triple it pins, at start, and the newest release of\n each, looked for daily. False, it fetches none, and a workspace is given a triple an operator\n published; no image carries one."
+ },
+ "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"
}
},
"title": "InstallationSettings",
@@ -15734,6 +17929,65 @@
"title": "ListAPIKeysResponse",
"additionalProperties": false
},
+ "spin.v1.ListAgentThreadsRequest": {
+ "type": "object",
+ "properties": {
+ "workspaceId": {
+ "type": "string",
+ "title": "workspace_id"
+ }
+ },
+ "title": "ListAgentThreadsRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListAgentThreadsResponse": {
+ "type": "object",
+ "properties": {
+ "environmentId": {
+ "type": "string",
+ "title": "environment_id",
+ "description": "environment_id is T3 Code's id for the workspace's server: a thread's page in its web client\n is /\u003cenvironment_id\u003e/\u003cthread id\u003e, served at the port `spin agent` forwards."
+ },
+ "threads": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.AgentThread"
+ },
+ "title": "threads",
+ "description": "Newest first."
+ },
+ "reportedAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "reported_at",
+ "description": "reported_at is when the supervisor last sent the index; unset where it never has - an owner\n who does not consent, or a workspace that ran no agent."
+ }
+ },
+ "title": "ListAgentThreadsResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.ListAgentVolumesRequest": {
+ "type": "object",
+ "title": "ListAgentVolumesRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListAgentVolumesResponse": {
+ "type": "object",
+ "properties": {
+ "volumes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.AgentVolume"
+ },
+ "title": "volumes"
+ }
+ },
+ "title": "ListAgentVolumesResponse",
+ "additionalProperties": false
+ },
"spin.v1.ListAllowedEmailsRequest": {
"type": "object",
"properties": {
@@ -15869,64 +18123,147 @@
"title": "ListConfigChangesResponse",
"additionalProperties": false
},
+ "spin.v1.ListConnectionsRequest": {
+ "type": "object",
+ "title": "ListConnectionsRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListConnectionsResponse": {
+ "type": "object",
+ "properties": {
+ "connections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.ForgeConnection"
+ },
+ "title": "connections"
+ }
+ },
+ "title": "ListConnectionsResponse",
+ "additionalProperties": false
+ },
"spin.v1.ListDeletedWorkspacesRequest": {
"type": "object",
"title": "ListDeletedWorkspacesRequest",
"additionalProperties": false
},
- "spin.v1.ListDeletedWorkspacesResponse": {
+ "spin.v1.ListDeletedWorkspacesResponse": {
+ "type": "object",
+ "properties": {
+ "workspaces": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.DeletedWorkspace"
+ },
+ "title": "workspaces"
+ }
+ },
+ "title": "ListDeletedWorkspacesResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.ListExposedPortsRequest": {
+ "type": "object",
+ "properties": {
+ "workspaceId": {
+ "type": "string",
+ "title": "workspace_id"
+ },
+ "page": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.PageRequest"
+ }
+ ],
+ "title": "page"
+ }
+ },
+ "title": "ListExposedPortsRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListExposedPortsResponse": {
+ "type": "object",
+ "properties": {
+ "ports": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.ExposedPort"
+ },
+ "title": "ports"
+ },
+ "page": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.PageResponse"
+ }
+ ],
+ "title": "page"
+ }
+ },
+ "title": "ListExposedPortsResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.ListForgeAppsRequest": {
+ "type": "object",
+ "title": "ListForgeAppsRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListForgeAppsResponse": {
+ "type": "object",
+ "properties": {
+ "apps": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.ForgeApp"
+ },
+ "title": "apps"
+ }
+ },
+ "title": "ListForgeAppsResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.ListHostClassesRequest": {
+ "type": "object",
+ "title": "ListHostClassesRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.ListHostClassesResponse": {
"type": "object",
"properties": {
- "workspaces": {
+ "classes": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/spin.v1.DeletedWorkspace"
+ "$ref": "#/components/schemas/spin.v1.HostClass"
},
- "title": "workspaces"
+ "title": "classes"
}
},
- "title": "ListDeletedWorkspacesResponse",
+ "title": "ListHostClassesResponse",
"additionalProperties": false
},
- "spin.v1.ListExposedPortsRequest": {
+ "spin.v1.ListHostShellsRequest": {
"type": "object",
"properties": {
- "workspaceId": {
+ "hostId": {
"type": "string",
- "title": "workspace_id"
- },
- "page": {
- "allOf": [
- {
- "$ref": "#/components/schemas/spin.v1.PageRequest"
- }
- ],
- "title": "page"
+ "title": "host_id",
+ "description": "One host's; empty for every host's."
}
},
- "title": "ListExposedPortsRequest",
+ "title": "ListHostShellsRequest",
"additionalProperties": false
},
- "spin.v1.ListExposedPortsResponse": {
+ "spin.v1.ListHostShellsResponse": {
"type": "object",
"properties": {
- "ports": {
+ "shells": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/spin.v1.ExposedPort"
+ "$ref": "#/components/schemas/spin.v1.HostShell"
},
- "title": "ports"
- },
- "page": {
- "allOf": [
- {
- "$ref": "#/components/schemas/spin.v1.PageResponse"
- }
- ],
- "title": "page"
+ "title": "shells"
}
},
- "title": "ListExposedPortsResponse",
+ "title": "ListHostShellsResponse",
"additionalProperties": false
},
"spin.v1.ListHostsRequest": {
@@ -16128,6 +18465,20 @@
"$ref": "#/components/schemas/spin.v1.PendingSignature"
},
"title": "pending"
+ },
+ "keyringSigned": {
+ "type": "boolean",
+ "title": "keyring_signed",
+ "description": "keyring_signed is whether the caller has signed a keyring at all. Until they have, no\n workspace is made for them (CreateWorkspace refuses it): its access is signed with a key of\n the keyring. Always true on an installation with no bucket, which keeps no keyrings."
+ },
+ "userId": {
+ "type": "string",
+ "title": "user_id",
+ "description": "user_id is the caller, and keyring their keyring by the version it began at (empty until they\n signed one): who a grant the browser terminal signs for them names. A control plane that says\n another only makes a grant no host admits."
+ },
+ "keyring": {
+ "type": "string",
+ "title": "keyring"
}
},
"title": "ListPendingSignaturesResponse",
@@ -16161,12 +18512,6 @@
},
"spin.v1.ListProjectsRequest": {
"type": "object",
- "properties": {
- "teamId": {
- "type": "string",
- "title": "team_id"
- }
- },
"title": "ListProjectsRequest",
"additionalProperties": false
},
@@ -16464,31 +18809,6 @@
"title": "ListSnapshotsResponse",
"additionalProperties": false
},
- "spin.v1.ListTeamForgesRequest": {
- "type": "object",
- "properties": {
- "teamId": {
- "type": "string",
- "title": "team_id"
- }
- },
- "title": "ListTeamForgesRequest",
- "additionalProperties": false
- },
- "spin.v1.ListTeamForgesResponse": {
- "type": "object",
- "properties": {
- "forges": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/spin.v1.TeamForge"
- },
- "title": "forges"
- }
- },
- "title": "ListTeamForgesResponse",
- "additionalProperties": false
- },
"spin.v1.ListTeamIdentityProvidersRequest": {
"type": "object",
"properties": {
@@ -17433,6 +19753,11 @@
"type": "string",
"title": "summary",
"description": "summary is a line saying what it changes, for a list: the document itself is what to read."
+ },
+ "deferred": {
+ "type": "boolean",
+ "title": "deferred",
+ "description": "deferred is a document nothing asks for until the person uses what it gives: the roster of a\n team that has only ever been its one member, read only for an identity. Offered there, not on\n every page."
}
},
"title": "PendingSignature",
@@ -17486,13 +19811,15 @@
"enum": [
"PLATFORM_IMAGE_ARTIFACT_UNSPECIFIED",
"PLATFORM_IMAGE_ARTIFACT_BASE",
- "PLATFORM_IMAGE_ARTIFACT_SUPERVISOR"
+ "PLATFORM_IMAGE_ARTIFACT_SUPERVISOR",
+ "PLATFORM_IMAGE_ARTIFACT_AGENT_HOME"
],
- "description": "- `PLATFORM_IMAGE_ARTIFACT_BASE`: The machine's base image, every workspace's disk's floor.\n- `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`: The supervisor extension every workspace is handed.",
+ "description": "- `PLATFORM_IMAGE_ARTIFACT_BASE`: The machine's base image, every workspace's disk's floor.\n- `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`: The supervisor extension every workspace is handed.\n- `PLATFORM_IMAGE_ARTIFACT_AGENT_HOME`: The empty home every agent's volume is cloned from.",
"x-enum-descriptions": [
"",
"The machine's base image, every workspace's disk's floor.",
- "The supervisor extension every workspace is handed."
+ "The supervisor extension every workspace is handed.",
+ "The empty home every agent's volume is cloned from."
]
},
"spin.v1.PlatformImageState": {
@@ -17575,6 +19902,38 @@
"title": "PlatformLogFieldValuesResponse",
"additionalProperties": false
},
+ "spin.v1.PollDeviceConnectRequest": {
+ "type": "object",
+ "properties": {
+ "handle": {
+ "type": "string",
+ "title": "handle"
+ }
+ },
+ "title": "PollDeviceConnectRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.PollDeviceConnectResponse": {
+ "type": "object",
+ "properties": {
+ "connection": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.ForgeConnection"
+ }
+ ],
+ "title": "connection",
+ "description": "Set once the person approved: the connection, kept. Unset is pending: poll again after\n interval_seconds, which the forge may have lengthened."
+ },
+ "intervalSeconds": {
+ "type": "integer",
+ "title": "interval_seconds",
+ "format": "int32"
+ }
+ },
+ "title": "PollDeviceConnectResponse",
+ "additionalProperties": false
+ },
"spin.v1.PortAccess": {
"type": "string",
"title": "PortAccess",
@@ -17740,10 +20099,6 @@
"type": "string",
"title": "id"
},
- "teamId": {
- "type": "string",
- "title": "team_id"
- },
"name": {
"type": "string",
"title": "name"
@@ -17751,7 +20106,7 @@
"slug": {
"type": "string",
"title": "slug",
- "description": "What a person names it by in the team: teams.slug's shape"
+ "description": "What a person names it by in the installation: teams.slug's shape"
},
"description": {
"type": "string",
@@ -17823,7 +20178,39 @@
"prebuildOwnerId": {
"type": "string",
"title": "prebuild_owner_id",
- "description": "Who the builds run as, for what they use: the admin who last named the refs."
+ "description": "Who the builds run as, for what they use: the project admin who last named the refs."
+ },
+ "sharedWithEveryone": {
+ "type": "boolean",
+ "title": "shared_with_everyone",
+ "description": "Seen by everybody signed in; otherwise by its admins and shares."
+ },
+ "administers": {
+ "type": "boolean",
+ "title": "administers",
+ "description": "Whether the caller may change, share and delete it."
+ },
+ "adminIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "admin_ids",
+ "description": "Its admins and whom it is shared with: shown to its admins, empty to everybody else."
+ },
+ "sharedUserIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "shared_user_ids"
+ },
+ "sharedTeamIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "shared_team_ids"
}
},
"title": "Project",
@@ -18344,6 +20731,48 @@
"additionalProperties": false,
"description": "The repository a workspace opened, as its guest clones it: the project's URL, the ref the\n workspace was opened at, and where on the disk it goes (projects.CheckoutPath). Part of the\n settings the owner approves, as the environment is: a disk is written to only as its owner said."
},
+ "spin.v1.RequestHostShellRequest": {
+ "type": "object",
+ "properties": {
+ "hostId": {
+ "type": "string",
+ "title": "host_id"
+ },
+ "reason": {
+ "type": "string",
+ "title": "reason",
+ "description": "Why: said to whoever approves it, and kept."
+ },
+ "minutes": {
+ "type": "integer",
+ "title": "minutes",
+ "format": "int32",
+ "description": "How long it is open once it opens, up to the installation's host_shell_max_minutes."
+ },
+ "incident": {
+ "type": "boolean",
+ "title": "incident",
+ "description": "Opens on the host as it is, its workspaces running: for a host that has to be looked at busy."
+ }
+ },
+ "title": "RequestHostShellRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.RequestHostShellResponse": {
+ "type": "object",
+ "properties": {
+ "shell": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostShell"
+ }
+ ],
+ "title": "shell"
+ }
+ },
+ "title": "RequestHostShellResponse",
+ "additionalProperties": false
+ },
"spin.v1.ResolverLookup": {
"type": "object",
"properties": {
@@ -18465,6 +20894,11 @@
"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)."
+ },
+ "hostClass": {
+ "type": "string",
+ "title": "host_class",
+ "description": "The host class its workspaces run on (HostClass)."
}
},
"title": "ResourcePreset",
@@ -18822,6 +21256,36 @@
"title": "SearchPlatformLogsRequest",
"additionalProperties": false
},
+ "spin.v1.SearchRepositoriesRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ },
+ "query": {
+ "type": "string",
+ "title": "query",
+ "description": "Empty lists the account's repositories, most recently pushed first."
+ }
+ },
+ "title": "SearchRepositoriesRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.SearchRepositoriesResponse": {
+ "type": "object",
+ "properties": {
+ "repositories": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/spin.v1.ForgeRepository"
+ },
+ "title": "repositories"
+ }
+ },
+ "title": "SearchRepositoriesResponse",
+ "additionalProperties": false
+ },
"spin.v1.SearchTracesRequest": {
"type": "object",
"properties": {
@@ -19161,6 +21625,62 @@
"title": "ServiceInterval",
"additionalProperties": false
},
+ "spin.v1.SetForgeAppRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ },
+ "appId": {
+ "type": "string",
+ "title": "app_id",
+ "format": "int64"
+ },
+ "slug": {
+ "type": "string",
+ "title": "slug"
+ },
+ "clientId": {
+ "type": "string",
+ "title": "client_id"
+ },
+ "clientSecret": {
+ "type": "string",
+ "title": "client_secret"
+ },
+ "webhookSecret": {
+ "type": "string",
+ "title": "webhook_secret"
+ },
+ "apiUrl": {
+ "type": "string",
+ "title": "api_url",
+ "description": "As StartAppCreationRequest's."
+ },
+ "webUrl": {
+ "type": "string",
+ "title": "web_url"
+ }
+ },
+ "title": "SetForgeAppRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.SetForgeAppResponse": {
+ "type": "object",
+ "properties": {
+ "app": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.ForgeApp"
+ }
+ ],
+ "title": "app"
+ }
+ },
+ "title": "SetForgeAppResponse",
+ "additionalProperties": false
+ },
"spin.v1.SetHostPolicyRequest": {
"type": "object",
"properties": {
@@ -19287,83 +21807,33 @@
},
"spin.v1.SetSnapshotVisibilityRequest": {
"type": "object",
- "properties": {
- "snapshotId": {
- "type": "string",
- "title": "snapshot_id"
- },
- "public": {
- "type": "boolean",
- "title": "public"
- },
- "acknowledgeFindings": {
- "type": "boolean",
- "title": "acknowledge_findings",
- "description": "As ShareSnapshotRequest's: making a snapshot public gives everybody a copy of its files."
- }
- },
- "title": "SetSnapshotVisibilityRequest",
- "additionalProperties": false
- },
- "spin.v1.SetSnapshotVisibilityResponse": {
- "type": "object",
- "properties": {
- "public": {
- "type": "boolean",
- "title": "public"
- }
- },
- "title": "SetSnapshotVisibilityResponse",
- "additionalProperties": false
- },
- "spin.v1.SetTeamForgeRequest": {
- "type": "object",
- "properties": {
- "teamId": {
- "type": "string",
- "title": "team_id"
- },
- "forge": {
- "type": "string",
- "title": "forge",
- "description": "netpolicy.Forges: \"github\"."
- },
- "appId": {
- "type": "string",
- "title": "app_id",
- "format": "int64",
- "description": "The GitHub App's id, and its installation on the organization the team's repositories are in."
- },
- "installationId": {
- "type": "string",
- "title": "installation_id",
- "format": "int64"
- },
- "privateKey": {
+ "properties": {
+ "snapshotId": {
"type": "string",
- "title": "private_key",
- "description": "The app's private key, PEM: what signs the request for an installation token. Replaces what\n the team held."
+ "title": "snapshot_id"
},
- "apiUrl": {
- "type": "string",
- "title": "api_url",
- "description": "Where the forge's API is, https; empty is github.com's. A GitHub Enterprise Server's is\n https://\u003chost\u003e/api/v3."
+ "public": {
+ "type": "boolean",
+ "title": "public"
},
- "hosts": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "title": "hosts",
- "description": "The exact names the credential may be sent to, 1 to 16; empty is github.com and\n api.github.com. A brokered rule that injects the forge and names any other host is refused it."
+ "acknowledgeFindings": {
+ "type": "boolean",
+ "title": "acknowledge_findings",
+ "description": "As ShareSnapshotRequest's: making a snapshot public gives everybody a copy of its files."
}
},
- "title": "SetTeamForgeRequest",
+ "title": "SetSnapshotVisibilityRequest",
"additionalProperties": false
},
- "spin.v1.SetTeamForgeResponse": {
+ "spin.v1.SetSnapshotVisibilityResponse": {
"type": "object",
- "title": "SetTeamForgeResponse",
+ "properties": {
+ "public": {
+ "type": "boolean",
+ "title": "public"
+ }
+ },
+ "title": "SetSnapshotVisibilityResponse",
"additionalProperties": false
},
"spin.v1.SetTeamNetworkPolicyRequest": {
@@ -19433,6 +21903,39 @@
"title": "SetTeamSecretResponse",
"additionalProperties": false
},
+ "spin.v1.SetWorkspaceBandwidthRequest": {
+ "type": "object",
+ "properties": {
+ "workspaceId": {
+ "type": "string",
+ "title": "workspace_id"
+ },
+ "bandwidthMbps": {
+ "type": "integer",
+ "title": "bandwidth_mbps",
+ "format": "int32"
+ }
+ },
+ "title": "SetWorkspaceBandwidthRequest",
+ "additionalProperties": false,
+ "description": "SetWorkspaceBandwidthRequest is the whole of what the workspace carries from now on, not a\n change to it: bandwidth_mbps in megabits a second each way, and zero is unshaped."
+ },
+ "spin.v1.SetWorkspaceBandwidthResponse": {
+ "type": "object",
+ "properties": {
+ "workspace": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.Workspace"
+ }
+ ],
+ "title": "workspace"
+ }
+ },
+ "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."
+ },
"spin.v1.SetWorkspaceNetworkTagsRequest": {
"type": "object",
"properties": {
@@ -19503,6 +22006,58 @@
"title": "SetWorkspaceNetworkWatchResponse",
"additionalProperties": false
},
+ "spin.v1.ShareProjectRequest": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "title": "id"
+ },
+ "sharedWithEveryone": {
+ "type": "boolean",
+ "title": "shared_with_everyone"
+ },
+ "userIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "user_ids"
+ },
+ "teamIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "team_ids"
+ },
+ "adminIds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "title": "admin_ids"
+ }
+ },
+ "title": "ShareProjectRequest",
+ "additionalProperties": false,
+ "description": "Who sees the project and who administers it, whole. The admins are at least one person, and\n always include the prebuild owner while the project prebuilds."
+ },
+ "spin.v1.ShareProjectResponse": {
+ "type": "object",
+ "properties": {
+ "project": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.Project"
+ }
+ ],
+ "title": "project"
+ }
+ },
+ "title": "ShareProjectResponse",
+ "additionalProperties": false
+ },
"spin.v1.ShareSnapshotRequest": {
"type": "object",
"properties": {
@@ -19774,6 +22329,139 @@
"additionalProperties": false,
"description": "Span is one operation of a trace, as OpenTelemetry names its parts."
},
+ "spin.v1.StartAppCreationRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ },
+ "organization": {
+ "type": "string",
+ "title": "organization",
+ "description": "The organization the app belongs to; empty is the admin's own account."
+ },
+ "apiUrl": {
+ "type": "string",
+ "title": "api_url",
+ "description": "Empty is github.com's; a GitHub Enterprise Server's API is https://\u003chost\u003e/api/v3, and its web\n origin https://\u003chost\u003e."
+ },
+ "webUrl": {
+ "type": "string",
+ "title": "web_url"
+ },
+ "public": {
+ "type": "boolean",
+ "title": "public",
+ "description": "Whether any account can install it. A private app installs only on the account that owns it,\n so only that account's repositories are reached."
+ }
+ },
+ "title": "StartAppCreationRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.StartAppCreationResponse": {
+ "type": "object",
+ "properties": {
+ "formUrl": {
+ "type": "string",
+ "title": "form_url",
+ "description": "Where to send the browser: a page of this control plane's that posts the manifest to the forge,\n because the dashboard's own policy lets it post nowhere else."
+ },
+ "state": {
+ "type": "string",
+ "title": "state"
+ },
+ "binding": {
+ "type": "string",
+ "title": "binding",
+ "description": "A secret for the client that began this, kept where only it reads it and never in a URL:\n CompleteAppCreation requires it with the state."
+ }
+ },
+ "title": "StartAppCreationResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.StartConnectRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ },
+ "redirectUri": {
+ "type": "string",
+ "title": "redirect_uri",
+ "description": "The dashboard's /git/callback, over https; anything else is refused. Empty is the\n installation's own."
+ }
+ },
+ "title": "StartConnectRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.StartConnectResponse": {
+ "type": "object",
+ "properties": {
+ "authorizeUrl": {
+ "type": "string",
+ "title": "authorize_url"
+ },
+ "state": {
+ "type": "string",
+ "title": "state"
+ },
+ "binding": {
+ "type": "string",
+ "title": "binding",
+ "description": "A secret for the client that began this, kept where only it reads it and never in a URL:\n CompleteConnect requires it with the state."
+ }
+ },
+ "title": "StartConnectResponse",
+ "additionalProperties": false
+ },
+ "spin.v1.StartDeviceConnectRequest": {
+ "type": "object",
+ "properties": {
+ "forge": {
+ "type": "string",
+ "title": "forge"
+ }
+ },
+ "title": "StartDeviceConnectRequest",
+ "additionalProperties": false
+ },
+ "spin.v1.StartDeviceConnectResponse": {
+ "type": "object",
+ "properties": {
+ "handle": {
+ "type": "string",
+ "title": "handle",
+ "description": "What PollDeviceConnect is asked with: a secret of the caller's, polled by them alone. The\n forge's device code stays on the control plane."
+ },
+ "userCode": {
+ "type": "string",
+ "title": "user_code",
+ "description": "What the person types at verification_uri, signed in to the forge."
+ },
+ "verificationUri": {
+ "type": "string",
+ "title": "verification_uri"
+ },
+ "expiresAt": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/google.protobuf.Timestamp"
+ }
+ ],
+ "title": "expires_at"
+ },
+ "intervalSeconds": {
+ "type": "integer",
+ "title": "interval_seconds",
+ "format": "int32",
+ "description": "How long to wait before each poll."
+ }
+ },
+ "title": "StartDeviceConnectResponse",
+ "additionalProperties": false
+ },
"spin.v1.StartDeviceLoginRequest": {
"type": "object",
"properties": {
@@ -20324,46 +23012,6 @@
"title": "Team",
"additionalProperties": false
},
- "spin.v1.TeamForge": {
- "type": "object",
- "properties": {
- "forge": {
- "type": "string",
- "title": "forge"
- },
- "appId": {
- "type": "string",
- "title": "app_id",
- "format": "int64"
- },
- "installationId": {
- "type": "string",
- "title": "installation_id",
- "format": "int64"
- },
- "apiUrl": {
- "type": "string",
- "title": "api_url"
- },
- "hosts": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "title": "hosts"
- },
- "updatedAt": {
- "allOf": [
- {
- "$ref": "#/components/schemas/google.protobuf.Timestamp"
- }
- ],
- "title": "updated_at"
- }
- },
- "title": "TeamForge",
- "additionalProperties": false
- },
"spin.v1.TeamIdentityProvider": {
"type": "object",
"properties": {
@@ -20632,6 +23280,37 @@
"title": "UntagImageResponse",
"additionalProperties": false
},
+ "spin.v1.UpdateHostClassRequest": {
+ "type": "object",
+ "properties": {
+ "class": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostClass"
+ }
+ ],
+ "title": "class"
+ }
+ },
+ "title": "UpdateHostClassRequest",
+ "additionalProperties": false,
+ "description": "The class named class.name, made what class says. Its group is held to it at the autoscaler's\n next pass; machines it started already keep their shape until they go."
+ },
+ "spin.v1.UpdateHostClassResponse": {
+ "type": "object",
+ "properties": {
+ "class": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/spin.v1.HostClass"
+ }
+ ],
+ "title": "class"
+ }
+ },
+ "title": "UpdateHostClassResponse",
+ "additionalProperties": false
+ },
"spin.v1.UpdateInstallationSettingsRequest": {
"type": "object",
"properties": {
@@ -20856,6 +23535,11 @@
"title": "rpo_seconds",
"format": "int32",
"description": "Optional: a new RPO. Present, since zero - the installation's - is an answer and not \"keep\"."
+ },
+ "hostClass": {
+ "type": "string",
+ "title": "host_class",
+ "description": "Optional: a new host class; empty keeps it. Its workspaces made before keep theirs."
}
},
"title": "UpdateResourcePresetRequest",
@@ -22510,13 +25194,25 @@
"name": "AdminService",
"description": "AdminService manages installation-wide settings, user management, and resource presets\n Only accessible to instance administrators --- Installation Settings ---"
},
+ {
+ "name": "ForgeService",
+ "description": "ForgeService is a person's account on a forge, connected through the installation's GitHub App\n (spin#758): the control plane keeps the token, lists the person's repositories with it, and a\n host's egress proxy sets it on their workspaces' brokered requests. No workspace holds it."
+ },
{
"name": "HostService"
},
+ {
+ "name": "HostShellService",
+ "description": "HostShellService is a root shell on a host, asked for by an administrator, for a while, with a\n reason (spin#712). The host is emptied of its workspaces first - unless the request is an\n incident's, which says so - and retired after, whatever happened in it: once a person has had root\n on a host it runs no workspace again. Every session is recorded by the control plane, which\n carries it; the terminal itself is a WebSocket at /api/host-shells/\u003cid\u003e/terminal\n (internal/hostshell)."
+ },
{
"name": "LogService",
"description": "LogService is what the installation logged, as its store of logs and traces keeps it\n (internal/logstore). Every search is confined by the control plane to what the caller may see;\n what the caller types is searched inside that and cannot widen it."
},
+ {
+ "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."
+ },
{
"name": "RunnerTokenService"
},
diff --git a/src/assets/shots/snapshots.png b/src/assets/shots/snapshots.png
new file mode 100644
index 0000000..e8f8846
Binary files /dev/null and b/src/assets/shots/snapshots.png differ
diff --git a/src/assets/shots/workspace.png b/src/assets/shots/workspace.png
index 4bb29bd..f074a29 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 f2c34ef..4132580 100644
Binary files a/src/assets/shots/workspaces.png and b/src/assets/shots/workspaces.png differ
diff --git a/src/content/docs/admin/hosts.mdx b/src/content/docs/admin/hosts.mdx
index 487d5db..8d0595c 100644
--- a/src/content/docs/admin/hosts.mdx
+++ b/src/content/docs/admin/hosts.mdx
@@ -86,5 +86,5 @@ A token is one-time unless `--reusable`, lasts 24 hours unless `--expires` says
most 30 days), and is shown once. `--team` makes a runner that only takes the team's workspaces.
The same flags set what the runner advertises and whether it is preemptible.
-Such a machine needs KVM, and must run a release of Spinbox no newer than the control plane's and no
-more than older.
+Such a machine needs KVM, and must run the control plane's release of Spinbox: a host of any other is
+refused, and the refusal says so.
diff --git a/src/content/docs/concepts/runners.mdx b/src/content/docs/concepts/runners.mdx
index 7038a13..8012e80 100644
--- a/src/content/docs/concepts/runners.mdx
+++ b/src/content/docs/concepts/runners.mdx
@@ -30,8 +30,8 @@ the answer names the runners' role. There is no token to create, store or rotate
A machine outside the group can join with a one-time registration token from Admin → Runners.
-A runner must be of the same release as the control plane or older, and no more than
-older. A newer one is refused: it may speak of things the control plane does not know.
+A runner must run the control plane's release. One of any other, older or newer, is refused when it
+registers, and the refusal says which release to run.
## Placement
diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx
index bdbeea5..3513a30 100644
--- a/src/content/docs/index.mdx
+++ b/src/content/docs/index.mdx
@@ -44,7 +44,7 @@ Egress is closed by default, and policies name what a workspace may reach. A tea
## And
-- **Built for agents.** [Claude Code and Codex on every workspace's PATH](), API keys with scopes, JSON output.
+- **Built for agents.** [Claude Code and Codex on every workspace's PATH](), API keys with scopes, JSON output.
- **Secrets the workspace never holds.** [A brokered rule]() adds the team's secret on the way out, one for the person and one for the agent.
- **Moves while it runs.** [A running workspace goes to another host]() with its processes as they are.
- **Asleep when idle, awake as it was.** [A workspace nobody uses is suspended](), after asking first, and a start resumes it.
diff --git a/src/content/docs/reference/api/adminservice.md b/src/content/docs/reference/api/adminservice.md
index 4bcd2bc..4f06ca3 100644
--- a/src/content/docs/reference/api/adminservice.md
+++ b/src/content/docs/reference/api/adminservice.md
@@ -31,6 +31,22 @@ Add allowed email/domain
|---|---|---|
| `entry` | [AllowedEmail](/reference/api/types/#allowedemail) | |
+## CreateHostClass
+
+`POST /spin.v1.AdminService/CreateHostClass`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `class` | [HostClass](/reference/api/types/#hostclass) | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `class` | [HostClass](/reference/api/types/#hostclass) | |
+
## CreateNetworkPolicy
`POST /spin.v1.AdminService/CreateNetworkPolicy`
@@ -66,8 +82,9 @@ Create a resource preset
| `sortOrder` | integer (int32) | Optional: display order |
| `burstFloorCpuCores` | integer (int32) | Required for a burst mode: what its workspaces start with, at most the preset's size. |
| `burstFloorMemoryMb` | integer (int32) | |
-| `bandwidthMbps` | integer (int32) | What a workspace of it may carry each way, in megabits a second; 0 is unshaped. |
+| `bandwidthMbps` | integer or null (int32) | What a workspace of it may carry each way, in megabits a second. Present, since 0 - unshaped - is an answer; absent, the installation's default. |
| `rpoSeconds` | integer (int32) | How far behind the object store a workspace of it may fall, in seconds; 0 is the installation's. |
+| `hostClass` | string | The host class its workspaces run on; empty is the default. |
**Response**
@@ -94,6 +111,20 @@ Apply a change the file proposes, or dismiss it. Either is remembered until the
No fields: `{}`.
+## DeleteHostClass
+
+`POST /spin.v1.AdminService/DeleteHostClass`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `name` | string | |
+
+**Response**
+
+No fields: `{}`.
+
## DeleteNetworkPolicy
`POST /spin.v1.AdminService/DeleteNetworkPolicy`
@@ -262,7 +293,7 @@ No fields: `{}`.
| `images` | list of [PlatformImage](/reference/api/types/#platformimage) | Empty until the object store is configured: there is nowhere to publish to. |
| `baseImages` | list of [BaseImage](/reference/api/types/#baseimage) | Every base image published, one per machine release, newest first. |
| `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 agent CLIs (Claude Code and Codex, one extension), 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
@@ -430,6 +461,20 @@ No fields: `{}`.
|---|---|---|
| `changes` | list of [ConfigChange](/reference/api/types/#configchange) | |
+## ListHostClasses
+
+`POST /spin.v1.AdminService/ListHostClasses`
+
+**Request**
+
+No fields: `{}`.
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `classes` | list of [HostClass](/reference/api/types/#hostclass) | |
+
## ListJobRuns
`POST /spin.v1.AdminService/ListJobRuns`
@@ -690,6 +735,22 @@ Take a ref off what it names: no new workspace gets those bytes by it, the disks
No fields: `{}`.
+## UpdateHostClass
+
+`POST /spin.v1.AdminService/UpdateHostClass`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `class` | [HostClass](/reference/api/types/#hostclass) | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `class` | [HostClass](/reference/api/types/#hostclass) | |
+
## UpdateInstallationSettings
`POST /spin.v1.AdminService/UpdateInstallationSettings`
@@ -747,6 +808,7 @@ Update a resource preset
| `burstFloorMemoryMb` | integer (int32) | Optional: new burst floor |
| `bandwidthMbps` | integer or null (int32) | Optional: a new bandwidth. Present, since zero - unshaped - is an answer and not "keep". |
| `rpoSeconds` | integer or null (int32) | Optional: a new RPO. Present, since zero - the installation's - is an answer and not "keep". |
+| `hostClass` | string | Optional: a new host class; empty keeps it. Its workspaces made before keep theirs. |
**Response**
diff --git a/src/content/docs/reference/api/forgeservice.md b/src/content/docs/reference/api/forgeservice.md
new file mode 100644
index 0000000..1b01133
--- /dev/null
+++ b/src/content/docs/reference/api/forgeservice.md
@@ -0,0 +1,239 @@
+---
+title: "ForgeService"
+description: "spin.v1.ForgeService: each procedure, its request and its response."
+source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
+editUrl: false
+sidebar:
+ order: 3
+---
+
+
+
+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.
+
+## CompleteAppCreation
+
+`POST /spin.v1.ForgeService/CompleteAppCreation`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `code` | string | |
+| `state` | string | |
+| `binding` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `app` | [ForgeApp](/reference/api/types/#forgeapp) | |
+
+## CompleteConnect
+
+`POST /spin.v1.ForgeService/CompleteConnect`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `code` | string | |
+| `state` | string | |
+| `binding` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `connection` | [ForgeConnection](/reference/api/types/#forgeconnection) | |
+
+## DeleteForgeApp
+
+`POST /spin.v1.ForgeService/DeleteForgeApp`
+
+Takes the app out of use at once. Every connection through it is revoked at the forge, and the app is gone when that is done.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+
+**Response**
+
+No fields: `{}`.
+
+## Disconnect
+
+`POST /spin.v1.ForgeService/Disconnect`
+
+Forgets the token and revokes it at the forge.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+
+**Response**
+
+No fields: `{}`.
+
+## ListConnections
+
+`POST /spin.v1.ForgeService/ListConnections`
+
+**Request**
+
+No fields: `{}`.
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `connections` | list of [ForgeConnection](/reference/api/types/#forgeconnection) | |
+
+## ListForgeApps
+
+`POST /spin.v1.ForgeService/ListForgeApps`
+
+The installation's app on each forge: whether a person can connect, and where. Its secrets are never answered with.
+
+**Request**
+
+No fields: `{}`.
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `apps` | list of [ForgeApp](/reference/api/types/#forgeapp) | |
+
+## PollDeviceConnect
+
+`POST /spin.v1.ForgeService/PollDeviceConnect`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `handle` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `connection` | [ForgeConnection](/reference/api/types/#forgeconnection) | Set once the person approved: the connection, kept. Unset is pending: poll again after interval_seconds, which the forge may have lengthened. |
+| `intervalSeconds` | integer (int32) | |
+
+## SearchRepositories
+
+`POST /spin.v1.ForgeService/SearchRepositories`
+
+The repositories the person's account reaches that match query, as the forge searches them with the person's token: what they can see there is what they can pick here.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+| `query` | string | Empty lists the account's repositories, most recently pushed first. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `repositories` | list of [ForgeRepository](/reference/api/types/#forgerepository) | |
+
+## SetForgeApp
+
+`POST /spin.v1.ForgeService/SetForgeApp`
+
+Keeping an app that was made elsewhere. Refused while the forge has one: an app is never edited, because another client id could not revoke what this one granted.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+| `appId` | string (int64) | |
+| `slug` | string | |
+| `clientId` | string | |
+| `clientSecret` | string | |
+| `webhookSecret` | string | |
+| `apiUrl` | string | As StartAppCreationRequest's. |
+| `webUrl` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `app` | [ForgeApp](/reference/api/types/#forgeapp) | |
+
+## StartAppCreation
+
+`POST /spin.v1.ForgeService/StartAppCreation`
+
+Making the app, by an instance admin, through the forge's manifest flow: StartAppCreation answers the form the browser posts to the forge, which makes the app and sends the admin back with a code; CompleteAppCreation exchanges it for the app's credentials. Nobody copies a key.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+| `organization` | string | The organization the app belongs to; empty is the admin's own account. |
+| `apiUrl` | string | Empty is github.com's; a GitHub Enterprise Server's API is https://<host>/api/v3, and its web origin https://<host>. |
+| `webUrl` | string | |
+| `public` | boolean | Whether any account can install it. A private app installs only on the account that owns it, so only that account's repositories are reached. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `formUrl` | string | Where to send the browser: a page of this control plane's that posts the manifest to the forge, because the dashboard's own policy lets it post nowhere else. |
+| `state` | string | |
+| `binding` | string | A secret for the client that began this, kept where only it reads it and never in a URL: CompleteAppCreation requires it with the state. |
+
+## StartConnect
+
+`POST /spin.v1.ForgeService/StartConnect`
+
+Connecting: StartConnect answers where the person authorizes the app and a binding only this client keeps; the forge sends the browser back to redirect_uri with a code and the state, and CompleteConnect exchanges them, with the binding, for the person's token.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+| `redirectUri` | string | The dashboard's /git/callback, over https; anything else is refused. Empty is the installation's own. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `authorizeUrl` | string | |
+| `state` | string | |
+| `binding` | string | A secret for the client that began this, kept where only it reads it and never in a URL: CompleteConnect requires it with the state. |
+
+## StartDeviceConnect
+
+`POST /spin.v1.ForgeService/StartDeviceConnect`
+
+Connecting from somewhere with no browser to send back to, the CLI: the control plane asks the forge for a device code (RFC 8628) and keeps it, and answers with the code the person types on the forge's page and a handle only the caller can poll with. PollDeviceConnect asks the forge, at most once an interval, and on the person's approval keeps their token as CompleteConnect does: once, for the person who began it.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `handle` | string | What PollDeviceConnect is asked with: a secret of the caller's, polled by them alone. The forge's device code stays on the control plane. |
+| `userCode` | string | What the person types at verification_uri, signed in to the forge. |
+| `verificationUri` | string | |
+| `expiresAt` | `Timestamp` | |
+| `intervalSeconds` | integer (int32) | How long to wait before each poll. |
+
diff --git a/src/content/docs/reference/api/hostservice.md b/src/content/docs/reference/api/hostservice.md
index 5114b42..f8b9e31 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: 3
+ order: 4
---
@@ -63,17 +63,13 @@ No fields: `{}`.
| Field | Type | What it is |
|---|---|---|
-| `autoscaling` | boolean | False when no group is configured: the hosts of this installation are whatever somebody registered by hand, and none of the numbers below mean anything. |
-| `group` | string | The group the control plane sizes, as its cloud names it. |
-| `desired` | integer (int32) | What it last asked the group for, and the most it may ask: zero of one is a fleet at rest, which is what an idle installation looks like. |
-| `most` | integer (int32) | |
-| `connected` | integer (int32) | The hosts that are connected now, which lags desired by a machine's boot: one desired and none connected is a machine on its way. |
-| `waiting` | integer (int32) | Workspaces that have nowhere to run yet, and when the first of them started waiting: this is what makes the control plane ask for a machine. |
-| `oldestWaiting` | `Timestamp` | |
-| `busy` | integer (int32) | Workspaces that are keeping the fleet up. |
-| `idleMinutes` | integer (int32) | How long the fleet may be idle before the group is emptied, and the window in which it is emptied at once. Empty quiet_hours is none. |
+| `autoscaling` | boolean | False when no fleet is configured: the hosts of this installation are whatever somebody registered by hand, and none of the numbers below mean anything. |
+| `connected` | integer (int32) | The hosts that are connected now, of every class, which lags what was asked for by a machine's boot. |
+| `idleMinutes` | integer (int32) | How long a class's group may be idle before it is emptied, and the window in which it is emptied at once. Empty quiet_hours is none. |
| `quietHours` | string | |
| `timeZone` | string | |
+| `maxHosts` | integer (int32) | The most hosts of every class together; 0 is no limit of the installation's own. |
+| `classes` | list of [FleetClass](/reference/api/types/#fleetclass) | Each host class's group, the default first. |
## GetHost
diff --git a/src/content/docs/reference/api/hostshellservice.md b/src/content/docs/reference/api/hostshellservice.md
new file mode 100644
index 0000000..8aa35ab
--- /dev/null
+++ b/src/content/docs/reference/api/hostshellservice.md
@@ -0,0 +1,107 @@
+---
+title: "HostShellService"
+description: "spin.v1.HostShellService: each procedure, its request and its response."
+source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
+editUrl: false
+sidebar:
+ order: 5
+---
+
+
+
+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).
+
+## ApproveHostShell
+
+`POST /spin.v1.HostShellService/ApproveHostShell`
+
+Approves a shell another administrator asked for.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `shell` | [HostShell](/reference/api/types/#hostshell) | |
+
+## CloseHostShell
+
+`POST /spin.v1.HostShellService/CloseHostShell`
+
+Ends a shell: one not yet approved is refused, any other is ended and its host retired.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `shell` | [HostShell](/reference/api/types/#hostshell) | |
+
+## GetHostShellRecording
+
+`POST /spin.v1.HostShellService/GetHostShellRecording`
+
+What a shell's sessions recorded, asciicast v2, each session's in turn.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `sessions` | list of string (byte) | Each session's asciicast v2, in the order they were opened. |
+| `truncated` | boolean | Whether there was more than one answer carries: the rest is in the bucket, under host-shells/<id>/. |
+
+## ListHostShells
+
+`POST /spin.v1.HostShellService/ListHostShells`
+
+The installation's host shells, newest first: all of them, or one host's.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `hostId` | string | One host's; empty for every host's. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `shells` | list of [HostShell](/reference/api/types/#hostshell) | |
+
+## RequestHostShell
+
+`POST /spin.v1.HostShellService/RequestHostShell`
+
+Asks for a shell on a host. Where the installation has a second administrator approve (host_shell_needs_approval), it waits for one; otherwise the host is emptied at once.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `hostId` | string | |
+| `reason` | string | Why: said to whoever approves it, and kept. |
+| `minutes` | integer (int32) | How long it is open once it opens, up to the installation's host_shell_max_minutes. |
+| `incident` | boolean | Opens on the host as it is, its workspaces running: for a host that has to be looked at busy. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `shell` | [HostShell](/reference/api/types/#hostshell) | |
+
diff --git a/src/content/docs/reference/api/index.md b/src/content/docs/reference/api/index.md
index a266b45..4a96f4d 100644
--- a/src/content/docs/reference/api/index.md
+++ b/src/content/docs/reference/api/index.md
@@ -22,14 +22,17 @@ curl -s https://app.example.com/spin.v1.WorkspaceService/ListWorkspaces \
- [AuthService](/reference/api/authservice/): 15 procedures
- [AdminService](/reference/api/adminservice/): AdminService manages installation-wide settings, user management, and resource presets. Only accessible to instance administrators.
+- [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/): 8 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.
- [RunnerTokenService](/reference/api/runnertokenservice/): 4 procedures
- [SettingsService](/reference/api/settingsservice/): 3 procedures
- [SetupService](/reference/api/setupservice/): SetupService handles initial platform setup and admin bootstrap. Most endpoints require admin authentication, except GetSetupStatus and PasswordLogin.
-- [WorkspaceService](/reference/api/workspaceservice/): 49 procedures
+- [WorkspaceService](/reference/api/workspaceservice/): 53 procedures
- [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.
- [SignatureService](/reference/api/signatureservice/): SignatureService is what only people sign of themselves: who a user's keys are and who a team's. members are. A host holds every workspace's access to these (internal/roster), so a control. plane that is taken can say neither which keys are somebody's nor who is in a team. A. workspace's access is signed through WorkspaceService.SetOwnerWord. The control plane proposes each document from what it holds - a key registered, a member added -. and keeps what was signed; it signs nothing. A client shows a person what they are signing and. signs it with a key of theirs: `spin sign`, with the keys ssh-agent holds, or the terminal's. page, with a passkey.
- [SSHKeyService](/reference/api/sshkeyservice/): SSHKeyService is the user's own keys, as a registry: the ones a client offers when the owner. signs a workspace's word (OwnerWord), and the anchor a create may name. A key here logs in to. nothing on its own - only a key the owner's word for a workspace lists does, there.
-- [TeamService](/reference/api/teamservice/): 27 procedures
+- [TeamService](/reference/api/teamservice/): 18 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 c872f8e..2ab41ae 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: 4
+ order: 6
---
diff --git a/src/content/docs/reference/api/projectservice.md b/src/content/docs/reference/api/projectservice.md
new file mode 100644
index 0000000..0487429
--- /dev/null
+++ b/src/content/docs/reference/api/projectservice.md
@@ -0,0 +1,166 @@
+---
+title: "ProjectService"
+description: "spin.v1.ProjectService: each procedure, its request and its response."
+source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
+editUrl: false
+sidebar:
+ order: 7
+---
+
+
+
+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.
+
+## CreateProject
+
+`POST /spin.v1.ProjectService/CreateProject`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `name` | string | |
+| `slug` | string | Made from the name when empty |
+| `description` | string | |
+| `repository` | string | |
+| `ref` | string | |
+| `image` | string | |
+| `preset` | string | |
+| `networkTags` | list of string | |
+| `prebuildRefs` | list of string | |
+| `prebuildRebaseEvery` | integer (int32) | Zero is the installation's default |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `project` | [Project](/reference/api/types/#project) | |
+
+## DeleteProject
+
+`POST /spin.v1.ProjectService/DeleteProject`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+
+**Response**
+
+No fields: `{}`.
+
+## GetProject
+
+`POST /spin.v1.ProjectService/GetProject`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `slug` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `project` | [Project](/reference/api/types/#project) | |
+
+## ListPrebuilds
+
+`POST /spin.v1.ProjectService/ListPrebuilds`
+
+A project's prebuilds, to whoever sees it; and a build of one of its refs now, by its admins, rather than at the next look.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `projectId` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `prebuilds` | list of [Prebuild](/reference/api/types/#prebuild) | Newest first. |
+
+## ListProjects
+
+`POST /spin.v1.ProjectService/ListProjects`
+
+**Request**
+
+No fields: `{}`.
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `projects` | list of [Project](/reference/api/types/#project) | |
+
+## RunPrebuild
+
+`POST /spin.v1.ProjectService/RunPrebuild`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `projectId` | string | |
+| `ref` | string | One of the project's prebuild refs. |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `prebuild` | [Prebuild](/reference/api/types/#prebuild) | The build, or the one already running at that commit. |
+
+## ShareProject
+
+`POST /spin.v1.ProjectService/ShareProject`
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `sharedWithEveryone` | boolean | |
+| `userIds` | list of string | |
+| `teamIds` | list of string | |
+| `adminIds` | list of string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `project` | [Project](/reference/api/types/#project) | |
+
+## UpdateProject
+
+`POST /spin.v1.ProjectService/UpdateProject`
+
+Changed, shared and deleted by its admins. A project with a workspace open on it is not deleted.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `name` | string | |
+| `slug` | string | |
+| `description` | string | |
+| `repository` | string | |
+| `ref` | string | |
+| `image` | string | |
+| `preset` | string | |
+| `networkTags` | list of string | |
+| `prebuildRefs` | list of string | |
+| `prebuildRebaseEvery` | integer (int32) | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `project` | [Project](/reference/api/types/#project) | |
+
diff --git a/src/content/docs/reference/api/runnertokenservice.md b/src/content/docs/reference/api/runnertokenservice.md
index b39f372..5b11dd0 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: 5
+ order: 8
---
diff --git a/src/content/docs/reference/api/settingsservice.md b/src/content/docs/reference/api/settingsservice.md
index 058b330..6dd43d2 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: 6
+ order: 9
---
diff --git a/src/content/docs/reference/api/setupservice.md b/src/content/docs/reference/api/setupservice.md
index c268bb4..b36abda 100644
--- a/src/content/docs/reference/api/setupservice.md
+++ b/src/content/docs/reference/api/setupservice.md
@@ -4,7 +4,7 @@ description: "spin.v1.SetupService: each procedure, its request and its response
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 7
+ order: 10
---
diff --git a/src/content/docs/reference/api/shareservice.md b/src/content/docs/reference/api/shareservice.md
index be3d4ed..3a39005 100644
--- a/src/content/docs/reference/api/shareservice.md
+++ b/src/content/docs/reference/api/shareservice.md
@@ -4,7 +4,7 @@ description: "spin.v1.ShareService: each procedure, its request and its response
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 9
+ order: 12
---
diff --git a/src/content/docs/reference/api/signatureservice.md b/src/content/docs/reference/api/signatureservice.md
index 6d89f4b..01a787c 100644
--- a/src/content/docs/reference/api/signatureservice.md
+++ b/src/content/docs/reference/api/signatureservice.md
@@ -4,7 +4,7 @@ description: "spin.v1.SignatureService: each procedure, its request and its resp
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 10
+ order: 13
---
@@ -26,6 +26,9 @@ No fields: `{}`.
| Field | Type | What it is |
|---|---|---|
| `pending` | list of [PendingSignature](/reference/api/types/#pendingsignature) | |
+| `keyringSigned` | boolean | keyring_signed is whether the caller has signed a keyring at all. Until they have, no workspace is made for them (CreateWorkspace refuses it): its access is signed with a key of the keyring. Always true on an installation with no bucket, which keeps no keyrings. |
+| `userId` | string | user_id is the caller, and keyring their keyring by the version it began at (empty until they signed one): who a grant the browser terminal signs for them names. A control plane that says another only makes a grant no host admits. |
+| `keyring` | string | |
## StartKeyringAnew
diff --git a/src/content/docs/reference/api/sshkeyservice.md b/src/content/docs/reference/api/sshkeyservice.md
index c64d8b6..c83158e 100644
--- a/src/content/docs/reference/api/sshkeyservice.md
+++ b/src/content/docs/reference/api/sshkeyservice.md
@@ -4,7 +4,7 @@ description: "spin.v1.SSHKeyService: each procedure, its request and its respons
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 11
+ order: 14
---
diff --git a/src/content/docs/reference/api/teamservice.md b/src/content/docs/reference/api/teamservice.md
index 593d476..ee8fb7e 100644
--- a/src/content/docs/reference/api/teamservice.md
+++ b/src/content/docs/reference/api/teamservice.md
@@ -4,7 +4,7 @@ description: "spin.v1.TeamService: each procedure, its request and its response.
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 12
+ order: 15
---
@@ -51,34 +51,6 @@ sidebar:
|---|---|---|
| `provider` | [TeamIdentityProvider](/reference/api/types/#teamidentityprovider) | |
-## CreateProject
-
-`POST /spin.v1.TeamService/CreateProject`
-
-A team's projects: a repository and the shape of the workspace that opens it (CreateWorkspaceRequest.project_id). Made, changed and deleted by the team's admins, listed and opened by its members. A project with a workspace open on it is not deleted.
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `teamId` | string | |
-| `name` | string | |
-| `slug` | string | Made from the name when empty |
-| `description` | string | |
-| `repository` | string | |
-| `ref` | string | |
-| `image` | string | |
-| `preset` | string | |
-| `networkTags` | list of string | |
-| `prebuildRefs` | list of string | |
-| `prebuildRebaseEvery` | integer (int32) | Zero is the installation's default |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `project` | [Project](/reference/api/types/#project) | |
-
## CreateTeam
`POST /spin.v1.TeamService/CreateTeam`
@@ -101,21 +73,6 @@ Create a new team (can be nested under parent_id)
|---|---|---|
| `team` | [Team](/reference/api/types/#team) | |
-## DeleteForge
-
-`POST /spin.v1.TeamService/DeleteForge`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `teamId` | string | |
-| `forge` | string | |
-
-**Response**
-
-No fields: `{}`.
-
## DeleteIdentityProvider
`POST /spin.v1.TeamService/DeleteIdentityProvider`
@@ -145,20 +102,6 @@ No fields: `{}`.
No fields: `{}`.
-## DeleteProject
-
-`POST /spin.v1.TeamService/DeleteProject`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `id` | string | |
-
-**Response**
-
-No fields: `{}`.
-
## DeleteSecret
`POST /spin.v1.TeamService/DeleteSecret`
@@ -210,22 +153,6 @@ 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 |
-## ListForges
-
-`POST /spin.v1.TeamService/ListForges`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `teamId` | string | |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `forges` | list of [TeamForge](/reference/api/types/#teamforge) | |
-
## ListIdentityProviders
`POST /spin.v1.TeamService/ListIdentityProviders`
@@ -266,40 +193,6 @@ Member management
| `members` | list of [TeamMember](/reference/api/types/#teammember) | |
| `page` | [PageResponse](/reference/api/types/#pageresponse) | |
-## ListPrebuilds
-
-`POST /spin.v1.TeamService/ListPrebuilds`
-
-A project's prebuilds, to its team's members; and a build of one of its refs now, by its admins, rather than at the next look.
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `projectId` | string | |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `prebuilds` | list of [Prebuild](/reference/api/types/#prebuild) | Newest first. |
-
-## ListProjects
-
-`POST /spin.v1.TeamService/ListProjects`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `teamId` | string | |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `projects` | list of [Project](/reference/api/types/#project) | |
-
## ListSecrets
`POST /spin.v1.TeamService/ListSecrets`
@@ -353,45 +246,6 @@ List teams (optionally filtered by parent)
No fields: `{}`.
-## RunPrebuild
-
-`POST /spin.v1.TeamService/RunPrebuild`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `projectId` | string | |
-| `ref` | string | One of the project's prebuild refs. |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `prebuild` | [Prebuild](/reference/api/types/#prebuild) | The build, or the one already running at that commit. |
-
-## SetForge
-
-`POST /spin.v1.TeamService/SetForge`
-
-A team's forges: the app it installed on its organization, from which the control plane mints the credential a brokered rule injecting that forge has the egress proxy set - an hour's, so a workspace of the team clones and pushes its private repositories with nothing of it on the disk. Set and deleted by the team's admins, after the forge has minted one with what was given; listed to its members. The app's key is never answered with again.
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `teamId` | string | |
-| `forge` | string | netpolicy.Forges: "github". |
-| `appId` | string (int64) | The GitHub App's id, and its installation on the organization the team's repositories are in. |
-| `installationId` | string (int64) | |
-| `privateKey` | string | The app's private key, PEM: what signs the request for an installation token. Replaces what the team held. |
-| `apiUrl` | string | Where the forge's API is, https; empty is github.com's. A GitHub Enterprise Server's is https://<host>/api/v3. |
-| `hosts` | list of string | The exact names the credential may be sent to, 1 to 16; empty is github.com and api.github.com. A brokered rule that injects the forge and names any other host is refused it. |
-
-**Response**
-
-No fields: `{}`.
-
## SetNetworkPolicy
`POST /spin.v1.TeamService/SetNetworkPolicy`
@@ -468,32 +322,6 @@ No fields: `{}`.
|---|---|---|
| `member` | [TeamMember](/reference/api/types/#teammember) | |
-## UpdateProject
-
-`POST /spin.v1.TeamService/UpdateProject`
-
-**Request**
-
-| Field | Type | What it is |
-|---|---|---|
-| `id` | string | |
-| `name` | string | |
-| `slug` | string | |
-| `description` | string | |
-| `repository` | string | |
-| `ref` | string | |
-| `image` | string | |
-| `preset` | string | |
-| `networkTags` | list of string | |
-| `prebuildRefs` | list of string | |
-| `prebuildRebaseEvery` | integer (int32) | |
-
-**Response**
-
-| Field | Type | What it is |
-|---|---|---|
-| `project` | [Project](/reference/api/types/#project) | |
-
## UpdateTeam
`POST /spin.v1.TeamService/UpdateTeam`
diff --git a/src/content/docs/reference/api/types.md b/src/content/docs/reference/api/types.md
index 6b12902..9be7d42 100644
--- a/src/content/docs/reference/api/types.md
+++ b/src/content/docs/reference/api/types.md
@@ -4,7 +4,7 @@ description: "Every message and enum the spin.v1 procedures name, with their fie
source: "spin:internal/controlplane/apidocs/spin.v1.openapi.json"
editUrl: false
sidebar:
- order: 13
+ order: 16
---
@@ -31,6 +31,31 @@ AccessPerson is somebody an access names: a user, and their keyring by the versi
| `keyring` | string | |
| `name` | string | |
+## 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.
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `title` | string | |
+| `project` | string | project is the directory the thread works in. |
+| `model` | string | |
+| `updatedAt` | `Timestamp` | |
+| `archived` | boolean | |
+
+## AgentVolume
+
+AgentVolume is one agent home of the caller's.
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `sizeBytes` | string (int64) | |
+| `createdAt` | `Timestamp` | |
+| `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. |
+
## AllowedEmail
Allowed email/domain entry
@@ -134,9 +159,9 @@ One thing the config file says differently from the installation.
## ConfigItemKind
-What one item of the installation's config file is. - `CONFIG_ITEM_KIND_SETTING`: A setting, by its key: registration_mode, default_network_tags, allowed_images... - `CONFIG_ITEM_KIND_EMAIL`: An address or domain registration admits, by itself.
+What one item of the installation's config file is. - `CONFIG_ITEM_KIND_SETTING`: A setting, by its key: registration_mode, default_network_tags, allowed_images... - `CONFIG_ITEM_KIND_EMAIL`: An address or domain registration admits, by itself. - `CONFIG_ITEM_KIND_HOST_CLASS`: A host class, by its name: the shape of a group of runners (HostClass).
-One of: `CONFIG_ITEM_KIND_UNSPECIFIED`, `CONFIG_ITEM_KIND_SETTING`, `CONFIG_ITEM_KIND_EMAIL`, `CONFIG_ITEM_KIND_PRESET`, `CONFIG_ITEM_KIND_POLICY`.
+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`.
## DeletedWorkspace
@@ -148,6 +173,7 @@ DeletedWorkspace is a workspace in the trash.
| `name` | string | The name it had; another workspace may have taken it since, and an undelete then gives it another. |
| `deletedAt` | `Timestamp` | |
| `trashedUntil` | `Timestamp` | |
+| `agentVolumeId` | string | The agent's home it mounts, which its erase keeps for another workspace (ListAgentVolumes); empty for a workspace with none. |
## DeviceLoginStatus
@@ -244,6 +270,21 @@ One of: `FILE_CHANGE_KIND_UNSPECIFIED`, `FILE_CHANGE_KIND_ADDED`, `FILE_CHANGE_K
One of: `FILE_TYPE_UNSPECIFIED`, `FILE_TYPE_REGULAR`, `FILE_TYPE_DIRECTORY`, `FILE_TYPE_SYMLINK`, `FILE_TYPE_OTHER`.
+## FleetClass
+
+One host class's group, as the control plane last saw it.
+
+| Field | Type | What it is |
+|---|---|---|
+| `class` | [HostClass](/reference/api/types/#hostclass) | |
+| `desired` | integer (int32) | What it last asked the group for, and the most it may ask now - the class's own limit, or what the installation's leaves it: zero of one is a group at rest. |
+| `most` | integer (int32) | |
+| `waiting` | integer (int32) | Workspaces of the class that have nowhere to run yet, and when the first of them started waiting: this is what makes the control plane ask for a machine. |
+| `oldestWaiting` | `Timestamp` | |
+| `busy` | integer (int32) | Workspaces of the class keeping its group up. |
+| `launchFailure` | string | Why the group has fewer machines running than it was asked for, in its cloud's own words - "AWS could not start a runner: <message>" - and when the cloud said it. Empty while it has as many, or while the cloud said nothing that explains it. |
+| `launchFailedAt` | `Timestamp` | |
+
## FleetHostVersion
FleetHostVersion is one host's halves: the runner binary and the machine under it.
@@ -258,6 +299,40 @@ FleetHostVersion is one host's halves: the runner binary and the machine under i
| `note` | string | |
| `lastHeartbeat` | `Timestamp` | |
+## ForgeApp
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | netpolicy.Forges: "github". |
+| `appId` | string (int64) | |
+| `slug` | string | |
+| `clientId` | string | |
+| `apiUrl` | string | |
+| `webUrl` | string | |
+| `hosts` | list of string | The exact names a person's token may be sent to. |
+| `installUrl` | string | Where the app is installed on an account, which is what lets a token reach its repositories. |
+| `deleting` | boolean | Being deleted: it takes no connection, and goes once what it granted has been revoked. |
+| `updatedAt` | `Timestamp` | |
+
+## ForgeConnection
+
+| Field | Type | What it is |
+|---|---|---|
+| `forge` | string | |
+| `login` | string | The account, as the forge names it. |
+| `expiresAt` | `Timestamp` | When the token ends: it is refreshed before then. |
+| `connectedAt` | `Timestamp` | |
+
+## ForgeRepository
+
+| Field | Type | What it is |
+|---|---|---|
+| `fullName` | string | owner/name. |
+| `cloneUrl` | string | What a project's repository is: https, with nothing of a credential in it. |
+| `defaultBranch` | string | |
+| `private` | boolean | |
+| `description` | string | |
+
## Grafana
Grafana is the one the control plane runs over the installation's metrics and serves to its administrators.
@@ -284,7 +359,7 @@ HeaderInjection is a header the egress proxy sets: header, to prefix and then a
| `secret` | string | The team secret's name. |
| `prefix` | string | What goes before the value, as "Bearer ". |
| `identity` | string | The audience of a token of the workspace's identity, issued for the proxy to present: bound to no key, and saying so (spin.via = egress-proxy). |
-| `forge` | string | The forge whose credential for the workspace's team goes in the header, whole: "github" is an installation token of the team's GitHub App, minted by the control plane (TeamService.SetForge). |
+| `forge` | string | The forge whose credential goes in the header, whole: "github" is the workspace owner's own token, connected through the installation's OAuth App (ForgeService), which the control plane keeps and refreshes. |
| `methods` | list of string | The requests the header is set on: these methods, and paths under path_prefix by whole segments. Another request to the rule's names is answered 403. None of either is every request. |
| `pathPrefix` | string | |
@@ -335,6 +410,21 @@ HostCheck is one thing a host asks itself about, at start and every few minutes:
| `detail` | string | detail is what it found when it is not ok, in the host's words. |
| `checkedAt` | `Timestamp` | |
+## HostClass
+
+A kind of runner: the shape of the machines a group of the installation's cloud starts for it, which the control plane makes and sizes. What a machine boots is the installation's launch template, never this. "default" is the class a preset that names none runs on.
+
+| Field | Type | What it is |
+|---|---|---|
+| `name` | string | Lower-case letters, digits and -, a letter first, up to 31: it is part of the group's name. |
+| `description` | string | |
+| `instanceTypes` | list of string | The instance types it may be, as the cloud names them, patterns allowed ("c8id.\*"); none is any type the rest admits. |
+| `vcpusMin` | integer (int32) | |
+| `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). |
+
## HostNetworkPolicy
| Field | Type | What it is |
@@ -365,6 +455,33 @@ HostPolicy is what an installation decides about one host. An unset field is the
| `maxWorkspaceIoMbPerSecond` | integer or null (int32) | What one machine may read and write per second here, in MiB and in operations, for the machines it starts afterwards. Unset is no bound. |
| `maxWorkspaceIoOpsPerSecond` | integer or null (int32) | |
+## HostShell
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+| `hostId` | string | |
+| `requestedBy` | string | Who asked, and who approved: an email, or empty for a person no longer on the installation. Approved by nobody where the installation asks for no second administrator. |
+| `approvedBy` | string | |
+| `reason` | string | |
+| `incident` | boolean | An incident's shell opens on a host still running workspaces. |
+| `minutes` | integer (int32) | How long it is open once it opens. |
+| `state` | [HostShellState](/reference/api/types/#hostshellstate) | |
+| `requestedAt` | `Timestamp` | |
+| `approvedAt` | `Timestamp` | |
+| `openedAt` | `Timestamp` | |
+| `closesAt` | `Timestamp` | |
+| `endedAt` | `Timestamp` | |
+| `ended` | string | How it ended, in words. |
+| `hostRetired` | boolean | Whether its host has been retired since: revoked, its disks marked to be re-keyed, and given back to its group where it has one. |
+| `running` | integer (int32) | How many workspaces the host still runs, while it is emptied. |
+
+## HostShellState
+
+- `HOST_SHELL_STATE_REQUESTED`: Asked for, waiting for a second administrator. - `HOST_SHELL_STATE_EMPTYING`: Approved: the host is cordoned and its workspaces are being suspended to run elsewhere. - `HOST_SHELL_STATE_OPEN`: Open: a terminal may attach until closes_at. - `HOST_SHELL_STATE_ENDED`: Over: closed, or past its time. Its host is retired. - `HOST_SHELL_STATE_REFUSED`: Refused, or withdrawn before it was approved. Its host was never touched.
+
+One of: `HOST_SHELL_STATE_UNSPECIFIED`, `HOST_SHELL_STATE_REQUESTED`, `HOST_SHELL_STATE_EMPTYING`, `HOST_SHELL_STATE_OPEN`, `HOST_SHELL_STATE_ENDED`, `HOST_SHELL_STATE_REFUSED`.
+
## HostState
HostState mirrors internal/lifecycle.HostState (§28.1) and the CHECK on hosts.state: one vocabulary for the fleet, whichever component is asking. Only ACTIVE takes new work; a CORDONED or DRAINING host keeps serving (and keeps its lease) while it is evacuated, and the Agent needs to be told which of the two it is. It replaces a second set of names this schema carried for the same states - HEALTHY, DEGRADED, OFFLINE - where DEGRADED was a word nothing branched on and no name said "an operator stopped this host".
@@ -431,7 +548,14 @@ InstallationSettings contains global settings for the Spin installation
| `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. |
-| `agentsFetch` | boolean | Whether the control plane fetches the agent CLIs - Claude Code and Codex - from their publishers on its own: the pair it pins, at start, and the newest release of each, looked for daily. False, it fetches none, and a workspace is given a pair an operator published; no image carries one. |
+| `agentsFetch` | boolean | Whether the control plane fetches the agents' programs - Claude Code, Codex and T3 Code - from their publishers on its own: the triple it pins, at start, and the newest release of each, looked for daily. False, it fetches none, and a workspace is given a triple an operator published; no image carries one. |
+| `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) | |
## Job
@@ -611,6 +735,7 @@ PendingSignature is one document the caller is asked to sign.
| `document` | string (byte) | document is the document to sign, as framed JSON: what the client shows and signs as it is. |
| `signers` | list of string | signers are the keys, in authorized_keys form, any one of which may sign it. |
| `summary` | string | summary is a line saying what it changes, for a list: the document itself is what to read. |
+| `deferred` | boolean | deferred is a document nothing asks for until the person uses what it gives: the roster of a team that has only ever been its one member, read only for an identity. Offered there, not on every page. |
## PlatformImage
@@ -624,9 +749,9 @@ PendingSignature is one document the caller is asked to sign.
## PlatformImageArtifact
-- `PLATFORM_IMAGE_ARTIFACT_BASE`: The machine's base image, every workspace's disk's floor. - `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`: The supervisor extension every workspace is handed.
+- `PLATFORM_IMAGE_ARTIFACT_BASE`: The machine's base image, every workspace's disk's floor. - `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`: The supervisor extension every workspace is handed. - `PLATFORM_IMAGE_ARTIFACT_AGENT_HOME`: The empty home every agent's volume is cloned from.
-One of: `PLATFORM_IMAGE_ARTIFACT_UNSPECIFIED`, `PLATFORM_IMAGE_ARTIFACT_BASE`, `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`.
+One of: `PLATFORM_IMAGE_ARTIFACT_UNSPECIFIED`, `PLATFORM_IMAGE_ARTIFACT_BASE`, `PLATFORM_IMAGE_ARTIFACT_SUPERVISOR`, `PLATFORM_IMAGE_ARTIFACT_AGENT_HOME`.
## PlatformImageState
@@ -691,9 +816,8 @@ A project: a repository and the shape of the workspace that opens it.
| Field | Type | What it is |
|---|---|---|
| `id` | string | |
-| `teamId` | string | |
| `name` | string | |
-| `slug` | string | What a person names it by in the team: teams.slug's shape |
+| `slug` | string | What a person names it by in the installation: teams.slug's shape |
| `description` | string | |
| `repository` | string | An https URL, with nothing of a credential in it |
| `ref` | string | The branch or tag a workspace opens at unless the open names one |
@@ -705,7 +829,12 @@ A project: a repository and the shape of the workspace that opens it.
| `updatedAt` | `Timestamp` | |
| `prebuildRefs` | list of string | The refs it prebuilds (spin#480 §3): none is none. Each is built as it moves, and opening the project at one clones its newest ready prebuild. |
| `prebuildRebaseEvery` | integer (int32) | Every how many prebuilds of a ref one is built from the image again, rather than on the one before: a clone is a layer deeper than what it is cloned from. |
-| `prebuildOwnerId` | string | Who the builds run as, for what they use: the admin who last named the refs. |
+| `prebuildOwnerId` | string | Who the builds run as, for what they use: the project admin who last named the refs. |
+| `sharedWithEveryone` | boolean | Seen by everybody signed in; otherwise by its admins and shares. |
+| `administers` | boolean | Whether the caller may change, share and delete it. |
+| `adminIds` | list of string | Its admins and whom it is shared with: shown to its admins, empty to everybody else. |
+| `sharedUserIds` | list of string | |
+| `sharedTeamIds` | list of string | |
## QuarantineDenial
@@ -812,6 +941,7 @@ ResourcePreset is a class of workspace: its size, and how a workspace of it may
| `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). |
+| `hostClass` | string | The host class its workspaces run on (HostClass). |
## Resources
@@ -1052,17 +1182,6 @@ What the supervisor inside the guest last reported. Absent (`connected` false wi
| `displayName` | string | Human-friendly name (falls back to name if empty) |
| `isPersonal` | boolean | Personal team for a single user (cannot be deleted or left by owner) |
-## TeamForge
-
-| Field | Type | What it is |
-|---|---|---|
-| `forge` | string | |
-| `appId` | string (int64) | |
-| `installationId` | string (int64) | |
-| `apiUrl` | string | |
-| `hosts` | list of string | |
-| `updatedAt` | `Timestamp` | |
-
## TeamIdentityProvider
| Field | Type | What it is |
diff --git a/src/content/docs/reference/api/workspaceservice.md b/src/content/docs/reference/api/workspaceservice.md
index 7778c14..b2da469 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: 8
+ order: 11
---
@@ -149,9 +149,10 @@ Create a new workspace
| `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. |
-| `projectId` | string | A project of a team the caller is in: the workspace is the project's - its team, image, preset and tags, which the request then names none of - and its guest clones the project's repository at ref, the project's own when empty. |
+| `projectId` | string | A project the caller sees: the workspace is the project's - its image, preset and tags, which the request then names none of - in team_id, the caller's personal team when empty, and its guest clones the project's repository at ref, the project's own when empty. Refused while the repository is on a forge the installation connects to (ForgeService) and the caller has not connected their account there. |
| `ref` | string | |
| `requestId` | string | Optional, at most 128 bytes: the client's key for this create, so a retry after an answer it never got is answered with the workspace the first attempt made. The same key with another request is refused (FAILED_PRECONDITION). |
+| `agentVolumeId` | string | Optional: an agent's home of the caller's to mount (ListAgentVolumes), one no workspace mounts now - the history of a workspace since erased, found again in this one. Empty makes the workspace a new one. Refused while no host has room: a workspace that waits for a host gets its new home when it is placed, and mounts none it was named. |
**Response**
@@ -159,6 +160,22 @@ Create a new workspace
|---|---|---|
| `workspace` | [Workspace](/reference/api/types/#workspace) | |
+## DeleteAgentVolume
+
+`POST /spin.v1.WorkspaceService/DeleteAgentVolume`
+
+Erase an agent home no workspace mounts: its volume, its key and its bytes.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `id` | string | |
+
+**Response**
+
+No fields: `{}`.
+
## DeleteSnapshot
`POST /spin.v1.WorkspaceService/DeleteSnapshot`
@@ -462,6 +479,42 @@ Keep a running workspace awake for a while: neither the idle suspension nor the
|---|---|---|
| `workspace` | [Workspace](/reference/api/types/#workspace) | |
+## ListAgentThreads
+
+`POST /spin.v1.WorkspaceService/ListAgentThreads`
+
+The agent's threads - T3 Code's chats - as the workspace's supervisor last reported them, which it does only while the owner's access consents (collect_logs). Titles, paths and times, never a message: the chat itself is read in T3 Code, over the owner's own session (`spin agent`).
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `workspaceId` | string | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `environmentId` | string | environment_id is T3 Code's id for the workspace's server: a thread's page in its web client is /<environment_id>/<thread id>, served at the port `spin agent` forwards. |
+| `threads` | list of [AgentThread](/reference/api/types/#agentthread) | Newest first. |
+| `reportedAt` | `Timestamp` | reported_at is when the supervisor last sent the index; unset where it never has - an owner who does not consent, or a workspace that ran no agent. |
+
+## ListAgentVolumes
+
+`POST /spin.v1.WorkspaceService/ListAgentVolumes`
+
+The caller's agent homes: the volume each workspace of theirs mounts at /home/spin-agent-t3, which outlives the workspace - its chats, its sessions - and which another workspace mounts at its creation (CreateWorkspaceRequest.agent_volume_id).
+
+**Request**
+
+No fields: `{}`.
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `volumes` | list of [AgentVolume](/reference/api/types/#agentvolume) | |
+
## ListDeletedWorkspaces
`POST /spin.v1.WorkspaceService/ListDeletedWorkspaces`
@@ -805,6 +858,25 @@ Who may reach a published port, and who it is shared with.
|---|---|---|
| `exposedPort` | [ExposedPort](/reference/api/types/#exposedport) | |
+## SetWorkspaceBandwidth
+
+`POST /spin.v1.WorkspaceService/SetWorkspaceBandwidth`
+
+Set what a workspace's network carries each way, an instance admin's to change: the limit bounds the installation's egress bill. A running machine is brought to it where it runs, with its connections open; any other takes it at its next start.
+
+**Request**
+
+| Field | Type | What it is |
+|---|---|---|
+| `workspaceId` | string | |
+| `bandwidthMbps` | integer (int32) | |
+
+**Response**
+
+| Field | Type | What it is |
+|---|---|---|
+| `workspace` | [Workspace](/reference/api/types/#workspace) | |
+
## SetWorkspaceNetworkTags
`POST /spin.v1.WorkspaceService/SetWorkspaceNetworkTags`
diff --git a/src/content/docs/reference/cli/admin.md b/src/content/docs/reference/cli/admin.md
index bc12637..926e691 100644
--- a/src/content/docs/reference/cli/admin.md
+++ b/src/content/docs/reference/cli/admin.md
@@ -23,6 +23,7 @@ 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
+ 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
@@ -328,6 +329,138 @@ Global Flags:
--profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default")
```
+### spin admin shell
+
+```
+A root shell on a runner, for a while, with a reason.
+
+The runner is emptied of its workspaces first - each suspended and resumed on another
+runner - unless the shell is an incident's (--incident), which opens on the runner as it is.
+Where the installation asks for it (host_shell_needs_approval), a second administrator
+approves the shell before the runner is touched. Every session is recorded by the control
+plane, kept in the installation's logs bucket under Object Lock. Once the shell has ended - its
+minutes up, or closed - the runner is retired: revoked, every disk it held the key of marked
+to be re-keyed, and its machine given back to its group, which replaces it.
+
+Usage:
+ spin admin shell [flags]
+ spin admin shell [command]
+
+Available Commands:
+ approve Approve a root shell another administrator asked for: its runner is emptied at once
+ close End a root shell: one not yet approved is refused; any other ends, and its runner is retired
+ list The installation's root shells on runners, newest first: every runner's, or one's
+ open Ask for a root shell on a runner, wait for it, and open a session on it
+ recording What a root shell's sessions recorded, asciicast v2, each session's in turn
+
+Flags:
+ -h, --help help for shell
+
+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")
+
+Use "spin admin shell [command] --help" for more information about a command.
+```
+
+#### spin admin shell approve
+
+```
+Approve a root shell another administrator asked for: its runner is emptied at once
+
+Usage:
+ spin admin shell approve [flags]
+
+Flags:
+ -h, --help help for approve
+
+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")
+```
+
+#### spin admin shell close
+
+```
+End a root shell: one not yet approved is refused; any other ends, and its runner is retired
+
+Usage:
+ spin admin shell close [flags]
+
+Flags:
+ -h, --help help for close
+
+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")
+```
+
+#### spin admin shell list
+
+```
+The installation's root shells on runners, newest first: every runner's, or one's
+
+Usage:
+ spin admin shell list [runner-id] [flags]
+
+Flags:
+ -h, --help help for list
+
+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")
+```
+
+#### spin admin shell open
+
+```
+Ask for a root shell on a runner - or take the one asked for already - wait until it is open,
+and open a session on it in this terminal. The session ends when the shell exits, this terminal
+closes, or the shell's minutes are up. Opening another session on the same shell, while it is
+open, is running this again.
+
+Examples:
+ spin admin shell open 0199... --reason "the disk fills and nothing says why" --minutes 20
+ spin admin shell open 0199... --reason "it drops every stream" --incident
+
+Usage:
+ spin admin shell open [flags]
+
+Flags:
+ -h, --help help for open
+ --incident Open on the runner as it is, its workspaces running, without emptying it first
+ --minutes int32 How long the shell is open once it opens, up to the installation's host_shell_max_minutes (default 30)
+ --reason string Why: said to whoever approves it, and kept
+
+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")
+```
+
+#### spin admin shell recording
+
+```
+What a root shell's sessions recorded, asciicast v2: each session's in turn, on standard output.
+One session's plays with asciinema (asciinema play); the recordings stay in the logs bucket, under
+host-shells//, for as long as the installation keeps its logs.
+
+Usage:
+ spin admin shell recording [flags]
+
+Flags:
+ -h, --help help for recording
+
+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")
+```
+
### spin admin storage
```
diff --git a/src/content/docs/reference/cli/agent.md b/src/content/docs/reference/cli/agent.md
new file mode 100644
index 0000000..d5e0e72
--- /dev/null
+++ b/src/content/docs/reference/cli/agent.md
@@ -0,0 +1,115 @@
+---
+title: "spin agent"
+description: "spin agent: Run T3 Code in a workspace and use it from this computer."
+source: "spin:cmd/cli"
+editUrl: false
+sidebar:
+ order: 2
+---
+
+
+
+```
+Run T3 Code in a workspace, on the repository at path, and use its web UI from this computer.
+
+T3 Code is the server that drives Claude Code and Codex from a browser: threads, checkpoints,
+and switching between them. The workspace's supervisor runs it on demand - the same server the
+dashboard's Agent tab opens - as the workspace's agent user, spin-agent-t3: no capability, its
+egress brokered and marked, given only path. It listens on the workspace's loopback alone, and
+your own SSH session forwards it here, so the UI is at http://127.0.0.1:3773 and
+the link printed pairs your browser with it. Closing the session, with Ctrl-C, ends the
+forwarding; the server goes on for half an hour with nothing asked of it and no chat running,
+then stops, and its threads stay in the agent's home on the disk.
+
+path defaults to the project's checkout; a workspace with no project is told where to work.
+T3 Code's server is a Node program that wants a workspace of two gigabytes or more: in one of
+a gigabyte the kernel kills it as it starts.
+
+Examples:
+ spin agent my-workspace
+ spin agent my-workspace /root/app --start
+ spin agent my-workspace --port 4000
+
+Usage:
+ spin agent [path] [flags]
+ spin agent [command]
+
+Available Commands:
+ home The agent's homes: volumes that outlive their workspaces
+
+Flags:
+ -h, --help help for agent
+ -i, --identity string Identity file (private key)
+ --port int The port on this computer the session forwards T3 Code to (default 3773)
+ --start Start the workspace first if it is stopped
+ --timeout duration How long to wait for the workspace to be running; by default, until the control plane gives up on it
+
+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")
+
+Use "spin agent [command] --help" for more information about a command.
+```
+
+### spin agent home
+
+```
+An agent's home is a volume of its own, mounted at /home/spin-agent-t3 in the workspace: what
+T3 Code and Claude Code keep there - the chats - outlives the workspace. Erasing a workspace
+leaves its home yours, unmounted; a new workspace mounts it at its creation (spin workspace
+create --agent-home ) and finds the chats there.
+
+Usage:
+ spin agent home [flags]
+ spin agent home [command]
+
+Available Commands:
+ delete Erase an agent home no workspace mounts: its chats, its key and its bytes
+ list Your agent homes, and which workspace mounts each
+
+Flags:
+ -h, --help help for home
+
+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")
+
+Use "spin agent home [command] --help" for more information about a command.
+```
+
+#### spin agent home delete
+
+```
+Erase an agent home no workspace mounts: its chats, its key and its bytes
+
+Usage:
+ spin agent home delete [flags]
+
+Flags:
+ -h, --help help for delete
+
+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")
+```
+
+#### spin agent home list
+
+```
+Your agent homes, and which workspace mounts each
+
+Usage:
+ spin agent home list [flags]
+
+Flags:
+ -h, --help help for list
+
+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")
+```
+
diff --git a/src/content/docs/reference/cli/api-key.md b/src/content/docs/reference/cli/api-key.md
index 605e74e..1708b8b 100644
--- a/src/content/docs/reference/cli/api-key.md
+++ b/src/content/docs/reference/cli/api-key.md
@@ -4,7 +4,7 @@ description: "spin api-key: Manage API keys."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 2
+ order: 3
---
diff --git a/src/content/docs/reference/cli/code.md b/src/content/docs/reference/cli/code.md
index 0d2d32d..49f2951 100644
--- a/src/content/docs/reference/cli/code.md
+++ b/src/content/docs/reference/cli/code.md
@@ -4,7 +4,7 @@ description: "spin code: Open a workspace in your editor over SSH."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 3
+ order: 4
---
diff --git a/src/content/docs/reference/cli/cp.md b/src/content/docs/reference/cli/cp.md
index 3159bc4..93d2666 100644
--- a/src/content/docs/reference/cli/cp.md
+++ b/src/content/docs/reference/cli/cp.md
@@ -4,7 +4,7 @@ description: "spin cp: Copy files to or from a workspace."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 4
+ order: 5
---
diff --git a/src/content/docs/reference/cli/exec.md b/src/content/docs/reference/cli/exec.md
index b54ac34..9ba5854 100644
--- a/src/content/docs/reference/cli/exec.md
+++ b/src/content/docs/reference/cli/exec.md
@@ -4,7 +4,7 @@ description: "spin exec: Run a command in a workspace."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 5
+ order: 6
---
diff --git a/src/content/docs/reference/cli/git.md b/src/content/docs/reference/cli/git.md
new file mode 100644
index 0000000..6882311
--- /dev/null
+++ b/src/content/docs/reference/cli/git.md
@@ -0,0 +1,119 @@
+---
+title: "spin git"
+description: "spin git: Your accounts on forges: GitHub, connected through this installation's app."
+source: "spin:cmd/cli"
+editUrl: false
+sidebar:
+ order: 7
+---
+
+
+
+```
+Your account on a forge is what clones and pushes your workspaces' private repositories: the
+control plane keeps its token, and the host your workspace runs on sets it on the requests a
+network policy brokers to the forge, so no workspace's disk ever holds it.
+
+You connect an account with spin git connect, which gives you a code to enter on the forge's page,
+or in the dashboard (Settings → Git): either way the forge asks you to authorize this
+installation's app, and the token it grants stays with the control plane.
+
+Usage:
+ spin git [flags]
+ spin git [command]
+
+Available Commands:
+ connect Connect your account on a forge, with a code you enter on the forge's own page
+ disconnect Forget your account on a forge, and have the forge revoke its token
+ list List your connected accounts
+ repos List the repositories your account reaches, or those whose name has query in it
+
+Flags:
+ -h, --help help for git
+
+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")
+
+Use "spin git [command] --help" for more information about a command.
+```
+
+### spin git connect
+
+```
+Connect your account on a forge through this installation's app. The control plane asks the
+forge for a code; you enter it on the forge's page, signed in as the account to connect, and
+authorize the app. The command waits until you have, and the control plane keeps the token: it
+never reaches this machine.
+
+The installation's GitHub App must have "Enable Device Flow" checked in its settings on the forge.
+
+Usage:
+ spin git connect [flags]
+
+Flags:
+ --forge string The forge (default "github")
+ -h, --help help for connect
+ --no-browser Do not open the forge's page in a browser
+
+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")
+```
+
+### spin git disconnect
+
+```
+Forget your account on a forge, and have the forge revoke the token this installation held: your
+workspaces stop reaching your private repositories within the hour their host keeps it.
+
+Usage:
+ spin git disconnect [flags]
+
+Flags:
+ -h, --help help for disconnect
+ -y, --yes 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")
+```
+
+### spin git list
+
+```
+List your connected accounts
+
+Usage:
+ spin git list [flags]
+
+Flags:
+ -h, --help help for list
+
+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")
+```
+
+### spin git repos
+
+```
+List the repositories your account reaches, or those whose name has query in it
+
+Usage:
+ spin git repos [query] [flags]
+
+Flags:
+ --forge string The forge (default "github")
+ -h, --help help for repos
+
+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")
+```
+
diff --git a/src/content/docs/reference/cli/index.md b/src/content/docs/reference/cli/index.md
index 2bea3b9..b101a48 100644
--- a/src/content/docs/reference/cli/index.md
+++ b/src/content/docs/reference/cli/index.md
@@ -21,17 +21,19 @@ Usage:
Available Commands:
admin Administrative commands
+ agent Run T3 Code in a workspace and use it from this computer
api-key Manage API keys
code Open a workspace in your editor over SSH
completion Generate the autocompletion script for the specified shell
cp Copy files to or from a workspace
exec Run a command in a workspace
+ git Your accounts on forges: GitHub, connected through this installation's app
help Help about any command
login Authenticate with the platform
logout End the profile's session and forget its login
open Open a repository, branch, commit or pull request from its URL
port Manage exposed ports for workspaces
- project Manage a team's projects: a repository and the workspace that opens it
+ project Manage projects: a repository and the workspace that opens it
settings Manage user settings
share Manage snapshot sharing
sign Sign what only you can: your keyring and your teams' rosters
@@ -56,15 +58,17 @@ Use "spin [command] --help" for more information about a command.
```
- [`spin admin`](/reference/cli/admin/): Administrative commands
+- [`spin agent`](/reference/cli/agent/): Run T3 Code in a workspace and use it from this computer
- [`spin api-key`](/reference/cli/api-key/): Manage API keys
- [`spin code`](/reference/cli/code/): Open a workspace in your editor over SSH
- [`spin cp`](/reference/cli/cp/): Copy files to or from a workspace
- [`spin exec`](/reference/cli/exec/): Run a command in a workspace
+- [`spin git`](/reference/cli/git/): Your accounts on forges: GitHub, connected through this installation's app
- [`spin login`](/reference/cli/login/): Authenticate with the platform
- [`spin logout`](/reference/cli/logout/): End the profile's session and forget its login
- [`spin open`](/reference/cli/open/): Open a repository, branch, commit or pull request from its URL
- [`spin port`](/reference/cli/port/): Manage exposed ports for workspaces
-- [`spin project`](/reference/cli/project/): Manage a team's projects: a repository and the workspace that opens it
+- [`spin project`](/reference/cli/project/): Manage projects: a repository and the workspace that opens it
- [`spin settings`](/reference/cli/settings/): Manage user settings
- [`spin share`](/reference/cli/share/): Manage snapshot sharing
- [`spin sign`](/reference/cli/sign/): Sign what only you can: your keyring and your teams' rosters
diff --git a/src/content/docs/reference/cli/login.md b/src/content/docs/reference/cli/login.md
index ce50f6a..ea9692d 100644
--- a/src/content/docs/reference/cli/login.md
+++ b/src/content/docs/reference/cli/login.md
@@ -4,7 +4,7 @@ description: "spin login: Authenticate with the platform."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 6
+ order: 8
---
diff --git a/src/content/docs/reference/cli/logout.md b/src/content/docs/reference/cli/logout.md
index 0579f95..46b278a 100644
--- a/src/content/docs/reference/cli/logout.md
+++ b/src/content/docs/reference/cli/logout.md
@@ -4,7 +4,7 @@ description: "spin logout: End the profile's session and forget its login."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 7
+ order: 9
---
diff --git a/src/content/docs/reference/cli/open.md b/src/content/docs/reference/cli/open.md
index fd81804..b477326 100644
--- a/src/content/docs/reference/cli/open.md
+++ b/src/content/docs/reference/cli/open.md
@@ -4,7 +4,7 @@ description: "spin open: Open a repository, branch, commit or pull request from
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 8
+ order: 10
---
@@ -13,8 +13,9 @@ sidebar:
Open what a forge's URL names in a workspace of the project that has the repository, and
connect to it: the repository at the project's ref, a branch (/tree/), a commit
(/commit/) or a pull request (/pull/), checked out as pr-. The project is found by
-its repository across your teams; with the repository in more than one team, --team says which.
-A repository no project of yours has is one to make a project of first ('spin project create').
+its repository among the projects you see; with more than one, --project says which. The
+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, and its first access is signed here with a key of your
keyring, with the settings approved - the repository is one of them. It ends in 'spin ssh'
@@ -32,7 +33,8 @@ Flags:
-h, --help help for open
--name string Workspace name (auto-generated if empty)
--no-connect Create the workspace and print it, without connecting
- --team string The team whose project to open, by id, path or me, when more than one has the repository
+ --project string The project to open, by slug, when more than one has the repository
+ --team string The team the workspace is made in, by id, path or me (your personal team if empty)
--timeout duration How long --wait waits; by default, until the control plane gives up on it
--wait Return once the workspace is running, with --no-connect
diff --git a/src/content/docs/reference/cli/port.md b/src/content/docs/reference/cli/port.md
index dd95482..287060a 100644
--- a/src/content/docs/reference/cli/port.md
+++ b/src/content/docs/reference/cli/port.md
@@ -4,7 +4,7 @@ description: "spin port: Manage exposed ports for workspaces."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 9
+ order: 11
---
diff --git a/src/content/docs/reference/cli/project.md b/src/content/docs/reference/cli/project.md
index a74a43f..3c7e9ad 100644
--- a/src/content/docs/reference/cli/project.md
+++ b/src/content/docs/reference/cli/project.md
@@ -1,34 +1,36 @@
---
title: "spin project"
-description: "spin project: Manage a team's projects: a repository and the workspace that opens it."
+description: "spin project: Manage projects: a repository and the workspace that opens it."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 10
+ order: 12
---
```
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 --team --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. A team's admins make and change projects; its members open them.
+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.
-A private repository's credential is the team's to add through the host's egress proxy (a
-network policy that injects a team secret towards the forge); the URL carries none.
+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
+on GitHub is cloned with the workspace owner's own GitHub account, connected in the dashboard
+(Settings → Git); the URL carries no credential.
Usage:
spin project [flags]
spin project [command]
Available Commands:
- create Make a project of a team
+ create Make a project
delete Delete a project; one with a workspace open on it is refused
- list List a team's projects
+ list List the projects you see
prebuild Build one of a project's prebuild refs now
prebuilds List a project's prebuilds, newest first
+ share Say who sees a project and who administers it, whole
update Save a project whole: every flag is what it becomes
Flags:
@@ -45,11 +47,12 @@ Use "spin project [command] --help" for more information about a command.
### spin project create
```
-Make a project of a team: its repository, the ref it opens at, and the shape of the workspace
-that opens it. The team's admins only.
+Make a project: its repository, the ref it opens at, and the shape of the workspace that opens
+it. You are its admin, and everybody sees it until you share it otherwise. An installation may keep
+making projects to its administrators.
Example:
- spin project create --team /engineering --name qboot --repository https://github.com/spin-stack/qboot --ref master --preset large
+ spin project create --name qboot --repository https://github.com/spin-stack/qboot --ref master --preset large
Usage:
spin project create [flags]
@@ -65,8 +68,7 @@ Flags:
--preset string The preset a workspace of it is sized by (default: the installation's)
--ref string The branch or tag a workspace opens at unless the open names one
--repository string The repository's https URL, with no credential in it
- --slug string What it is named by in the team (made from the name if empty)
- --team string The team, by id, path or me
+ --slug string What it is named by in the installation (made from the name if empty)
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
@@ -83,9 +85,8 @@ Usage:
spin project delete [flags]
Flags:
- -h, --help help for delete
- --team string The team, by id, path or me
- -y, --yes Skip confirmation
+ -h, --help help for delete
+ -y, --yes Skip confirmation
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
@@ -96,14 +97,13 @@ Global Flags:
### spin project list
```
-List a team's projects
+List the projects you see
Usage:
spin project list [flags]
Flags:
- -h, --help help for list
- --team string The team, by id, path or me
+ -h, --help help for list
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
@@ -115,15 +115,14 @@ Global Flags:
```
Build one of a project's prebuild refs now, at the commit its forge says it is at, rather than at
-the next look (every five minutes): a commit whose build failed is built again. The team's admins
-only. Answered with the build, or the one of that commit already building or ready.
+the next look (every five minutes): a commit whose build failed is built again. The project's
+admins only. Answered with the build, or the one of that commit already building or ready.
Usage:
spin project prebuild [ [flags]
Flags:
- -h, --help help for prebuild
- --team string The team, by id, path or me
+ -h, --help help for prebuild
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
@@ -143,8 +142,33 @@ Usage:
spin project prebuilds [flags]
Flags:
- -h, --help help for prebuilds
- --team string The team, by id, path or me
+ -h, --help help for prebuilds
+
+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")
+```
+
+### spin project share
+
+```
+Say who sees a project and who administers it: everybody (--everyone), or the people (--user,
+by id) and teams (--team, by id, path or me) named; and its admins (--admin, by id), at least one,
+who always see it. What is not named is no longer shared. Its admins only.
+
+Example:
+ spin project share qboot --team /engineering --admin 0190c1f2-0000-7000-8000-000000000001
+
+Usage:
+ spin project share [flags]
+
+Flags:
+ --admin stringArray A person who administers it, by id (repeatable; at least one)
+ --everyone Everybody signed in sees it
+ -h, --help help for share
+ --team stringArray A team whose members see it, by id, path or me (repeatable)
+ --user stringArray A person who sees it, by id (repeatable)
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
@@ -156,8 +180,8 @@ Global Flags:
```
Save a project whole: what each flag says is what the project becomes, and a flag not given
-is the field emptied, since a project is saved as a form shows it. A workspace already open keeps
-what it was opened with.
+is the field emptied, since a project is saved as a form shows it. Its admins only. A workspace
+already open keeps what it was opened with.
Usage:
spin project update [flags]
@@ -173,8 +197,7 @@ Flags:
--preset string The preset a workspace of it is sized by (default: the installation's)
--ref string The branch or tag a workspace opens at unless the open names one
--repository string The repository's https URL, with no credential in it
- --slug string What it is named by in the team (made from the name if empty)
- --team string The team, by id, path or me
+ --slug string What it is named by in the installation (made from the name if empty)
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
diff --git a/src/content/docs/reference/cli/settings.md b/src/content/docs/reference/cli/settings.md
index 4a1dd36..48b2ec1 100644
--- a/src/content/docs/reference/cli/settings.md
+++ b/src/content/docs/reference/cli/settings.md
@@ -4,7 +4,7 @@ description: "spin settings: Manage user settings."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 11
+ order: 13
---
diff --git a/src/content/docs/reference/cli/share.md b/src/content/docs/reference/cli/share.md
index 82c8eb1..66fcfab 100644
--- a/src/content/docs/reference/cli/share.md
+++ b/src/content/docs/reference/cli/share.md
@@ -4,7 +4,7 @@ description: "spin share: Manage snapshot sharing."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 12
+ order: 14
---
diff --git a/src/content/docs/reference/cli/sign.md b/src/content/docs/reference/cli/sign.md
index 10632e5..be87c12 100644
--- a/src/content/docs/reference/cli/sign.md
+++ b/src/content/docs/reference/cli/sign.md
@@ -4,7 +4,7 @@ description: "spin sign: Sign what only you can: your keyring and your teams' ro
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 13
+ order: 15
---
diff --git a/src/content/docs/reference/cli/smoke.md b/src/content/docs/reference/cli/smoke.md
index 67e77b3..47dac61 100644
--- a/src/content/docs/reference/cli/smoke.md
+++ b/src/content/docs/reference/cli/smoke.md
@@ -4,7 +4,7 @@ description: "spin smoke: Check an installation does what its API promises, with
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 14
+ order: 16
---
diff --git a/src/content/docs/reference/cli/snapshot.md b/src/content/docs/reference/cli/snapshot.md
index aeca8b4..c5cca16 100644
--- a/src/content/docs/reference/cli/snapshot.md
+++ b/src/content/docs/reference/cli/snapshot.md
@@ -4,7 +4,7 @@ description: "spin snapshot: Take, list and delete a workspace's snapshots."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 15
+ order: 17
---
diff --git a/src/content/docs/reference/cli/ssh-config.md b/src/content/docs/reference/cli/ssh-config.md
index e6f8fe6..b4889bf 100644
--- a/src/content/docs/reference/cli/ssh-config.md
+++ b/src/content/docs/reference/cli/ssh-config.md
@@ -4,7 +4,7 @@ description: "spin ssh-config: Make every workspace reachable with plain ssh, as
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 17
+ order: 19
---
diff --git a/src/content/docs/reference/cli/ssh-key.md b/src/content/docs/reference/cli/ssh-key.md
index 3b572b8..64b3b93 100644
--- a/src/content/docs/reference/cli/ssh-key.md
+++ b/src/content/docs/reference/cli/ssh-key.md
@@ -4,17 +4,18 @@ description: "spin ssh-key: Manage SSH keys."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 18
+ order: 20
---
```
-The public keys registered with your account: a registry to pick from, and nothing more.
+The public keys registered with your account, which your keyring is signed from.
-A key registered here logs in to no workspace on its own. A workspace lets in only the keys its
-owner's word lists - 'spin workspace keys add ' signs one in, with the
-workspace's anchor - and a passkey the terminal page registers is added here for that.
+A key registered here logs in nowhere until your keyring lists it: 'spin sign' signs the
+keyring's next version, with a key it already lists, and the key then logs in to every workspace
+whose access names you, as its owner or a guest. A passkey the terminal page registers
+is added here for that too (docs/access.md).
Usage:
spin ssh-key [flags]
@@ -51,6 +52,7 @@ Usage:
Flags:
-h, --help help for add
+ --wait wait until your keyring lists the key, signed elsewhere
Global Flags:
--config string config file (default is $HOME/.spin/config.yaml)
diff --git a/src/content/docs/reference/cli/ssh.md b/src/content/docs/reference/cli/ssh.md
index 67f071d..800d8ee 100644
--- a/src/content/docs/reference/cli/ssh.md
+++ b/src/content/docs/reference/cli/ssh.md
@@ -4,7 +4,7 @@ description: "spin ssh: SSH into a workspace."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 16
+ order: 18
---
diff --git a/src/content/docs/reference/cli/team.md b/src/content/docs/reference/cli/team.md
index 8da2af5..0d55482 100644
--- a/src/content/docs/reference/cli/team.md
+++ b/src/content/docs/reference/cli/team.md
@@ -4,7 +4,7 @@ description: "spin team: Manage teams."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 19
+ order: 21
---
@@ -22,7 +22,6 @@ Usage:
Available Commands:
create Create a new team
delete Delete a team
- forge Manage the app a team's workspaces reach its repositories with
get Get team details
list List teams
member Manage team members
@@ -85,103 +84,6 @@ Global Flags:
--profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default")
```
-### spin team forge
-
-```
-A team's forge is the app it installed on its organization - a GitHub App, today. The control
-plane keeps the app's key and mints an installation token with it, an hour's, for the egress
-proxy of a host a workspace of the team runs on; a brokered network rule that injects the forge
-has the proxy set it on the workspace's requests to the forge. A workspace clones and pushes the
-team's private repositories with nothing of either on its disk, so nothing of either is in a
-snapshot of it. A workspace of a sub-team uses the nearest of its teams that has one.
-
-Usage:
- spin team forge [flags]
- spin team forge [command]
-
-Available Commands:
- delete Delete a team's forge app
- list List a team's forge apps
- set Set a team's app on a forge: github, its GitHub App
-
-Flags:
- -h, --help help for forge
-
-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")
-
-Use "spin team forge [command] --help" for more information about a command.
-```
-
-#### spin team forge delete
-
-```
-Delete a team's forge app
-
-Usage:
- spin team forge delete [flags]
-
-Flags:
- -h, --help help for delete
- -y, --yes 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")
-```
-
-#### spin team forge list
-
-```
-List a team's forge apps
-
-Usage:
- spin team forge list [flags]
-
-Flags:
- -h, --help help for list
-
-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")
-```
-
-#### spin team forge set
-
-```
-Set the team's GitHub App: its id, its installation on the organization the team's
-repositories are in, and its private key, read from a file (as GitHub hands it over) or from
-standard input with --private-key-file -. Never an argument: an argument is in every process
-listing on the machine. The control plane tries the app before keeping it: an id, an
-installation or a key GitHub refuses is said here. The token goes to github.com and
-api.github.com unless --host names where; a brokered rule that injects the forge and names any
-other host is refused it.
-
-Example:
- spin team forge set engineering github --app-id 123456 --installation-id 7890123 \
- --private-key-file ~/Downloads/engineering.2026-10-15.private-key.pem
-
-Usage:
- spin team forge set [flags]
-
-Flags:
- --api-url string The forge's API, for GitHub Enterprise Server: https:///api/v3
- --app-id int The app's id, on its settings page
- -h, --help help for set
- --host stringArray A host the token may be sent to, named exactly (repeatable)
- --installation-id int The app's installation on the organization, at the end of its URL
- --private-key-file string The app's private key, PEM; - reads standard input
-
-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")
-```
-
### spin team get
```
diff --git a/src/content/docs/reference/cli/trust.md b/src/content/docs/reference/cli/trust.md
index 149a547..51e0fd8 100644
--- a/src/content/docs/reference/cli/trust.md
+++ b/src/content/docs/reference/cli/trust.md
@@ -4,7 +4,7 @@ description: "spin trust: What this machine trusts of an installation."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 20
+ order: 22
---
diff --git a/src/content/docs/reference/cli/version.md b/src/content/docs/reference/cli/version.md
index 9e68606..0236a1c 100644
--- a/src/content/docs/reference/cli/version.md
+++ b/src/content/docs/reference/cli/version.md
@@ -4,7 +4,7 @@ description: "spin version: Print version information."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 21
+ order: 23
---
diff --git a/src/content/docs/reference/cli/whoami.md b/src/content/docs/reference/cli/whoami.md
index d97ebbd..01c94ec 100644
--- a/src/content/docs/reference/cli/whoami.md
+++ b/src/content/docs/reference/cli/whoami.md
@@ -4,7 +4,7 @@ description: "spin whoami: Show current user info."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 22
+ order: 24
---
diff --git a/src/content/docs/reference/cli/workspace.md b/src/content/docs/reference/cli/workspace.md
index f4e1277..3fc0d2f 100644
--- a/src/content/docs/reference/cli/workspace.md
+++ b/src/content/docs/reference/cli/workspace.md
@@ -4,7 +4,7 @@ description: "spin workspace: Manage workspaces."
source: "spin:cmd/cli"
editUrl: false
sidebar:
- order: 23
+ order: 25
---
@@ -23,6 +23,7 @@ Available Commands:
approve-egress-root Let the workspace trust the installation's egress root
approve-settings Approve the startup settings the workspace is handed
audiences Who the workspace's identity may be given for, as its owner's word says
+ bandwidth Set what a workspace's network carries each way (admin)
clone Clone a running workspace
console What the workspace's machine printed to its serial console
create Create a new workspace
@@ -51,6 +52,7 @@ Available Commands:
status Why a workspace is not behaving
stop Stop a running workspace
suspend Put a running workspace down with its processes where they are
+ threads The agent's chats, as the workspace last reported them
undelete Take a workspace out of the trash, stopped
unquarantine Release a quarantined workspace (admin)
update Update workspace properties
@@ -136,6 +138,29 @@ Global Flags:
--profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default")
```
+### spin workspace bandwidth
+
+```
+Set what a workspace's network carries each way, in megabits a second; 0 lifts the limit
+and leaves it unshaped. A running machine is brought to it where it runs, and its open connections
+stay open. A stopped or suspended workspace takes it at its next start.
+
+An administrator's to set, signed in: the limit bounds what the workspace can cost in egress, so
+its own person may not raise it. Its host is told after the value is recorded; if it cannot take it
+the machine goes on as it was, and the host is asked again.
+
+Usage:
+ spin workspace bandwidth [flags]
+
+Flags:
+ -h, --help help for bandwidth
+
+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")
+```
+
### spin workspace clone
```
@@ -202,9 +227,11 @@ you invite - whether logs leave the workspace and which settings it applies. The
on lets a key in only by it, and the control plane cannot write one. Settings are applied once
you approve them (--approve-settings, or 'spin workspace approve-settings').
-A project of a team (--team, --project) is the workspace's shape - its image, preset and network
-policies - and the repository its guest clones into /workspace/, at --ref or the
-project's own. Opening one approves the settings, since the repository is one of them.
+A project (--project) is the workspace's shape - its image, preset and network policies - and
+the repository its guest clones into /workspace/, at --ref or the project's own;
+the workspace is made in --team, or your personal team. Opening one approves the settings, since
+the repository is one of them. A project whose repository is on GitHub opens once you have
+connected your GitHub account (Settings → Git in the dashboard).
Examples:
spin workspace create --name mydev
@@ -217,6 +244,7 @@ Usage:
spin workspace create [flags]
Flags:
+ --agent-home string An agent's home of yours to mount, by id (spin agent home list): the chats of a workspace since erased
--approve-settings Show your startup settings and approve them in the first access
--env strings Environment variables (KEY=VALUE)
--from-snapshot string Start from a snapshot instead of an image
@@ -225,7 +253,7 @@ Flags:
--name string Workspace name (auto-generated if empty)
--network-tag strings Network policy to run under, by name (repeatable)
--preset string The preset it is sized by, by name (default: the installation's)
- --project string A project of --team to open, by slug: its image, preset and policies, and its repository cloned
+ --project string A project to open, by slug: its image, preset and policies, and its repository cloned
--ref string The branch or tag to open the project at (default: the project's)
--team string The team, by id, path or me (your personal team if empty)
--timeout duration How long --wait waits; by default, until the control plane gives up on it
@@ -867,6 +895,29 @@ Global Flags:
--profile string named profile to use; SPIN_PROFILE names it when this flag does not (default "default")
```
+### spin workspace threads
+
+```
+The agent's threads - T3 Code's chats - as the workspace's supervisor last reported them,
+newest first: a title, the directory it works in, the model, and when it last moved. Never a
+message: a chat is read in T3 Code, over your own session (spin agent).
+
+The supervisor reports the index only while the workspace's access consents to its logs
+leaving the machine (spin workspace logs-consent); a workspace whose owner has not says
+nothing here.
+
+Usage:
+ spin workspace threads [flags]
+
+Flags:
+ -h, --help help for threads
+
+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")
+```
+
### spin workspace undelete
```
diff --git a/src/content/docs/reference/configuration/file.md b/src/content/docs/reference/configuration/file.md
index 22c61dc..8947336 100644
--- a/src/content/docs/reference/configuration/file.md
+++ b/src/content/docs/reference/configuration/file.md
@@ -70,6 +70,8 @@ settings:
# session tagged spin-team-id with the team; it reads only a secret or a parameter tagged the
# same. Unset, a team's secrets are sealed in the database and none is a reference.
# team_secrets_role: arn:aws:iam::123456789012:role/spin-team-secrets
+ # Who makes projects: everyone signed in, or only the installation's admins. Unset, everyone.
+ # project_creation: everyone
# 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
@@ -135,9 +137,10 @@ registration:
# 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
# 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: unshaped); ssh, the editor
-# and forwarded ports are not held to it. rpo_seconds is how far behind the object store its
-# disk may fall (left out: the installation's volume_rpo_seconds).
+# 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.
+# rpo_seconds is how far behind the object store its disk may fall (left out: the installation's
+# volume_rpo_seconds).
presets:
- name: small
description: For an agent doing one task at a time
@@ -176,7 +179,9 @@ presets:
# ICMP type. A rule with no destination is its ports anywhere, and one with no port is every
# port at its destinations. Two things on two sets of ports are two rules: one rule of both
# would open each port at each destination. A workspace needs no port for DNS: its resolver is
-# the host's, which answers under every policy.
+# the host's. It asks upstream only for a name a rule names, and for anything where a rule reaches
+# every address on some port (no destination, or 0.0.0.0/0); it refuses every other lookup, so a
+# rule of narrow CIDRs resolves nothing, and offline resolves nothing.
#
# A policy is as exact as DNS: an address serving several names is open to all of them once
# one is allowed - unless the rule says verifySNI: true, which passes a TLS connection only when
@@ -198,6 +203,8 @@ network_policies:
- 80
- 443
# A resolver of the workspace's own on UDP, and ping anywhere: two rules, not their product.
+ # Neither names a host nor reaches every address, so the host's resolver answers nothing for it:
+ # under this alone, names resolve only through the workspace's own resolver.
- name: diagnostics
rules:
- allow: [1.1.1.1/32]
diff --git a/src/content/docs/reference/security/known-gaps.md b/src/content/docs/reference/security/known-gaps.md
index 64fe38e..d28db07 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/security/README.md) at `c02ffc3ac`. 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/docs/security/README.md) at `2cfbcd74d`. 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.
@@ -26,7 +26,10 @@ What no line keeps yet. Each is a design change, not a patch, which is why it is
is open to all of them once one is allowed; a rule that checks SNI (`netpolicy.Rule.VerifySNI`)
narrows that for a client that names the host it wants, and passes a tenant that names an allowed
host to reach another behind the same address (domain fronting). Only a brokered rule that
- injects, whose proxy holds each request's `Host` to the name, keeps a tenant to it.
+ injects, whose proxy holds each request's `Host` to the name, keeps a tenant to it. The resolver
+ takes upstream only what a policy names (`network.Fabric.asks`), so a name is a channel only to
+ whoever answers one a policy allows: a wildcard over a domain that strangers can publish under is
+ a way out, by its questions as by its addresses.
- **Every host writes `layers/`.** A layer is content-addressed, create-only and locked, and opened
only through a manifest its writer signed, so a host can add layers nothing reads - and pay for
them - and change none. Under the volumes it writes only its own subtree of each, never the
@@ -61,10 +64,67 @@ What no line keeps yet. Each is a design change, not a patch, which is why it is
which carries Quickwit's own services beside the logs it is sent, and Grafana can write samples
as well as read them. Root on the control plane's machine can drop the table; that root is the
installation already.
-- **I17 and I18 hold against the control plane and the operator, not against the tenant, nor a
- guest.** Root in a guest is anybody the access lets in - its owner and each guest alike: it can
+- **I17, I18 and I19 do not hold against a control plane that uses a host.** It opens a root shell
+ on any host (`OpenHostShellCommand`): both keys are its own, and the host checks only the time and
+ the keys' shape. Emptying the host first, the approval and the recording are the control plane's
+ own code, which one that is taken skips. Root on a host reads and writes every workspace on it,
+ memory included, and is answered by the key service as that host is. Holding the shell to a key
+ the control plane does not have is not built: a lost key would leave a host with no way in during
+ an incident, and it would only narrow a control plane already taken (`CLAUDE.md`).
+- **The owner's word names no disk.** A host takes the spec's volume over the one it held, since a
+ restore changes it, and a word says nothing of the volume. So a control plane that is taken can
+ start a person's workspace on a disk it chose, whose code then runs as that workspace: it logs in
+ as nobody, and pushes and calls through the egress proxy as the owner.
+- **A taken control plane opens every connected person's forge tokens.** It holds the access and
+ refresh tokens sealed under its own key, and with them acts on the forge directly, as each person,
+ within the app's permissions (contents and pull requests, read and write) and the repositories
+ that person reaches where the app is installed. Nothing on a host stands in the way, and the app
+ installed on selected repositories and the forge's branch rules are the only limit. A refresh
+ token it copied can outlive it until the person's authorization ends.
+- **Machines the control plane's role starts.** Auto Scaling asks that role for `ec2:RunInstances`
+ on the runners' launch template, and under that grant a call that names the template and other
+ user data starts the machine with the other user data, tried on AWS on 2026-11-04; holding the
+ instance to `ec2:IsLaunchTemplateResource` did not stop it. User data is not measured, so such a
+ machine might attest as a runner under a document the control plane chose; whether it does has
+ not been tried. It is told, not stopped: a rule emails `security_emails` within minutes of the
+ call.
+- **I17 and I18 hold against an operator, not against the tenant, nor a guest.** Root in a guest is anybody the access lets in - its owner and each guest alike: it can
rewrite the drop-in and the supervisor's answers. Sealing keeps everyone else out; it does not
keep the people let in from the machine they were let into.
+- **A browser session logs in for 12 h with nobody asked.** Code running on the terminal's origin -
+ already trusted with every signature there - or malware in the browser uses its session key
+ without a prompt until the grant ends; ending the session (`#/session`) drops the key, and taking
+ the passkey that signed it out of the keyring ends the grant at every host within `roster.Fresh`.
+ A first access the key signed is read for the workspace's life, held only to having been taken
+ while the grant held (`SetOwnerWord`). A taken control plane holds no key that signs one, but it
+ serves the page that frames the agent's: while a grant holds and the dashboard is open, it can
+ make a workspace in the person's name and frame it, and the session key signs that first access
+ with no prompt and nothing on screen. It gets a word naming the image and team it chose and the
+ installation's audiences, so identity tokens naming the person for a disk it chose; it gets no
+ login, guests, settings or logs (`ownerword.Word.Minimal`) and none of the workspaces that have
+ a word. A link to a terminal of its own got the same for one click, so this was accepted. A script
+ on the dashboard's origin reaches the same, for an image the installation allows.
+- **Taking a passkey out of a keyring locks what it signed.** A workspace whose newest access that
+ passkey or its session key signed lets nobody in until its owner signs it again with another key,
+ one signature per workspace. It is the only remote revocation for a stolen laptop, and most
+ workspaces hang on one passkey, so the page leads its owner to that signature (`enter`). An owner
+ whose keyring started anew cannot sign one, and recovers the workspace instead.
+- **A person's first keyring is whoever signs it first.** A keyring's first version is taken as it
+ is signed: nothing names a person's keys before it. A stolen account, or a taken control plane,
+ can sign one for somebody who has not yet, list the person's passkey beside its own when they
+ register one, and log in to all they make. It holds for anybody whose first keyring was signed
+ before: the chain is then theirs.
+- **A synced passkey is as safe as what syncs it.** Whoever gets into a person's Apple or Google
+ account, or their password manager, holds their passkey and signs as them: the same as the person
+ to every host. It is a third party outside the control plane and the operator, accepted so that a
+ person has one passkey rather than one per browser. A security key stays on the device.
+- **A new browser is one touch from pinning an egress root.** With a synced passkey it no longer
+ waits for another device, and pins the first root offered (`heldRoot`), so a control plane taken
+ before then has it trust its own. The person's way to check is unchanged: `spin-controlplane
+ egress-root`.
+- **What is typed in an overlaid Agent frame reaches T3 Code.** The dashboard frames the agent's page
+ and can lay words over it, as it cannot over the routes that sign: typing goes to the client, as
+ the agent's user. A terminal stays in a tab of its own for this reason, a shell there being root.
- **A taken control plane can start a person's keyring anew.** It does through the account what a
person who lost every key does (`StartKeyringAnew`), and a keyring started anew is somebody no
access or roster signed before names - so it gets into none of their workspaces and none of their
@@ -92,9 +152,17 @@ What no line keeps yet. Each is a design change, not a patch, which is why it is
session, so one that is taken tags it with any team, and it holds every value it reads in the
clear, as it does a sealed one. The tag is only as good as who may tag a secret: a principal of
the account that can tag one `spin-team-id` hands it to that team.
-- **The terminal's code is trusted as the installation's Terraform wrote it.** Whoever can write
- `term.`'s bucket, which the boundary denies every installation role, serves the code that
- holds a session and asks the passkey. It is the release's signed artifact, pinned by version.
+ Its role takes the control plane's credentials from anywhere while they last, so a reference is
+ read off the machine where a sealed value is not: that needs the attested key.
+- **The terminal's code is trusted as the proxy serves it.** `term.`, and the agent's
+ client it frames at `agents.`, are the release's signed files on the proxy image's verity
+ root, under a Caddyfile spin-boot carries, so a control plane that is taken changes neither; a
+ proxy taken at run time serves any code that holds a session and asks the passkey.
+- **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 a control plane that is taken and writes another key publishes
+ nothing of it; a proxy taken at run time publishes any key set. 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 taken at run time signs for any
subject. A self-hosted installation has no key service, and its control plane keeps the key it
@@ -102,7 +170,9 @@ What no line keeps yet. Each is a design change, not a patch, which is why it is
- **A keyring and a roster are trusted from their first version.** A version is signed by a key
the one before trusts, and kept under the bucket's lock in GOVERNANCE, which the account's root
can lift. A user or a team made while the control plane is taken starts a chain whose first
- version the control plane can sign - and a team it makes is its own, which nothing trusts.
+ version the control plane can sign - and a team it makes is its own, which nothing trusts. A version
+ already kept is not written over: on AWS the bucket's policy refuses a write under `rosters/`
+ without `If-None-Match`. A store with no such policy has only the client's word for it.
- **The domain an identity is named under is the control plane's word.** It comes with the host's
policy, and a taken control plane can change it - which a relying party refuses, since the
issuer it names publishes another key set: denial, not a widening. An owner who names
diff --git a/src/content/docs/reference/security/rules.md b/src/content/docs/reference/security/rules.md
index f2495e4..283fd02 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/security/README.md) at `c02ffc3ac`. 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/docs/security/README.md) at `2cfbcd74d`. 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,7 +19,7 @@ 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) - or the browser terminal's, for the calls it makes alone. | `TestAPageOnAnotherSiteCannotSpendTheVisitorsCookie`, `TestEveryHTTPRouteIsMountedBehindAuth`, `TestOnlyTheTerminalIsTheTerminalsOrigin`, `TestEveryTerminalRouteIsOneServed` |
| 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 holds none to the control plane: its host carries its supervisor's stream under the host's own (I8), and revoking the host cuts every stream it carries. | `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` |
+| 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` |
| 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* |
@@ -29,9 +29,9 @@ The rules every entry point must keep, from spin's [`docs/security/README.md`](h
| I14 | A machine's QEMU reaches only what it is handed, and holds only what it is bounded to - a core included. | `TestAProductionInstallationPlacesOnlyOnIsolatedHosts`, `TestEveryIsolationIsAskedOnce`, `TestAHostSaysItKeepsWhatItCanGiveAMachine`, `TestSMTTurnedOnGivesTheMachinesCoresOfTheirOwn`, `TestAHostThatNoLongerKeepsItsMachinesApartIsDrained`; the VM lane's `TestAMachinesQEMUReachesNothingOnTheHostOrAnotherMachine`, `TestAGuestAnswersOnlyTheRunner`, `TestAMachineIsBornWithinItsBounds`; `TestAMachineWithAUserOfItsOwnIsLaunchedApart`, `TestARunnersMachineMergesNoPagesAcrossMachines` |
| 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 the keys of the people a workspace's access names - its owner and the guests they invited - log in to it, each with the installation's certificate over it; the control plane holds no key that logs in and is never an SSH client of a guest (ADR-0030). | the sshd lanes' `TestSSHDLetsInOnlyACertificateOverAKeyTheHostAdmits`, `TestThePasskeyTerminalLogsInToTheClaimedGuest`; `TestAPreparedRootLetsInOnlyByItsHostsWord`, `TestALoginIsWhatTheHostSaysOfTheKey`, `TestALoginIsWhatTheNewestAccessSaysOfTheKey`, `TestAWordAdmitsTheKeysOfThePeopleItNames`, `TestTheNewestKeptWordIsTheHighestItsOwnerSigns`, `TestAMachineIsGivenItsOwnWorkspacesNewestAccess`, `TestTheControlPlaneKeepsOnlyTheOwnersNewerAccess`, `TestAGuestIsLetInByTheAccessThatNamesThem`, `TestATunnelCertifiesOnlyAKeyOfSomebodyTheAccessNames`, `TestOnlyTheListedProceduresAdmitAGuest`, `TestAKeyringStartedAnewIsAnotherChain`; the VM lane's `TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`, `TestAGuestTakenOutStaysOutOfAWorkspaceRestoredToBeforeIt`, `TestTheOwnerLogsInToACloneOfTheirOwnSnapshot`; `TestAGuestIsConnectedToOnlyByAClaimThatHolds`, `TestASessionIsRefusedBeforeTheRelayIsDialled` |
+| I17 | Only the keys of the people a workspace's access names - its owner and the guests they invited - log in to it, each with the installation's certificate over it, or a session key under a grant one of those keys signed, within its 12 h; the control plane holds no key that logs in and is never an SSH client of a guest (ADR-0030). | the sshd lanes' `TestSSHDLetsInOnlyACertificateOverAKeyTheHostAdmits`, `TestThePasskeyTerminalLogsInToTheClaimedGuest`; `TestAPreparedRootLetsInOnlyByItsHostsWord`, `TestALoginIsWhatTheHostSaysOfTheKey`, `TestALoginIsWhatTheNewestAccessSaysOfTheKey`, `TestAWordAdmitsTheKeysOfThePeopleItNames`, `TestTheNewestKeptWordIsTheHighestItsOwnerSigns`, `TestAMachineIsGivenItsOwnWorkspacesNewestAccess`, `TestTheControlPlaneKeepsOnlyTheOwnersNewerAccess`, `TestAGuestIsLetInByTheAccessThatNamesThem`, `TestATunnelCertifiesOnlyAKeyOfSomebodyTheAccessNames`, `TestOnlyTheListedProceduresAdmitAGuest`, `TestAKeyringStartedAnewIsAnotherChain`; `TestAGrantHoldsFromItsIssueToItsEnd`, `TestASessionKeyLogsInWhereTheKeyThatGrantedItWould`, `TestASessionKeySignsOnlyAFirstAccessThatLetsInItsOwner`, `TestASessionKeysFirstWrittenAfterTheOwnersIsPassedOver`, `TestATunnelCertifiesASessionKeyOnlyUnderAGrantThatHolds`, `TestALoginUnderAGrantIsThePersonsWhoseKeySignedIt`, `TestSSHDHandsTheHostTheGrantACertificateCarries`; the VM lane's `TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`, `TestAGuestTakenOutStaysOutOfAWorkspaceRestoredToBeforeIt`, `TestTheOwnerLogsInToACloneOfTheirOwnSnapshot`; `TestAGuestIsConnectedToOnlyByAClaimThatHolds`, `TestASessionIsRefusedBeforeTheRelayIsDialled` |
| I19 | A workspace's identity is signed by the host that runs it, for the machine that asked, named only as its owner's access says - its team, owner, image and parent, fixed by the first - held to the owner's keyring and the team's roster, which people sign, and given only for an audience the access names; the control plane holds no key that signs one (ADR-0030). | `TestATokenIsTheAskingMachinesWorkspace`, `TestATokenIsForAnAudienceItsOwnerNamed`, `TestAWordGivesTheIdentityForTheAudiencesItNames`, `TestARunIsHeldToTheFirstKeyItPresents`, `TestAMachineIsNamedAsItsAccessSays`, `TestAnOwnerWhoseKeyringStartedAnewNamesNoMachine`, `TestABookSaysWhoIsAMemberByTheKeyringTheRosterNames`, `TestAKeyringIsAChainItsOwnKeysSigned`, `TestARosterIsAChainItsOwnersSigned`, `TestOnlyATeamOfOneStartsAnew`, `TestASignatureIsKeptOnlyAsProposedByWhoMaySignIt`, `TestAKeyringStartsAnewWhenItsPersonAsks`, `TestATeamOfMoreThanOneIsSignedAgainByAnotherOwner`, `TestAHostOpensTheKeyThroughAnAttestedDecrypt`, `TestAHostWritesOnlyItsOwnIdentityRecords`; the VM lane's `TestAWorkspacesIdentityIsTheHostsSignatureOfWhatItsOwnerSigned` |
| I18 | What a workspace applies of its owner's settings, what of it leaves it, and which egress root it trusts are its access's: settings only as approved by their digest, the journal and the console only with consent, the root only as named by its digest (ADR-0030). | `TestOnlySettingsTheOwnerApprovedAreApplied`, `TestTheJournalIsSentOnlyWithTheOwnersConsent`, `TestTheEgressRootIsTrustedOnlyByTheOwnersWord`, `TestAWordNamesOnlyTheEgressRootThisMachinePinned`, `TestTheConsoleLeavesTheHostOnlyAsTheNewestAccessSays`, `TestAConsoleLeavesTheHostOnlyWithItsOwnersConsent` |
| 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` |
-| I22 | A header the egress proxy sets for a workspace goes on no request its rule's scope - methods, a path prefix by whole segments - leaves out, and a host that cannot hold a scope is not given one. | `TestAnInjectionIsSetOnlyOnTheRequestsItsScopeNames`, `TestARequestOutsideAnInjectionsScopeIsRefused`, `TestAScopedInjectionGoesOnlyToAHostThatHoldsIt`, `TestAnInjectionCrossesTheWireWhole` |
+| I22 | A header the egress proxy sets for a workspace goes on no request its rule's scope - methods, a path prefix by whole segments - leaves out. | `TestAnInjectionIsSetOnlyOnTheRequestsItsScopeNames`, `TestARequestOutsideAnInjectionsScopeIsRefused`, `TestAnInjectionCrossesTheWireWhole` |
diff --git a/src/content/docs/reference/status/index.md b/src/content/docs/reference/status/index.md
index d89492d..a76be1e 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/ROADMAP.md). The page is written by `task reference` from `spin features` at spin `c02ffc3ac`, 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/docs/ROADMAP.md). The page is written by `task reference` from `spin features` at spin `2cfbcd74d`, 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,116 +17,126 @@ 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vm_test.go#L189), a test of `internal/e2e` | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vm_test.go#L189) |
-| Create, start, stop and delete | [`WorkspaceService/CreateWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_WorkspaceCRUD`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L423) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L379), a test of `internal/e2e` | [`TestATrashedWorkspaceIsWholeWhenItsOwnerUndeletesIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L411)]
[`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L379) |
-| Idle suspend, with a question before it that a keep answers | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L107), a test of `internal/e2e` | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L107) |
-| A workspace that ran as long as the installation allows is suspended | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L69), a test of `internal/e2e` | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L69) |
-| A process inside holds its workspace awake while it lives | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L190), a test of `internal/e2e` | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L190) |
-| Suspend with the processes where they are; a start resumes them | [`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L35), a test of `internal/e2e` | [`TestE2E_ASuspendedWorkspaceResumesWhereItWas`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L160)
[`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L35) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L342), a test of `internal/e2e` | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L342)
[`TestABusyWorkspaceIsLentMemoryAndGivesItBack`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L288), a test of `internal/e2e` | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/background_test.go#L288) |
-| Move a running workspace to another host, its processes as they are | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/hosts_flows_test.go#L65), a test of `internal/e2e` | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/clone_test.go#L64), a test of `internal/e2e` | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/hosts_flows_test.go#L100), a test of `internal/e2e` | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/hosts_flows_test.go#L100) |
-| A workspace outlives its runner | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vm_test.go#L272), a test of `internal/e2e` | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vm_test.go#L272) |
-| Quarantine, by an administrator | [`WorkspaceService/QuarantineWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L209) |
-| Why a workspace is not behaving: status, console, history | [`spin workspace status`](/reference/cli/workspace/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L209)
[`TestE2E_AWorkspacesConsoleIsWhatItsMachinePrinted`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L243) |
-| The owner's shell, environment, editor settings, Taskfile and dotfiles reach the guest | [`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/env_test.go#L23), a test of `internal/e2e` | [`TestTheOwnersSettingsArriveInTheGuest`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/env_test.go#L64)
[`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/env_test.go#L23) |
-| Reset a disk to what it was created from | [`spin workspace reset`](/reference/cli/workspace/) | [`TestE2E_ResetPutsAWorkspaceBackToWhatItWasCreatedFrom`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L1208) |
-| Claude Code and Codex on every workspace's PATH, at the versions the release pins, with their own defaults
*Where the installation fetches them (agents_fetch) or an operator publishes them: no image carries Claude Code, which only Anthropic may distribute.* | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/agentclis_test.go#L20), a test of `internal/e2e` | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/agentclis_test.go#L20) |
+| A machine per workspace: KVM, its own kernel, root, a persistent disk, SSH | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vm_test.go#L189), a test of `internal/e2e` | [`TestAWorkspaceBootsARealGuest`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vm_test.go#L189) |
+| Create, start, stop and delete | [`WorkspaceService/CreateWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_WorkspaceCRUD`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L415) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L379), a test of `internal/e2e` | [`TestATrashedWorkspaceIsWholeWhenItsOwnerUndeletesIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L411)
[`TestAWorkspaceWhoseDaysInTheTrashAreUpIsErasedFromItsHost`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L379) |
+| Idle suspend, with a question before it that a keep answers | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L107), a test of `internal/e2e` | [`TestAWorkspaceIsAskedBeforeItsSuspensionAndAKeepAnswersIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L107) |
+| A workspace that ran as long as the installation allows is suspended | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L69), a test of `internal/e2e` | [`TestAWorkspaceThatRanForAsLongAsAllowedIsSuspended`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L69) |
+| A process inside holds its workspace awake while it lives | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L190), a test of `internal/e2e` | [`TestAProcessHoldsItsWorkspaceAwakeWhileItLives`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L190) |
+| Suspend with the processes where they are; a start resumes them | [`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L35), a test of `internal/e2e` | [`TestE2E_ASuspendedWorkspaceResumesWhereItWas`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L288)
[`TestAWorkspaceNobodyUsesIsSuspendedAndAStartResumesIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L35) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L342), a test of `internal/e2e` | [`TestABusyWorkspaceIsLentCoresAndGivesThemBack`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L342)
[`TestABusyWorkspaceIsLentMemoryAndGivesItBack`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L288), a test of `internal/e2e` | [`TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/background_test.go#L288)
[`TestAResumedWorkspaceIsGivenWhatItIsGrantedWhileItRuns`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/bandwidth_test.go#L118), a test of `internal/e2e` | [`TestAnAdministratorChangesARunningWorkspacesBandwidth`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/bandwidth_test.go#L118)
[`TestAWorkspacesBandwidthHoldsAcrossItsRunsAndItsRunner`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/hosts_flows_test.go#L65), a test of `internal/e2e` | [`TestHostsAMoveTakesARunningWorkspaceToAnotherHost`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/clone_test.go#L64), a test of `internal/e2e` | [`TestACloneHoldsWhatItsSourceWroteAndItsShape`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/hosts_flows_test.go#L100), a test of `internal/e2e` | [`TestHostsAWorkspaceWhoseHostIsGoneIsRecoveredOnAnother`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/hosts_flows_test.go#L100) |
+| A workspace outlives its runner | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vm_test.go#L272), a test of `internal/e2e` | [`TestAWorkspaceOutlivesItsRunner`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vm_test.go#L272) |
+| Quarantine, by an administrator | [`WorkspaceService/QuarantineWorkspace`](/reference/api/workspaceservice/) | [`TestE2E_AQuarantinedWorkspaceIsHeldUntilReleased`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L337)
[`TestE2E_AWorkspacesConsoleIsWhatItsMachinePrinted`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L371) |
+| The owner's shell, environment, editor settings, Taskfile and dotfiles reach the guest | [`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/env_test.go#L23), a test of `internal/e2e` | [`TestTheOwnersSettingsArriveInTheGuest`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/env_test.go#L64)
[`TestAWorkspacesOwnEnvironmentIsInItsOwnersLoginShell`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/env_test.go#L23) |
+| Reset a disk to what it was created from | [`spin workspace reset`](/reference/cli/workspace/) | [`TestE2E_ResetPutsAWorkspaceBackToWhatItWasCreatedFrom`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L1197) |
+| Claude Code, Codex and T3 Code on every workspace's PATH, at the versions the release pins, with their own defaults
*Where the installation fetches them (agents_fetch) or an operator publishes them: no image carries Claude Code, which only Anthropic may distribute.* | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/agentclis_test.go#L20), a test of `internal/e2e` | [`TestTheAgentCLIsAreOnPathWithThePinnedVersions`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/agentclis_test.go#L20) |
+| 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 terminal's agent page that carries the client 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/t3_test.go#L80)
[`TestE2E_TheAgentIsServedOnThisComputerWhileTheSessionIsOpen`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L94)
[`agent.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/agent.spec.ts#L63): a passkey signs the person in, and the agent runs in their workspace from the terminal's page |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/t3_test.go#L80) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/t3_test.go#L80) |
+| The agent's chats listed on the workspace - titles, paths and times, never a message - reported by its supervisor only with the owner's consent | [`WorkspaceService/ListAgentThreads`](/reference/api/workspaceservice/) | [`TestTheAgentsThreadsAreListedOnlyWithTheOwnersConsent`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/t3_test.go#L136) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L97) |
-| A workspace opened at a commit or a pull request, checked out as pr-<n> | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/project_test.go#L91), a test of `internal/e2e` | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/project_test.go#L91)
[`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L97) |
-| Teams, with owner, admin and member | [`spin team member role`](/reference/cli/team/) | [`TestE2E_ATeamRoleIsWhatItsHolderMayDo`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L499) |
-| A team's own identity providers | [`TeamService/CreateIdentityProvider`](/reference/api/teamservice/) | [`TestE2E_ATeamsIdentityProviderIsItsAdminsAndSignsNobodyIn`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L531) |
-| A team's forge app: private repositories cloned and pushed with nothing on the disk | [`spin team forge set`](/reference/cli/team/) | not yet: a real GitHub App and a private repository for the lane to clone and push |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/team_policy_test.go#L24) |
-| Projects: a repository and the shape of the workspace that opens it | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/project_test.go#L27), a test of `internal/e2e` | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/prebuild_test.go#L29), a test of `internal/e2e` | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/prebuild_test.go#L29) |
-| A team's workspaces run on the installation's hosts | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/team_test.go#L20), a test of `internal/e2e` | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/team_test.go#L20) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/project_test.go#L91), a test of `internal/e2e` | [`TestAProjectsWorkspaceOpensAtTheCommitItWasAskedFor`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/project_test.go#L91)
[`TestE2E_AURLOpensTheWorkspaceItNames`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L491) |
+| A team's own identity providers | [`TeamService/CreateIdentityProvider`](/reference/api/teamservice/) | [`TestE2E_ATeamsIdentityProviderIsItsAdminsAndSignsNobodyIn`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L523) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/project_test.go#L27), a test of `internal/e2e` | [`TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/prebuild_test.go#L29), a test of `internal/e2e` | [`TestAProjectOpensAsItsPrebuildMadeIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/prebuild_test.go#L29) |
+| A team's workspaces run on the installation's hosts | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/team_test.go#L20), a test of `internal/e2e` | [`TestATeamsWorkspaceRunsOnTheInstallationsHost`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/clone_test.go#L64)
[`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/compaction_test.go#L19) |
-| Snapshots with memory, resumed with their processes | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/restore_test.go#L175), a test of `internal/e2e` | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/restore_test.go#L175) |
-| Restore a workspace to a snapshot | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/restore_test.go#L29), a test of `internal/e2e` | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/restore_test.go#L29) |
-| A diff of the files that changed between two snapshots | [`TestADiffOfTwoSnapshotsIsWhatChangedBetweenThem`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/diff_test.go#L28), a test of `internal/e2e` | [`TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/diff_test.go#L95) |
-| Share a snapshot with a person or make it public, and create a workspace from it | [`TestACloneOfASnapshotRegistersAsItself`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/clone_test.go#L24), a test of `internal/e2e` | [`TestASharedSnapshotIsCreatedFromByThePersonItIsSharedWith`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/share_test.go#L22) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L46) |
-| A long chain is compacted while the workspace runs | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/compaction_test.go#L19), a test of `internal/e2e` | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/clone_test.go#L64)
[`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/compaction_test.go#L19) |
+| Snapshots with memory, resumed with their processes | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/restore_test.go#L175), a test of `internal/e2e` | [`TestARunningWorkspaceResumesFromASnapshotsMemory`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/restore_test.go#L175) |
+| Restore a workspace to a snapshot | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/restore_test.go#L29), a test of `internal/e2e` | [`TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/restore_test.go#L29)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/diff_test.go#L28), a test of `internal/e2e` | [`TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/diff_test.go#L95)
[`TestE2E_TheQuickstartsDiffIsWhatTheSiteShows`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/clone_test.go#L24), a test of `internal/e2e` | [`TestASharedSnapshotIsCreatedFromByThePersonItIsSharedWith`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/share_test.go#L22) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L171) |
+| A long chain is compacted while the workspace runs | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/compaction_test.go#L19), a test of `internal/e2e` | [`TestADiskSnapshottedPastTheThresholdIsCompactedWhileItRuns`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/retag_test.go#L163), a test of `internal/e2e` | [`TestAWorkspaceNamedNoPolicyReachesNothing`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/retag_test.go#L71)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/retag_test.go#L24) |
-| A policy changed reaches the host that runs the workspace | [`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/retag_test.go#L24), a test of `internal/e2e` | [`TestAnEditedPolicyChangesWhatARunningGuestReaches`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/retag_test.go#L100)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L100) |
-| A workspace trusts the egress root only by its owner's word | [`TestAGuestTrustsTheEgressRootOnlyByItsOwnersWord`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/egress_root_test.go#L20), a test of `internal/e2e` | [`TestAGuestTrustsTheEgressRootOnlyByItsOwnersWord`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/egress_root_test.go#L20) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L792) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L28), a test of `internal/e2e` | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L28) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/egress_process_test.go#L21), a test of `internal/e2e` | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L72), a test of `internal/e2e` | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L100)
[`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L72) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L168), a test of `internal/e2e` | [`TestAWebSocketToABrokeredNameCarriesTheGuestsBytesAndRefusesAPathOutOfScope`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L196)
[`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L168) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L231), a test of `internal/e2e` | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L231)
[`TestEachStreamOfAnHTTP2ConnectionToABrokeredNameCarriesTheRulesHeader`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L251) |
-| Watch where a workspace connects, into its logs | [`spin workspace watch`](/reference/cli/workspace/) | [`TestE2E_AWatchedWorkspacesConnectionsAreInItsLogs`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L264) |
-| An agent the supervisor starts is marked, and reaches only brokered names | [`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/seal_test.go#L21), a test of `internal/e2e` | [`TestAnAgentReachesOnlyBrokeredNames`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/brokered_test.go#L138)
[`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/proxy/lane_test.go#L144)
[`workspace-sign-in.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/retag_test.go#L163), a test of `internal/e2e` | [`TestAWorkspaceNamedNoPolicyReachesNothing`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/retag_test.go#L71)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/resolver_test.go#L76), a test of `internal/e2e` | [`TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/resolver_test.go#L76) |
+| A policy changed reaches the host that runs the workspace | [`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/retag_test.go#L24), a test of `internal/e2e` | [`TestAnEditedPolicyChangesWhatARunningGuestReaches`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/retag_test.go#L100)
[`TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L100) |
+| A workspace trusts the egress root only by its owner's word | [`TestAGuestTrustsTheEgressRootOnlyByItsOwnersWord`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/egress_root_test.go#L20), a test of `internal/e2e` | [`TestAGuestTrustsTheEgressRootOnlyByItsOwnersWord`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/egress_root_test.go#L20) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L784) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L28), a test of `internal/e2e` | [`TestABrokeredRequestIsSeenAsTheProxyReadIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L28) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/egress_process_test.go#L21), a test of `internal/e2e` | [`TestTheEgressProxyIsAProcessOfItsOwnThatTheRunnerStartsAgain`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L72), a test of `internal/e2e` | [`TestAPersonsRuleAndAnAgentsOnOneNameEachArriveWithTheirOwnInjection`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L100)
[`TestABrokeredNameForTheAgentIsTheAgentsAlone`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L72) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L168), a test of `internal/e2e` | [`TestAWebSocketToABrokeredNameCarriesTheGuestsBytesAndRefusesAPathOutOfScope`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L196)
[`TestAWebSocketToABrokeredNameIsCarriedAfterItsHandshake`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L168) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L231), a test of `internal/e2e` | [`TestHTTP2ToABrokeredNameIsTheRealHosts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L231)
[`TestEachStreamOfAnHTTP2ConnectionToABrokeredNameCarriesTheRulesHeader`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L251) |
+| Watch where a workspace connects, into its logs | [`spin workspace watch`](/reference/cli/workspace/) | [`TestE2E_AWatchedWorkspacesConnectionsAreInItsLogs`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L392) |
+| An agent the supervisor starts is marked, and reaches only brokered names | [`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/seal_test.go#L21), a test of `internal/e2e` | [`TestAnAgentReachesOnlyBrokeredNames`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/brokered_test.go#L138)
[`TestARealWorkspaceMarksTheAgentItsSupervisorStarts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/proxy/lane_test.go#L144)
[`workspace-sign-in.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L1195)
[`auth.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/auth.spec.ts#L19): 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L1150) |
-| SSH keys, and plain ssh to spin.<workspace> | [`spin ssh-config`](/reference/cli/ssh-config/) | [`TestE2E_SSHConfigCarriesSSHToAWorkspace`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L663) |
-| Who logs in to a workspace is what its owner signed | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/login_test.go#L259), a test of `internal/e2e` | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/login_test.go#L259) |
-| Guests of a workspace, invited by its owner | [`spin workspace guests`](/reference/cli/workspace/) | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/login_test.go#L259) |
-| A workspace's identity, signed by its host, for the audiences its owner allows | [`TestAWorkspacesIdentityIsTheHostsSignatureOfWhatItsOwnerSigned`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/identity_test.go#L60), a test of `internal/e2e` | [`TestAWorkspacesIdentityIsTheHostsSignatureOfWhatItsOwnerSigned`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/identity_test.go#L60) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vscodehotplug_test.go#L23), a test of `internal/e2e` | [`TestTheCarriedVSCodeServerRunsOfflineAndAnswersOverTheSSHTunnel`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/vscodeserver_test.go#L86)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L178) |
+| Device login for a terminal | [`AuthService/StartDeviceLogin`](/reference/api/authservice/) | [`TestE2E_Logout`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L1184)
[`auth.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/auth.spec.ts#L19): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L1139) |
+| SSH keys, and plain ssh to spin.<workspace> | [`spin ssh-config`](/reference/cli/ssh-config/) | [`TestE2E_SSHConfigCarriesSSHToAWorkspace`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L655) |
+| Who logs in to a workspace is what its owner signed | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/login_test.go#L259), a test of `internal/e2e` | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/login_test.go#L259) |
+| Guests of a workspace, invited by its owner | [`spin workspace guests`](/reference/cli/workspace/) | [`TestOnlyThePeopleTheAccessNamesLogInToAWorkspace`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/login_test.go#L259) |
+| A workspace's identity, signed by its host, for the audiences its owner allows | [`TestAWorkspacesIdentityIsTheHostsSignatureOfWhatItsOwnerSigned`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/identity_test.go#L60), a test of `internal/e2e` | [`TestAWorkspacesIdentityIsTheHostsSignatureOfWhatItsOwnerSigned`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/identity_test.go#L60) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vscodehotplug_test.go#L23), a test of `internal/e2e` | [`TestTheCarriedVSCodeServerRunsOfflineAndAnswersOverTheSSHTunnel`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vscodeserver_test.go#L86)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L306) |
### 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/config_file_test.go#L92) |
-| Resource presets | [`AdminService/CreateResourcePreset`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L422): 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L216): closed, open and a domain on the allowlist decide who registers
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L146): 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L606)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L895): 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/) | [`TestACarryResumesASuspendedWorkspaceAndStopsIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/carry_test.go#L21)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L705): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/config_file_test.go#L92) |
+| Resource presets | [`AdminService/CreateResourcePreset`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L424): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L218): closed, open and a domain on the allowlist decide who registers
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L148): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L598)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L897): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L1044)
[`admin.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L707): 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/runner_token_test.go#L99)
[`TestARegistrationTokenExpiredOrRevokedRegistersNothing`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/runner_token_test.go#L99)
[`TestARegistrationTokenExpiredOrRevokedRegistersNothing`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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 host that reboots brings its workspaces back | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/restart_test.go#L38), a test of `internal/e2e` | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/preemption_test.go#L26), a test of `internal/e2e` | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/writeback_test.go#L28), a test of `internal/e2e` | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L1052) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/hosts_test.go#L420) |
+| A host that reboots brings its workspaces back | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/restart_test.go#L38), a test of `internal/e2e` | [`TestAHostRebootBringsItsWorkspacesBack`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/preemption_test.go#L26), a test of `internal/e2e` | [`TestACloudsReclaimWarningEmptiesTheHostBeforeAnySignal`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/writeback_test.go#L28), a test of `internal/e2e` | [`TestCustomWritebackValuesAreMeasuredOnTheRunner`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L1044) |
### Observability
| Feature | Exists | End to end |
|---|---|---|
-| Platform logs, workspace logs and traces, searched in the product | [`LogService/SearchPlatformLogs`](/reference/api/logservice/) | [`TestAWorkspacesLinesAndTracesAreFoundBySearchingForIt`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/observe_test.go#L22)
[`TestE2E_TheInstallationsLogsAreSearchable`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L954) |
-| Metrics, with Grafana on the control plane | [`AdminService/GetMonitoring`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L1149): 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/dashboard/e2e/admin.spec.ts#L1126): admin reads the installation's dashboards without leaving the page |
-| A workspace's journal and console are collected only with its owner's consent | [`spin workspace logs-consent`](/reference/cli/workspace/) | [`TestE2E_AWorkspacesConsoleIsKeptAndSearchable`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/e2e_test.go#L975) |
+| Platform logs, workspace logs and traces, searched in the product | [`LogService/SearchPlatformLogs`](/reference/api/logservice/) | [`TestAWorkspacesLinesAndTracesAreFoundBySearchingForIt`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/observe_test.go#L130)
[`TestE2E_TheInstallationsLogsAreSearchable`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L946) |
+| Metrics, with Grafana on the control plane | [`AdminService/GetMonitoring`](/reference/api/adminservice/) | [`admin.spec.ts`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L1159): 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/dashboard/e2e/admin.spec.ts#L1128): admin reads the installation's dashboards without leaving the page |
+| A workspace's journal and console are collected only with its owner's consent | [`spin workspace logs-consent`](/reference/cli/workspace/) | [`TestE2E_AWorkspacesConsoleIsKeptAndSearchable`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/e2e_test.go#L967) |
### 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/cmd/cli/journeys_e2e_test.go#L314) |
-| 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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/suspend_test.go#L126), a test of `internal/e2e` | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/internal/e2e/suspend_test.go#L126)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/cmd/cli/journeys_e2e_test.go#L442) |
+| 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/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/suspend_test.go#L126), a test of `internal/e2e` | [`TestAMachineAdoptedWithoutItsReleasesIsSuspendedWithItsDisk`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/suspend_test.go#L126)
[`TestAVSCodeServerOfAnotherCommitIsHandedToTheRunningMachine`](https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/internal/e2e/vscodehotplug_test.go#L23) |
## Partly there
diff --git a/src/content/docs/use/cli.md b/src/content/docs/use/cli.md
index ab51228..d7247d1 100644
--- a/src/content/docs/use/cli.md
+++ b/src/content/docs/use/cli.md
@@ -18,9 +18,8 @@ oras pull ghcr.io/spin-stack/spin-release:v20261027.01-spin-linux-amd64
```
`spin version` prints the release it was built from, and the CLI says it on every request. An
-installation refuses a CLI more than 90 days older than its own release, and says to install the
-installation's. A CLI newer than the installation is served, and says once that what it asks for
-and the installation does not know is ignored. Use a CLI of the installation's release.
+installation refuses a CLI of any other release, and says which it runs. A CLI behind it replaces
+itself with that release and runs the command again; `spin update` does the same when asked.
## Sign in
diff --git a/src/content/docs/use/dashboard.md b/src/content/docs/use/dashboard.md
index 65e2a84..76ead5c 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 v20261026.02, 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 v20261104.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
@@ -17,3 +17,7 @@ The dashboard is at `https://app.`. These screenshots are of spin v20261

+## A workspace's snapshots
+
+
+
diff --git a/src/data/constants.json b/src/data/constants.json
index 54ff78f..d128f16 100644
--- a/src/data/constants.json
+++ b/src/data/constants.json
@@ -3,10 +3,6 @@
"text": "1 GiB",
"source": "spin:internal/storage/qcow/manager.go"
},
- "compatWindow": {
- "text": "90 days",
- "source": "spin:internal/controlplane/compat/compat.go"
- },
"logRetentionDays": {
"text": "30 days",
"source": "spin-terraform-aws:modules/controlplane/variables.tf"
diff --git a/src/data/features.json b/src/data/features.json
index 3e817a3..0765840 100644
--- a/src/data/features.json
+++ b/src/data/features.json
@@ -89,7 +89,19 @@
"proof": "e2e:TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine",
"note": "",
"endToEnd": [
- "go:internal/e2e.TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine"
+ "go:internal/e2e.TestAGrantWhoseTimeIsUpIsGivenBackToTheMachine",
+ "go:internal/e2e.TestAResumedWorkspaceIsGivenWhatItIsGrantedWhileItRuns"
+ ]
+ },
+ {
+ "area": "Workspaces",
+ "name": "An administrator changes a running workspace's bandwidth, its connections open",
+ "state": "built",
+ "proof": "e2e:TestAnAdministratorChangesARunningWorkspacesBandwidth",
+ "note": "",
+ "endToEnd": [
+ "go:internal/e2e.TestAnAdministratorChangesARunningWorkspacesBandwidth",
+ "go:internal/e2e.TestAWorkspacesBandwidthHoldsAcrossItsRunsAndItsRunner"
]
},
{
@@ -237,7 +249,8 @@
"proof": "e2e:TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo",
"note": "",
"endToEnd": [
- "go:internal/e2e.TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo"
+ "go:internal/e2e.TestARestoredWorkspaceBootsOnTheSnapshotItWasRestoredTo",
+ "go:cmd/cli.TestE2E_TheQuickstartsDiffIsWhatTheSiteShows"
]
},
{
@@ -247,7 +260,8 @@
"proof": "e2e:TestADiffOfTwoSnapshotsIsWhatChangedBetweenThem",
"note": "",
"endToEnd": [
- "go:internal/e2e.TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved"
+ "go:internal/e2e.TestADiffOfTwoSnapshotsNamesWhatWasAddedChangedAndRemoved",
+ "go:cmd/cli.TestE2E_TheQuickstartsDiffIsWhatTheSiteShows"
]
},
{
@@ -291,6 +305,16 @@
"go:internal/e2e.TestChangingAWorkspacesPoliciesReachesTheHostThatRunsIt"
]
},
+ {
+ "area": "Network",
+ "name": "A workspace's DNS goes upstream only for what its policy names, so a policy that closes egress closes the tunnel through names",
+ "state": "built",
+ "proof": "e2e:TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames",
+ "note": "",
+ "endToEnd": [
+ "go:internal/e2e.TestAWorkspaceAsksItsResolverOnlyWhatItsPolicyNames"
+ ]
+ },
{
"area": "Network",
"name": "A policy changed reaches the host that runs the workspace",
@@ -499,7 +523,7 @@
},
{
"area": "Workspaces",
- "name": "Claude Code and Codex on every workspace's PATH, at the versions the release pins, with their own defaults",
+ "name": "Claude Code, Codex and T3 Code on every workspace's PATH, at the versions the release pins, with their own defaults",
"state": "built",
"proof": "e2e:TestTheAgentCLIsAreOnPathWithThePinnedVersions",
"note": "Where the installation fetches them (agents_fetch) or an operator publishes them: no image carries Claude Code, which only Anthropic may distribute.",
@@ -507,6 +531,58 @@
"go:internal/e2e.TestTheAgentCLIsAreOnPathWithThePinnedVersions"
]
},
+ {
+ "area": "Workspaces",
+ "name": "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 terminal's agent page that carries the client over a passkey's session",
+ "state": "built",
+ "proof": "cli:agent --port",
+ "note": "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.",
+ "endToEnd": [
+ "go:internal/e2e.TestTheSupervisorRunsTheAgentForItsOwner",
+ "go:cmd/cli.TestE2E_TheAgentIsServedOnThisComputerWhileTheSessionIsOpen",
+ "pw:agent.spec.ts:a passkey signs the person in, and the agent runs in their workspace from the terminal's page"
+ ]
+ },
+ {
+ "area": "Workspaces",
+ "name": "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",
+ "state": "built",
+ "proof": "rpc:spin.vm.v1.AgentService/BeginLogin",
+ "note": "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.",
+ "endToEnd": [
+ "go:internal/e2e.TestTheSupervisorRunsTheAgentForItsOwner"
+ ]
+ },
+ {
+ "area": "Workspaces",
+ "name": "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",
+ "state": "built",
+ "proof": "rpc:spin.vm.v1.AgentService/Start",
+ "note": "That it stops once idle is the sweep's unit test on a clock the test moves: no lane waits half an hour.",
+ "endToEnd": [
+ "go:internal/e2e.TestTheSupervisorRunsTheAgentForItsOwner"
+ ]
+ },
+ {
+ "area": "Workspaces",
+ "name": "The agent's chats listed on the workspace - titles, paths and times, never a message - reported by its supervisor only with the owner's consent",
+ "state": "built",
+ "proof": "rpc:spin.v1.WorkspaceService/ListAgentThreads",
+ "note": "",
+ "endToEnd": [
+ "go:internal/e2e.TestTheAgentsThreadsAreListedOnlyWithTheOwnersConsent"
+ ]
+ },
+ {
+ "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",
+ "state": "built",
+ "proof": "cli:workspace create --agent-home",
+ "note": "Sealed with the workspace's checkpoints, claimed with the launch beside the disk, and moved with the workspace.",
+ "endToEnd": [
+ "go:internal/e2e.TestAnAgentsHomeOutlivesItsWorkspace"
+ ]
+ },
{
"area": "Access",
"name": "Run a command in a workspace, copy files to and from it",
@@ -539,11 +615,27 @@
},
{
"area": "Teams and projects",
- "name": "A team's forge app: private repositories cloned and pushed with nothing on the disk",
+ "name": "Your GitHub account, connected once: private repositories cloned and pushed with nothing on the disk",
"state": "built",
- "proof": "cli:team forge set",
+ "proof": "rpc:spin.v1.ForgeService/CompleteConnect",
"note": "",
- "endToEndGap": "a real GitHub App and a private repository for the lane to clone and push"
+ "endToEndGap": "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"
+ },
+ {
+ "area": "Teams and projects",
+ "name": "Your GitHub account connected from the CLI, with a code entered on GitHub's page",
+ "state": "built",
+ "proof": "cli:git connect",
+ "note": "",
+ "endToEndGap": "the code is entered by a person on GitHub's page: checked by hand once per release"
+ },
+ {
+ "area": "Teams and projects",
+ "name": "A repository picked from the ones your account reaches",
+ "state": "built",
+ "proof": "cli:git repos",
+ "note": "",
+ "endToEndGap": "a real GitHub account the lane can search with"
},
{
"area": "Teams and projects",
@@ -557,7 +649,7 @@
},
{
"area": "Teams and projects",
- "name": "Projects: a repository and the shape of the workspace that opens it",
+ "name": "Projects of the installation: a repository and the shape of the workspace that opens it, shared with everyone or some",
"state": "built",
"proof": "e2e:TestAProjectsWorkspaceHasItsRepositoryCheckedOutAtTheRef",
"note": "",
@@ -634,7 +726,7 @@
"proof": "cli:admin jobs",
"note": "",
"endToEnd": [
- "go:internal/e2e.TestACarryResumesASuspendedWorkspaceAndStopsIt",
+ "go:cmd/cli.TestE2E_Admin",
"pw:admin.spec.ts:admin runs a job by hand and sees how it ended"
]
},
@@ -685,6 +777,16 @@
"note": "",
"endToEndGap": "an autoscaling group to start and stop: the one group is AWS's, and no lane has an account"
},
+ {
+ "area": "Administration",
+ "name": "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",
+ "state": "built",
+ "proof": "cli:admin shell open",
+ "note": "",
+ "endToEnd": [
+ "go:internal/e2e.TestHostsARootShellOnAHostIsRecordedAndItsHostRetired"
+ ]
+ },
{
"area": "Administration",
"name": "A host that reboots brings its workspaces back",
diff --git a/src/data/figures.json b/src/data/figures.json
index 79c5efa..556382b 100644
--- a/src/data/figures.json
+++ b/src/data/figures.json
@@ -1,6 +1,6 @@
{
"datapath": {
- "source": "https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/network/datapath",
+ "source": "https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/network/egress-policy",
+ "source": "https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/docs/network/egress-policy",
"width": 854,
"words": [
"What the forward chain decides, in order",
@@ -157,7 +157,7 @@
]
},
"isolation": {
- "source": "https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/network/isolation",
+ "source": "https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/docs/network/isolation",
"width": 499,
"words": [
"Same address, different segment",
@@ -204,7 +204,7 @@
]
},
"traffic": {
- "source": "https://github.com/spin-stack/spin/blob/c02ffc3acfd56cb242d21547cb177da68a3d4f11/docs/security/traffic",
+ "source": "https://github.com/spin-stack/spin/blob/2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9/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 8e13152..bff5065 100644
--- a/src/data/pricing.json
+++ b/src/data/pricing.json
@@ -1,6 +1,6 @@
{
- "generated": "2026-11-04",
- "published": "2026-11-03T23:57:16Z",
+ "generated": "2026-11-05",
+ "published": "2026-11-04T21:03:05Z",
"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 a05839f..a05ba8e 100644
--- a/src/data/reference.json
+++ b/src/data/reference.json
@@ -1,8 +1,8 @@
{
"spin": {
"installs": "v20261027.01",
- "commit": "c02ffc3acfd56cb242d21547cb177da68a3d4f11",
- "tag": "v20261026.02",
+ "commit": "2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9",
+ "tag": "v20261104.01",
"machine": "v20261026.01"
},
"terraform": {
@@ -13,12 +13,12 @@
"commit": "7e17029f6a087f386a4798af03df6e9623ecfe12"
},
"counts": {
- "built": 75,
+ "built": 85,
"partial": 0,
"notBuilt": 3,
"limits": 17,
- "endToEnd": 71,
- "gap": 4
+ "endToEnd": 79,
+ "gap": 6
},
- "openapiSha256": "2529fc196704cce777802ac15a57f1c461eae9f1ca95b9eef23a25494ac2a726"
+ "openapiSha256": "7550d0c634c6ebaaffb877af8073dbca698aa2c3c0e9f096c7739454c32ca690"
}
diff --git a/src/data/shots.json b/src/data/shots.json
index 33e1dfe..384b883 100644
--- a/src/data/shots.json
+++ b/src/data/shots.json
@@ -1,6 +1,6 @@
{
- "release": "v20261026.02",
- "commit": "c02ffc3acfd56cb242d21547cb177da68a3d4f11",
+ "release": "v20261104.01",
+ "commit": "2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9",
"shots": [
{
"name": "workspaces",
@@ -9,6 +9,10 @@
{
"name": "workspace",
"test": "a running workspace"
+ },
+ {
+ "name": "snapshots",
+ "test": "a workspace's snapshots"
}
]
}
diff --git a/tools/refgen/constants.go b/tools/refgen/constants.go
index 993c2ed..5a83c74 100644
--- a/tools/refgen/constants.go
+++ b/tools/refgen/constants.go
@@ -22,8 +22,6 @@ import (
type constants struct {
// RotateAtBytes is the size a volume's top layer commits at whatever its age.
RotateAtBytes codeValue `json:"rotateAtBytes"`
- // CompatWindow is how much older than its control plane a runner may be.
- CompatWindow codeValue `json:"compatWindow"`
// LogRetentionDays is how long CloudWatch keeps the flow log and the boot logs by default.
LogRetentionDays codeValue `json:"logRetentionDays"`
}
@@ -36,7 +34,6 @@ type codeValue struct {
const (
rotatePath = "internal/storage/qcow/manager.go"
- compatPath = "internal/controlplane/compat/compat.go"
logsModule = "modules/controlplane"
)
@@ -51,13 +48,6 @@ func spinConstants(c *constants, spin string) error {
return fmt.Errorf("%s: DefaultRotateAtBytes is no int64", rotatePath)
}
c.RotateAtBytes = codeValue{bytesText(n), "spin:" + rotatePath}
- if v, err = goConstant(filepath.Join(spin, compatPath), "Window"); err != nil {
- return err
- }
- if n, ok = constant.Int64Val(v); !ok {
- return fmt.Errorf("%s: Window is no duration", compatPath)
- }
- c.CompatWindow = codeValue{durationText(time.Duration(n)), "spin:" + compatPath}
return nil
}
diff --git a/tools/refgen/status.go b/tools/refgen/status.go
index e32a53d..7b4fd45 100644
--- a/tools/refgen/status.go
+++ b/tools/refgen/status.go
@@ -235,14 +235,17 @@ func statusPage(rows []feature, t tests, commit string) (map[string][]byte, erro
return map[string][]byte{filepath.Join("status", "index.md"): []byte(b.String())}, nil
}
-// proof is a proof as the page shows it: the procedure linked to its service's page, the
-// command linked to its page, the test by name.
+// proof is a proof as the page shows it: a procedure of spin.v1 linked to its service's page, one of
+// an API the site has no pages for by name, the command linked to its page, the test by name.
func proof(p string) string {
kind, what, _ := strings.Cut(p, ":")
switch kind {
case "rpc":
service, method, _ := strings.Cut(what, "/")
short := service[strings.LastIndex(service, ".")+1:]
+ if !strings.HasPrefix(service, "spin.v1.") {
+ return fmt.Sprintf("`%s/%s`", short, method)
+ }
return fmt.Sprintf("[`%s/%s`](/reference/api/%s/)", short, method, strings.ToLower(short))
case "cli":
top := strings.Fields(what)[0]
diff --git a/tools/refgen/status_test.go b/tools/refgen/status_test.go
index 7d2b8d5..ab16668 100644
--- a/tools/refgen/status_test.go
+++ b/tools/refgen/status_test.go
@@ -122,3 +122,15 @@ func TestTheStatusPageFailsOnATestSpinDoesNotHave(t *testing.T) {
})
}
}
+
+// Only spin.v1 has pages: a link to a page of another API is one the build did not produce.
+func TestAProofLinksAProcedureOnlyWhereTheAPIHasPages(t *testing.T) {
+ for p, want := range map[string]string{
+ "rpc:spin.v1.WorkspaceService/GetWorkspace": "[`WorkspaceService/GetWorkspace`](/reference/api/workspaceservice/)",
+ "rpc:spin.vm.v1.AgentService/Run": "`AgentService/Run`",
+ } {
+ if got := proof(p); got != want {
+ t.Errorf("proof(%q) = %s, want %s", p, got, want)
+ }
+ }
+}
diff --git a/versions.yaml b/versions.yaml
index b51d9a8..ed975cf 100644
--- a/versions.yaml
+++ b/versions.yaml
@@ -7,8 +7,8 @@
- name: spin
kind: git
source: https://github.com/spin-stack/spin
- version: v20261026.02
- pin: c02ffc3acfd56cb242d21547cb177da68a3d4f11
+ version: v20261104.01
+ pin: 2cfbcd74db9e1b599cc29f342d1d35ce1dbe1be9
track: github-release
note: >-
The release the site documents (D2: the newest, exactly). The daily sync moves it to the next