diff --git a/spec/openapi.dashboard-api.yaml b/spec/openapi.dashboard-api.yaml index 6e15f856b..ae3578c10 100644 --- a/spec/openapi.dashboard-api.yaml +++ b/spec/openapi.dashboard-api.yaml @@ -194,13 +194,7 @@ components: description: Sort column and direction. schema: type: string - enum: - [ - created_at_asc, - created_at_desc, - updated_at_asc, - updated_at_desc, - ] + enum: [created_at_asc, created_at_desc, updated_at_asc, updated_at_desc] default: created_at_desc tag_assignments_cursor: name: cursor @@ -471,7 +465,7 @@ components: nextCursor: type: string nullable: true - description: Cursor to pass to the next list request, or `null` if there is no next page. + description: Cursor to pass to the next list request, or `null` if there is no next page. BuildStatusItem: type: object @@ -566,6 +560,7 @@ components: - memoryMB - diskSizeMB - retentionExpired + - eventsRetentionExpired properties: templateID: type: string @@ -597,7 +592,10 @@ components: $ref: "#/components/schemas/DiskSizeMB" retentionExpired: type: boolean - description: Whether the sandbox ended more than the retention window ago, so its monitoring, events, and logs data is no longer available + description: Whether the sandbox ended more than the fixed retention window ago, so its monitoring and logs data is no longer available + eventsRetentionExpired: + type: boolean + description: Whether the sandbox ended more than the team's events retention window ago, so its events data is no longer available HealthResponse: type: object @@ -617,6 +615,7 @@ components: - maxVcpu - maxRamMb - diskMb + - eventsTtlDays properties: maxLengthHours: type: integer @@ -636,6 +635,9 @@ components: diskMb: type: integer format: int32 + eventsTtlDays: + type: integer + format: int32 UserTeam: type: object diff --git a/spec/openapi.infra.yaml b/spec/openapi.infra.yaml index ee4fcb539..9c7121bb6 100644 --- a/spec/openapi.infra.yaml +++ b/spec/openapi.infra.yaml @@ -822,6 +822,39 @@ components: resume); auto-resume, which can be triggered by arbitrary traffic, refuses such a sandbox. Defaults to true. + SandboxForkRequest: + type: object + properties: + timeout: + type: integer + format: int32 + minimum: 0 + default: 15 + description: Time to live for the new forked sandboxes in seconds. + count: + type: integer + format: int32 + minimum: 1 + maximum: 100 + default: 1 + description: >- + Number of forked sandboxes to create. All forks boot from the same + snapshot, so the snapshot is captured once regardless of count. + Each fork succeeds or fails independently; the outcome of each is + reported in its entry of the response list. + + SandboxForkResult: + type: object + description: >- + Result of one requested fork. Exactly one of sandbox or error is set: + sandbox when the fork started successfully, error when it failed to + start. + properties: + sandbox: + $ref: "#/components/schemas/Sandbox" + error: + $ref: "#/components/schemas/Error" + TeamMetric: description: Team metric with timestamp required: @@ -1549,6 +1582,10 @@ components: - cpuCount - memoryTotalBytes - disks + - hugePagesTotal + - hugePagesUsed + - hugePagesReserved + - hugePageSizeBytes properties: allocatedCPU: type: integer @@ -1574,6 +1611,22 @@ components: type: integer format: uint64 description: Total node memory in bytes + hugePagesTotal: + type: integer + format: uint64 + description: Total number of preallocated hugepages on the node + hugePagesUsed: + type: integer + format: uint64 + description: Number of hugepages in use (total - free) + hugePagesReserved: + type: integer + format: uint64 + description: Number of reserved hugepages (committed but not yet faulted) + hugePageSizeBytes: + type: integer + format: uint64 + description: Size of a single hugepage in bytes disks: type: array description: Detailed metrics for each disk/mount point @@ -2512,6 +2565,52 @@ paths: "500": $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/fork: + post: + summary: Fork sandbox + description: >- + Fork the sandbox: checkpoint the running sandbox in place (it is + briefly paused, snapshotted with its full memory state, and resumed on + its node, keeping its ID and expiration untouched) and create count + new sandboxes from that snapshot. Returns one result per requested + fork, each carrying either the created sandbox or the error that + prevented it from starting. A non-201 status means the request failed + before any fork was attempted. + tags: [sandboxes] + security: + - ApiKeyAuth: [] + - AuthProviderBearerAuth: [] + AuthProviderTeamAuth: [] + - AdminApiKeyAuth: [] + AdminTeamAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/SandboxForkRequest" + responses: + "201": + description: >- + The sandbox was snapshotted and the forks were attempted; each + entry reports one fork's outcome + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/SandboxForkResult" + "409": + $ref: "#/components/responses/409" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/connect: post: summary: Connect sandbox @@ -2691,6 +2790,12 @@ paths: schema: type: string description: Filter snapshots by source sandbox ID + - name: name + in: query + description: Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). + required: false + schema: + type: string - $ref: "#/components/parameters/paginationLimit" - $ref: "#/components/parameters/paginationNextToken" responses: diff --git a/src/core/shared/contracts/dashboard-api.types.ts b/src/core/shared/contracts/dashboard-api.types.ts index 9d581f35f..1e30823fa 100644 --- a/src/core/shared/contracts/dashboard-api.types.ts +++ b/src/core/shared/contracts/dashboard-api.types.ts @@ -1280,8 +1280,10 @@ export interface components { cpuCount: components['schemas']['CPUCount'] memoryMB: components['schemas']['MemoryMB'] diskSizeMB: components['schemas']['DiskSizeMB'] - /** @description Whether the sandbox ended more than the retention window ago, so its monitoring, events, and logs data is no longer available */ + /** @description Whether the sandbox ended more than the fixed retention window ago, so its monitoring and logs data is no longer available */ retentionExpired: boolean + /** @description Whether the sandbox ended more than the team's events retention window ago, so its events data is no longer available */ + eventsRetentionExpired: boolean } HealthResponse: { /** @description Human-readable health check result. */ @@ -1300,6 +1302,8 @@ export interface components { maxRamMb: number /** Format: int32 */ diskMb: number + /** Format: int32 */ + eventsTtlDays: number } UserTeam: { /** Format: uuid */ diff --git a/src/core/shared/contracts/infra-api.types.ts b/src/core/shared/contracts/infra-api.types.ts index f3f508a50..6afcb5dd0 100644 --- a/src/core/shared/contracts/infra-api.types.ts +++ b/src/core/shared/contracts/infra-api.types.ts @@ -676,6 +676,55 @@ export interface paths { patch?: never trace?: never } + '/sandboxes/{sandboxID}/fork': { + parameters: { + query?: never + header?: never + path?: never + cookie?: never + } + get?: never + put?: never + /** + * Fork sandbox + * @description Fork the sandbox: checkpoint the running sandbox in place (it is briefly paused, snapshotted with its full memory state, and resumed on its node, keeping its ID and expiration untouched) and create count new sandboxes from that snapshot. Returns one result per requested fork, each carrying either the created sandbox or the error that prevented it from starting. A non-201 status means the request failed before any fork was attempted. + */ + post: { + parameters: { + query?: never + header?: never + path: { + sandboxID: components['parameters']['sandboxID'] + } + cookie?: never + } + requestBody?: { + content: { + 'application/json': components['schemas']['SandboxForkRequest'] + } + } + responses: { + /** @description The sandbox was snapshotted and the forks were attempted; each entry reports one fork's outcome */ + 201: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['SandboxForkResult'][] + } + } + 401: components['responses']['401'] + 404: components['responses']['404'] + 409: components['responses']['409'] + 500: components['responses']['500'] + } + } + delete?: never + options?: never + head?: never + patch?: never + trace?: never + } '/sandboxes/{sandboxID}/connect': { parameters: { query?: never @@ -936,6 +985,8 @@ export interface paths { parameters: { query?: { sandboxID?: string + /** @description Filter snapshots by name or ID, optionally tag-qualified (e.g. "my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). */ + name?: string /** @description Maximum number of items to return per page */ limit?: components['parameters']['paginationLimit'] /** @description Cursor to start the list from */ @@ -2842,6 +2893,25 @@ export interface components { */ memory: boolean } + SandboxForkRequest: { + /** + * Format: int32 + * @description Time to live for the new forked sandboxes in seconds. + * @default 15 + */ + timeout: number + /** + * Format: int32 + * @description Number of forked sandboxes to create. All forks boot from the same snapshot, so the snapshot is captured once regardless of count. Each fork succeeds or fails independently; the outcome of each is reported in its entry of the response list. + * @default 1 + */ + count: number + } + /** @description Result of one requested fork. Exactly one of sandbox or error is set: sandbox when the fork started successfully, error when it failed to start. */ + SandboxForkResult: { + sandbox?: components['schemas']['Sandbox'] + error?: components['schemas']['Error'] + } /** @description Team metric with timestamp */ TeamMetric: { /** @@ -3325,6 +3395,26 @@ export interface components { * @description Total node memory in bytes */ memoryTotalBytes: number + /** + * Format: uint64 + * @description Total number of preallocated hugepages on the node + */ + hugePagesTotal: number + /** + * Format: uint64 + * @description Number of hugepages in use (total - free) + */ + hugePagesUsed: number + /** + * Format: uint64 + * @description Number of reserved hugepages (committed but not yet faulted) + */ + hugePagesReserved: number + /** + * Format: uint64 + * @description Size of a single hugepage in bytes + */ + hugePageSizeBytes: number /** @description Detailed metrics for each disk/mount point */ disks: components['schemas']['DiskMetrics'][] }