diff --git a/apps/api-client-go/api/openapi.yaml b/apps/api-client-go/api/openapi.yaml index 4b353ee6b..7a4f572c9 100644 --- a/apps/api-client-go/api/openapi.yaml +++ b/apps/api-client-go/api/openapi.yaml @@ -1642,7 +1642,7 @@ paths: minimum: 1 type: number style: form - - description: "Filter by partial Box ID, internal UUID, or name match" + - description: Filter by partial Box ID or name match explode: true in: query name: id @@ -1794,7 +1794,6 @@ paths: default: createdAt enum: - id - - boxId - name - state - region @@ -6350,8 +6349,7 @@ components: type: object Box: example: - id: fd955d93-e74a-48e7-9f2d-fcbe6dd9e920 - boxId: aB3cD4eF5gH6 + id: aB3cD4eF5gH6 organizationId: organization123 name: MyBox user: boxlite @@ -6389,11 +6387,7 @@ components: toolboxProxyUrl: https://proxy.app.boxlite.io/toolbox properties: id: - description: The internal UUID of the box - example: fd955d93-e74a-48e7-9f2d-fcbe6dd9e920 - type: string - boxId: - description: The public Box ID shown to users and SDK clients + description: The public 12-character Box ID example: aB3cD4eF5gH6 type: string organizationId: @@ -6521,7 +6515,6 @@ components: example: https://proxy.app.boxlite.io/toolbox type: string required: - - boxId - cpu - disk - env @@ -6540,8 +6533,7 @@ components: PaginatedBoxes: example: items: - - id: fd955d93-e74a-48e7-9f2d-fcbe6dd9e920 - boxId: aB3cD4eF5gH6 + - id: aB3cD4eF5gH6 organizationId: organization123 name: MyBox user: boxlite @@ -6577,8 +6569,7 @@ components: daemonVersion: 1.0.0 runnerId: runner123 toolboxProxyUrl: https://proxy.app.boxlite.io/toolbox - - id: fd955d93-e74a-48e7-9f2d-fcbe6dd9e920 - boxId: aB3cD4eF5gH6 + - id: aB3cD4eF5gH6 organizationId: organization123 name: MyBox user: boxlite @@ -8012,7 +8003,6 @@ components: AdminBoxItem: example: id: box_abc123 - boxId: abc123XYZ organizationId: org_xyz state: started runnerId: runner-uuid @@ -8030,10 +8020,6 @@ components: description: Box ID example: box_abc123 type: string - boxId: - description: Public box ID shown to users - example: abc123XYZ - type: string organizationId: description: Organization ID example: org_xyz @@ -9356,7 +9342,6 @@ components: value: 0.8008281904610115 boxes: - id: box_abc123 - boxId: abc123XYZ organizationId: org_xyz state: started runnerId: runner-uuid @@ -9370,7 +9355,6 @@ components: orgName: Alice Personal personal: true - id: box_abc123 - boxId: abc123XYZ organizationId: org_xyz state: started runnerId: runner-uuid diff --git a/apps/api-client-go/api_box.go b/apps/api-client-go/api_box.go index 8e20a9213..8dae8b5f6 100644 --- a/apps/api-client-go/api_box.go +++ b/apps/api-client-go/api_box.go @@ -1968,7 +1968,7 @@ func (r BoxAPIListBoxesPaginatedRequest) Limit(limit float32) BoxAPIListBoxesPag return r } -// Filter by partial Box ID, internal UUID, or name match +// Filter by partial Box ID or name match func (r BoxAPIListBoxesPaginatedRequest) Id(id string) BoxAPIListBoxesPaginatedRequest { r.id = &id return r diff --git a/apps/api-client-go/model_admin_box_item.go b/apps/api-client-go/model_admin_box_item.go index 779601676..a3f724e7e 100644 --- a/apps/api-client-go/model_admin_box_item.go +++ b/apps/api-client-go/model_admin_box_item.go @@ -23,8 +23,6 @@ var _ MappedNullable = &AdminBoxItem{} type AdminBoxItem struct { // Box ID Id string `json:"id"` - // Public box ID shown to users - BoxId *string `json:"boxId,omitempty"` // Organization ID OrganizationId string `json:"organizationId"` State BoxState `json:"state"` @@ -89,38 +87,6 @@ func (o *AdminBoxItem) SetId(v string) { o.Id = v } -// GetBoxId returns the BoxId field value if set, zero value otherwise. -func (o *AdminBoxItem) GetBoxId() string { - if o == nil || IsNil(o.BoxId) { - var ret string - return ret - } - return *o.BoxId -} - -// GetBoxIdOk returns a tuple with the BoxId field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *AdminBoxItem) GetBoxIdOk() (*string, bool) { - if o == nil || IsNil(o.BoxId) { - return nil, false - } - return o.BoxId, true -} - -// HasBoxId returns a boolean if a field has been set. -func (o *AdminBoxItem) HasBoxId() bool { - if o != nil && !IsNil(o.BoxId) { - return true - } - - return false -} - -// SetBoxId gets a reference to the given string and assigns it to the BoxId field. -func (o *AdminBoxItem) SetBoxId(v string) { - o.BoxId = &v -} - // GetOrganizationId returns the OrganizationId field value func (o *AdminBoxItem) GetOrganizationId() string { if o == nil { @@ -316,9 +282,6 @@ func (o AdminBoxItem) MarshalJSON() ([]byte, error) { func (o AdminBoxItem) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} toSerialize["id"] = o.Id - if !IsNil(o.BoxId) { - toSerialize["boxId"] = o.BoxId - } toSerialize["organizationId"] = o.OrganizationId toSerialize["state"] = o.State if !IsNil(o.RunnerId) { @@ -379,7 +342,6 @@ func (o *AdminBoxItem) UnmarshalJSON(data []byte) (err error) { if err = json.Unmarshal(data, &additionalProperties); err == nil { delete(additionalProperties, "id") - delete(additionalProperties, "boxId") delete(additionalProperties, "organizationId") delete(additionalProperties, "state") delete(additionalProperties, "runnerId") diff --git a/apps/api-client-go/model_box.go b/apps/api-client-go/model_box.go index eb9f422e2..67a9326c7 100644 --- a/apps/api-client-go/model_box.go +++ b/apps/api-client-go/model_box.go @@ -21,10 +21,8 @@ var _ MappedNullable = &Box{} // Box struct for Box type Box struct { - // The internal UUID of the box + // The public 12-character Box ID Id string `json:"id"` - // The public Box ID shown to users and SDK clients - BoxId string `json:"boxId"` // The organization ID of the box OrganizationId string `json:"organizationId"` // The name of the box @@ -89,10 +87,9 @@ type _Box Box // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewBox(id string, boxId string, organizationId string, name string, user string, env map[string]string, labels map[string]string, public bool, networkBlockAll bool, target string, cpu float32, gpu float32, memory float32, disk float32, toolboxProxyUrl string) *Box { +func NewBox(id string, organizationId string, name string, user string, env map[string]string, labels map[string]string, public bool, networkBlockAll bool, target string, cpu float32, gpu float32, memory float32, disk float32, toolboxProxyUrl string) *Box { this := Box{} this.Id = id - this.BoxId = boxId this.OrganizationId = organizationId this.Name = name this.User = user @@ -141,30 +138,6 @@ func (o *Box) SetId(v string) { o.Id = v } -// GetBoxId returns the BoxId field value -func (o *Box) GetBoxId() string { - if o == nil { - var ret string - return ret - } - - return o.BoxId -} - -// GetBoxIdOk returns a tuple with the BoxId field value -// and a boolean to check if the value has been set. -func (o *Box) GetBoxIdOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.BoxId, true -} - -// SetBoxId sets field value -func (o *Box) SetBoxId(v string) { - o.BoxId = v -} - // GetOrganizationId returns the OrganizationId field value func (o *Box) GetOrganizationId() string { if o == nil { @@ -939,7 +912,6 @@ func (o Box) MarshalJSON() ([]byte, error) { func (o Box) ToMap() (map[string]interface{}, error) { toSerialize := map[string]interface{}{} toSerialize["id"] = o.Id - toSerialize["boxId"] = o.BoxId toSerialize["organizationId"] = o.OrganizationId toSerialize["name"] = o.Name toSerialize["user"] = o.User @@ -1009,7 +981,6 @@ func (o *Box) UnmarshalJSON(data []byte) (err error) { // that every required field exists as a key in the generic map. requiredProperties := []string{ "id", - "boxId", "organizationId", "name", "user", @@ -1053,7 +1024,6 @@ func (o *Box) UnmarshalJSON(data []byte) (err error) { if err = json.Unmarshal(data, &additionalProperties); err == nil { delete(additionalProperties, "id") - delete(additionalProperties, "boxId") delete(additionalProperties, "organizationId") delete(additionalProperties, "name") delete(additionalProperties, "user") diff --git a/apps/api/src/admin/dto/admin-overview.dto.ts b/apps/api/src/admin/dto/admin-overview.dto.ts index bfd389ac9..f985316f9 100644 --- a/apps/api/src/admin/dto/admin-overview.dto.ts +++ b/apps/api/src/admin/dto/admin-overview.dto.ts @@ -109,9 +109,6 @@ export class AdminBoxItemDto { @ApiProperty({ description: 'Box ID', example: 'box_abc123' }) id: string - @ApiPropertyOptional({ description: 'Public box ID shown to users', example: 'abc123XYZ' }) - boxId?: string - @ApiProperty({ description: 'Organization ID', example: 'org_xyz' }) organizationId: string diff --git a/apps/api/src/admin/services/observability.service.spec.ts b/apps/api/src/admin/services/observability.service.spec.ts index 859e60460..f1f9e7f8e 100644 --- a/apps/api/src/admin/services/observability.service.spec.ts +++ b/apps/api/src/admin/services/observability.service.spec.ts @@ -398,7 +398,6 @@ describe('AdminObservabilityService', () => { overviewService.listBoxes.mockResolvedValue([ { id: 'box-1', - boxId: 'public-box-1', organizationId: 'org-1', state: 'started', runnerId: 'runner-1', @@ -408,7 +407,6 @@ describe('AdminObservabilityService', () => { }, { id: 'box-2', - boxId: 'public-box-2', organizationId: 'org-1', state: 'started', runnerId: 'runner-2', @@ -429,24 +427,23 @@ describe('AdminObservabilityService', () => { expect(result.correlation).toMatchObject({ traceIds: ['trace-box-1'], orgIds: ['org-1'], - boxIds: ['box-1', 'public-box-1'], + boxIds: ['box-1'], runnerIds: ['runner-1'], machineIds: ['runner-1'], serviceNames: ['box-box-1'], }) expect(result.boxes.map((box) => box.id)).toEqual(['box-1']) - expect(result.boxes.map((box) => box.boxId)).toEqual(['public-box-1']) expect(result.runners.map((runner) => runner.id)).toEqual(['runner-1']) expect(result.machines.map((machine) => machine.host)).toEqual(['runner-1']) expect(cloudWatchLogReader.getRelatedLogs).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ - boxIds: ['box-1', 'public-box-1'], + boxIds: ['box-1'], }), ) expect(s3ObjectReader.listRelatedObjects).toHaveBeenCalledWith( expect.objectContaining({ - boxIds: ['box-1', 'public-box-1'], + boxIds: ['box-1'], }), ) }) @@ -479,7 +476,6 @@ describe('AdminObservabilityService', () => { overviewService.listBoxes.mockResolvedValue([ { id: 'box-1', - boxId: 'public-box-1', organizationId: 'org-1', state: 'started', runnerId: 'runner-1', @@ -592,8 +588,7 @@ describe('AdminObservabilityService', () => { }) overviewService.listBoxes.mockResolvedValue([ { - id: 'box-internal-1', - boxId: 'box-1', + id: 'box-1', organizationId: 'org-1', state: 'started', runnerId: 'runner-1', @@ -675,7 +670,7 @@ describe('AdminObservabilityService', () => { traceIds: expect.arrayContaining(['trace-1']), orgIds: expect.arrayContaining(['org-1']), userIds: expect.arrayContaining(['user-1']), - boxIds: expect.arrayContaining(['box-1', 'box-internal-1']), + boxIds: expect.arrayContaining(['box-1']), runnerIds: expect.arrayContaining(['runner-1']), machineIds: expect.arrayContaining(['machine-1']), requestIds: expect.arrayContaining(['req-1']), @@ -687,7 +682,7 @@ describe('AdminObservabilityService', () => { expect(result.traceSpans).toHaveLength(1) expect(result.logs).toHaveLength(2) expect(result.metrics.series).toHaveLength(1) - expect(result.boxes.map((box) => box.id)).toEqual(['box-internal-1']) + expect(result.boxes.map((box) => box.id)).toEqual(['box-1']) expect(result.runners.map((runner) => runner.id)).toEqual(['runner-1']) expect(result.machines.map((machine) => machine.host)).toEqual(['machine-1']) expect(result.auditLogs.map((log) => log.id)).toEqual(['audit-1']) @@ -771,22 +766,22 @@ describe('AdminObservabilityService', () => { }) expect(result.operations).toEqual( expect.arrayContaining([ - expect.objectContaining({ id: 'recover:box-internal-1', state: 'disabled' }), + expect.objectContaining({ id: 'recover:box-1', state: 'disabled' }), expect.objectContaining({ id: 'cordon:runner-1', state: 'enabled' }), expect.objectContaining({ id: 'drain:runner-1', state: 'enabled' }), - expect.objectContaining({ id: 'resize:box-internal-1', state: 'request_only' }), + expect.objectContaining({ id: 'resize:box-1', state: 'request_only' }), ]), ) expect(cloudWatchLogReader.getRelatedLogs).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ traceIds: ['trace-1'], - boxIds: ['box-1', 'box-internal-1'], + boxIds: ['box-1'], }), ) expect(s3ObjectReader.listRelatedObjects).toHaveBeenCalledWith( expect.objectContaining({ - boxIds: ['box-1', 'box-internal-1'], + boxIds: ['box-1'], executionIds: ['exec-1'], }), ) @@ -808,8 +803,7 @@ describe('AdminObservabilityService', () => { overviewService.listBoxes.mockResolvedValue([ { - id: 'box-internal-1', - boxId: 'box-public-1', + id: 'box-public-1', organizationId: 'org-1', state: 'stopped', runnerId: 'runner-1', @@ -870,9 +864,9 @@ describe('AdminObservabilityService', () => { machineId: 'runner-1', }) - expect(result.auditLogs.map((log) => log.id)).toEqual(['audit-prefixed-box', 'audit-prefixed-box-internal']) + expect(result.auditLogs.map((log) => log.id)).toEqual(['audit-prefixed-box']) expect(result.sources).toEqual( - expect.arrayContaining([expect.objectContaining({ source: 'audit', state: 'available', count: 2 })]), + expect.arrayContaining([expect.objectContaining({ source: 'audit', state: 'available', count: 1 })]), ) }) @@ -886,8 +880,7 @@ describe('AdminObservabilityService', () => { overviewService.listBoxes.mockResolvedValue([ { - id: 'box-internal-1', - boxId: 'box-1', + id: 'box-1', organizationId: 'org-1', state: 'started', runnerId: 'runner-1', @@ -932,7 +925,7 @@ describe('AdminObservabilityService', () => { title: 'User user-1', identifiers: expect.objectContaining({ userId: 'user-1', orgId: 'org-1' }), }) - expect(userResult.boxes.map((box) => box.id)).toEqual(['box-internal-1']) + expect(userResult.boxes.map((box) => box.id)).toEqual(['box-1']) expect(userResult.auditLogs.map((log) => log.id)).toEqual(['audit-user-actor']) expect(userResult.externalLinks.clickstack.query).toContain('boxlite.user_id') expect(userResult.commands.api).toContain('userId=user-1') diff --git a/apps/api/src/admin/services/observability.service.ts b/apps/api/src/admin/services/observability.service.ts index f726beedd..4eb068c69 100644 --- a/apps/api/src/admin/services/observability.service.ts +++ b/apps/api/src/admin/services/observability.service.ts @@ -626,7 +626,7 @@ export class AdminObservabilityService { return { ...base, type: 'box', - title: box.boxId ? `Box ${box.boxId}` : `Box ${box.id}`, + title: `Box ${box.id}`, subtitle: box.id, state: box.state, owner: box.owner?.email || box.owner?.name, @@ -1519,7 +1519,6 @@ export class AdminObservabilityService { for (const box of boxes) { this.addUnique(correlation.orgIds, box.organizationId) this.addUnique(correlation.boxIds, box.id) - this.addUnique(correlation.boxIds, box.boxId) this.addUnique(correlation.runnerIds, box.runnerId) } @@ -1676,7 +1675,7 @@ export class AdminObservabilityService { } private matchesBox(box: AdminBoxItemDto, correlation: AdminObservabilityCorrelationDto): boolean { - if (correlation.boxIds.includes(box.id) || (box.boxId && correlation.boxIds.includes(box.boxId))) { + if (correlation.boxIds.includes(box.id)) { return true } diff --git a/apps/api/src/admin/services/overview.service.ts b/apps/api/src/admin/services/overview.service.ts index 8c700ea97..b30f6962b 100644 --- a/apps/api/src/admin/services/overview.service.ts +++ b/apps/api/src/admin/services/overview.service.ts @@ -116,7 +116,6 @@ export class AdminOverviewService { return boxes.map((s) => ({ id: s.id, - boxId: s.boxId, organizationId: s.organizationId, state: s.state, runnerId: s.runnerId, diff --git a/apps/api/src/box/dto/box.dto.spec.ts b/apps/api/src/box/dto/box.dto.spec.ts index add7422ea..e6c1f2916 100644 --- a/apps/api/src/box/dto/box.dto.spec.ts +++ b/apps/api/src/box/dto/box.dto.spec.ts @@ -8,7 +8,7 @@ import { Box } from '../entities/box.entity' import { BoxDto } from './box.dto' describe('BoxDto public identity', () => { - it('exposes the public boxId separately from the internal UUID', () => { + it('exposes a single public id without a legacy boxId alias', () => { const box = new Box('us', 'data-loader') box.organizationId = '057963b2-60ca-4356-81fc-11503e15f249' box.osUser = 'boxlite' @@ -16,7 +16,6 @@ describe('BoxDto public identity', () => { const dto = BoxDto.fromBox(box, 'https://proxy.boxlite.dev/toolbox') expect(dto.id).toBe(box.id) - expect(dto.boxId).toBe(box.boxId) - expect(dto.boxId).not.toBe(box.id) + expect((dto as any).boxId).toBeUndefined() }) }) diff --git a/apps/api/src/box/dto/box.dto.ts b/apps/api/src/box/dto/box.dto.ts index 8dc6c7d8b..802f22566 100644 --- a/apps/api/src/box/dto/box.dto.ts +++ b/apps/api/src/box/dto/box.dto.ts @@ -36,16 +36,10 @@ export class BoxVolume { @ApiSchema({ name: 'Box' }) export class BoxDto { @ApiProperty({ - description: 'The internal UUID of the box', - example: 'fd955d93-e74a-48e7-9f2d-fcbe6dd9e920', - }) - id: string - - @ApiProperty({ - description: 'The public Box ID shown to users and SDK clients', + description: 'The public 12-character Box ID', example: 'aB3cD4eF5gH6', }) - boxId: string + id: string @ApiProperty({ description: 'The organization ID of the box', @@ -252,7 +246,6 @@ export class BoxDto { static fromBox(box: Box, toolboxProxyUrl: string): BoxDto { return { id: box.id, - boxId: box.boxId, organizationId: box.organizationId, name: box.name, target: box.region, diff --git a/apps/api/src/box/dto/list-boxes-query.dto.ts b/apps/api/src/box/dto/list-boxes-query.dto.ts index 263348553..14b1c98b7 100644 --- a/apps/api/src/box/dto/list-boxes-query.dto.ts +++ b/apps/api/src/box/dto/list-boxes-query.dto.ts @@ -14,7 +14,6 @@ import { PageLimit } from '../../common/decorators/page-limit.decorator' export enum BoxSortField { ID = 'id', - BOX_ID = 'boxId', NAME = 'name', STATE = 'state', REGION = 'region', @@ -42,7 +41,7 @@ export class ListBoxesQueryDto { @ApiProperty({ name: 'id', - description: 'Filter by partial Box ID, internal UUID, or name match', + description: 'Filter by partial Box ID or name match', required: false, type: String, example: 'abc123', diff --git a/apps/api/src/box/entities/box.entity.spec.ts b/apps/api/src/box/entities/box.entity.spec.ts index f3439d680..5f87b4b88 100644 --- a/apps/api/src/box/entities/box.entity.spec.ts +++ b/apps/api/src/box/entities/box.entity.spec.ts @@ -8,13 +8,12 @@ import { BOX_ID_LENGTH, BOX_ID_REGEX } from '../utils/box-id.util' import { Box } from './box.entity' describe('Box entity public identity', () => { - it('mints a 12-character public boxId separately from the internal UUID', () => { + it('mints a single 12-character public id as the primary identity', () => { const box = new Box('us', 'data-loader') - expect(box.id).toBeDefined() - expect(box.boxId).toHaveLength(BOX_ID_LENGTH) - expect(box.boxId).toMatch(BOX_ID_REGEX) - expect(box.boxId).not.toBe(box.id) + expect(box.id).toHaveLength(BOX_ID_LENGTH) + expect(box.id).toMatch(BOX_ID_REGEX) + expect((box as any).boxId).toBeUndefined() expect(box.name).toBe('data-loader') }) }) diff --git a/apps/api/src/box/entities/box.entity.ts b/apps/api/src/box/entities/box.entity.ts index 7703e4971..460a37085 100644 --- a/apps/api/src/box/entities/box.entity.ts +++ b/apps/api/src/box/entities/box.entity.ts @@ -8,7 +8,6 @@ import { Column, CreateDateColumn, Entity, Index, PrimaryColumn, OneToOne, Uniqu import { BoxState } from '../enums/box-state.enum' import { BoxDesiredState } from '../enums/box-desired-state.enum' import { BoxClass } from '../enums/box-class.enum' -import { randomUUID } from 'crypto' import { BoxVolume } from '../dto/box.dto' import { nanoid } from 'nanoid' import { BoxLastActivity } from './box-last-activity.entity' @@ -16,13 +15,11 @@ import { BOX_ID_LENGTH, BOX_ID_REGEX, generateBoxId } from '../utils/box-id.util @Entity('box') @Unique(['organizationId', 'name']) -@Index('box_boxid_unique_idx', ['boxId'], { unique: true }) @Index('box_state_idx', ['state']) @Index('box_desiredstate_idx', ['desiredState']) @Index('box_runnerid_idx', ['runnerId']) @Index('box_runner_state_idx', ['runnerId', 'state']) @Index('box_organizationid_idx', ['organizationId']) -@Index('box_organizationid_boxid_idx', ['organizationId', 'boxId']) @Index('box_region_idx', ['region']) @Index('box_resources_idx', ['cpu', 'mem', 'disk', 'gpu']) @Index('box_runner_state_desired_idx', ['runnerId', 'state', 'desiredState'], { @@ -39,12 +36,9 @@ import { BOX_ID_LENGTH, BOX_ID_REGEX, generateBoxId } from '../utils/box-id.util @Index('box_labels_gin_full_idx', { synchronize: false }) @Index('idx_box_volumes_gin', { synchronize: false }) export class Box { - @PrimaryColumn({ default: () => 'uuid_generate_v4()' }) + @PrimaryColumn({ type: 'character varying', length: BOX_ID_LENGTH }) id: string - @Column({ type: 'character varying', length: BOX_ID_LENGTH }) - boxId: string = generateBoxId() - @Column({ type: 'uuid', }) @@ -173,7 +167,7 @@ export class Box { daemonVersion?: string constructor(region: string, name?: string) { - this.id = randomUUID() + this.id = generateBoxId() // Set name - use provided name or fallback to ID this.name = name || this.id this.region = region @@ -199,8 +193,8 @@ export class Box { } private validateBoxId(): void { - if (!BOX_ID_REGEX.test(this.boxId)) { - throw new Error(`Box ${this.id} has invalid boxId ${this.boxId}`) + if (!BOX_ID_REGEX.test(this.id)) { + throw new Error(`Box has invalid id ${this.id}`) } } diff --git a/apps/api/src/box/repositories/box.repository.ts b/apps/api/src/box/repositories/box.repository.ts index f55a61a20..2a9cb17ad 100644 --- a/apps/api/src/box/repositories/box.repository.ts +++ b/apps/api/src/box/repositories/box.repository.ts @@ -186,7 +186,6 @@ export class BoxRepository extends BaseRepository { try { this.boxLookupCacheInvalidationService.invalidateOrgId({ id: box.id, - boxId: box.boxId, organizationId: box.organizationId, name: box.name, }) @@ -202,15 +201,13 @@ export class BoxRepository extends BaseRepository { */ private invalidateLookupCacheOnUpdate( updatedBox: Box, - previousBox: Pick, + previousBox: Pick, ): void { try { this.boxLookupCacheInvalidationService.invalidate({ id: updatedBox.id, - boxId: updatedBox.boxId, organizationId: updatedBox.organizationId, previousOrganizationId: previousBox.organizationId, - previousBoxId: previousBox.boxId, name: updatedBox.name, previousName: previousBox.name, }) diff --git a/apps/api/src/box/services/box-lookup-cache-invalidation.service.ts b/apps/api/src/box/services/box-lookup-cache-invalidation.service.ts index 059cee01c..ec532cc25 100644 --- a/apps/api/src/box/services/box-lookup-cache-invalidation.service.ts +++ b/apps/api/src/box/services/box-lookup-cache-invalidation.service.ts @@ -8,10 +8,8 @@ import { Injectable, Logger } from '@nestjs/common' import { DataSource } from 'typeorm' import { boxLookupCacheKeyByAuthToken, - boxLookupCacheKeyByBoxId, boxLookupCacheKeyById, boxLookupCacheKeyByName, - boxOrgIdCacheKeyByBoxId, boxOrgIdCacheKeyById, boxOrgIdCacheKeyByName, } from '../utils/box-lookup-cache.util' @@ -19,11 +17,9 @@ import { type InvalidateBoxLookupCacheArgs = | { id: string - boxId: string organizationId: string name: string previousOrganizationId?: string | null - previousBoxId?: string | null previousName?: string | null } | { @@ -64,9 +60,6 @@ export class BoxLookupCacheInvalidationService { const names = Array.from( new Set([args.name, args.previousName].filter((n): n is string => Boolean(n && n.trim().length > 0))), ) - const boxIds = Array.from( - new Set([args.boxId, args.previousBoxId].filter((id): id is string => Boolean(id && id.trim().length > 0))), - ) const cacheIds: string[] = [] for (const organizationId of organizationIds) { @@ -75,18 +68,9 @@ export class BoxLookupCacheInvalidationService { boxLookupCacheKeyById({ organizationId, returnDestroyed, - boxId: args.id, + id: args.id, }), ) - for (const boxId of boxIds) { - cacheIds.push( - boxLookupCacheKeyByBoxId({ - organizationId, - returnDestroyed, - boxId, - }), - ) - } for (const boxName of names) { cacheIds.push( boxLookupCacheKeyByName({ @@ -105,21 +89,19 @@ export class BoxLookupCacheInvalidationService { cache .remove(cacheIds) - .then(() => this.logger.debug(`Invalidated box lookup cache for ${args.boxId}`)) + .then(() => this.logger.debug(`Invalidated box lookup cache for ${args.id}`)) .catch((error) => this.logger.warn( - `Failed to invalidate box lookup cache for ${args.boxId}: ${error instanceof Error ? error.message : String(error)}`, + `Failed to invalidate box lookup cache for ${args.id}: ${error instanceof Error ? error.message : String(error)}`, ), ) } invalidateOrgId(args: { id: string - boxId: string organizationId: string name: string previousOrganizationId?: string | null - previousBoxId?: string | null previousName?: string | null }): void { const cache = this.dataSource.queryResultCache @@ -137,26 +119,15 @@ export class BoxLookupCacheInvalidationService { const names = Array.from( new Set([args.name, args.previousName].filter((n): n is string => Boolean(n && n.trim().length > 0))), ) - const boxIds = Array.from( - new Set([args.boxId, args.previousBoxId].filter((id): id is string => Boolean(id && id.trim().length > 0))), - ) const cacheIds: string[] = [] for (const organizationId of organizationIds) { cacheIds.push( boxOrgIdCacheKeyById({ organizationId, - boxId: args.id, + id: args.id, }), ) - for (const boxId of boxIds) { - cacheIds.push( - boxOrgIdCacheKeyByBoxId({ - organizationId, - boxId, - }), - ) - } for (const boxName of names) { cacheIds.push( boxOrgIdCacheKeyByName({ @@ -168,20 +139,17 @@ export class BoxLookupCacheInvalidationService { } // Also invalidate the "no org" variants (when organizationId was not provided to getOrganizationId) - cacheIds.push(boxOrgIdCacheKeyById({ boxId: args.id })) - for (const boxId of boxIds) { - cacheIds.push(boxOrgIdCacheKeyByBoxId({ boxId })) - } + cacheIds.push(boxOrgIdCacheKeyById({ id: args.id })) for (const boxName of names) { cacheIds.push(boxOrgIdCacheKeyByName({ boxName })) } cache .remove(cacheIds) - .then(() => this.logger.debug(`Invalidated box orgId cache for ${args.boxId}`)) + .then(() => this.logger.debug(`Invalidated box orgId cache for ${args.id}`)) .catch((error) => this.logger.warn( - `Failed to invalidate box orgId cache for ${args.boxId}: ${error instanceof Error ? error.message : String(error)}`, + `Failed to invalidate box orgId cache for ${args.id}: ${error instanceof Error ? error.message : String(error)}`, ), ) } diff --git a/apps/api/src/box/services/box.service.box-id.spec.ts b/apps/api/src/box/services/box.service.box-id.spec.ts index e6bd7cc1f..cd7e72062 100644 --- a/apps/api/src/box/services/box.service.box-id.spec.ts +++ b/apps/api/src/box/services/box.service.box-id.spec.ts @@ -18,7 +18,7 @@ function createService(findOne: jest.Mock): BoxService { } describe('BoxService public identity lookup', () => { - it('resolves the public boxId before falling back to the internal UUID or name', async () => { + it('resolves the public id directly before falling back to name', async () => { const organizationId = '057963b2-60ca-4356-81fc-11503e15f249' const box = new Box('us', 'data-loader') box.organizationId = organizationId @@ -26,13 +26,13 @@ describe('BoxService public identity lookup', () => { const findOne = jest.fn().mockResolvedValueOnce(box) const service = createService(findOne) - await expect(service.findOneByIdOrName(box.boxId, organizationId)).resolves.toBe(box) + await expect(service.findOneByIdOrName(box.id, organizationId)).resolves.toBe(box) expect(findOne).toHaveBeenCalledTimes(1) expect(findOne).toHaveBeenCalledWith( expect.objectContaining({ where: { - boxId: box.boxId, + id: box.id, organizationId, state: Not(BoxState.DESTROYED), }, diff --git a/apps/api/src/box/services/box.service.ts b/apps/api/src/box/services/box.service.ts index 7a9ed258a..f353258d4 100644 --- a/apps/api/src/box/services/box.service.ts +++ b/apps/api/src/box/services/box.service.ts @@ -64,10 +64,8 @@ import { BOX_LOOKUP_CACHE_TTL_MS, BOX_ORG_ID_CACHE_TTL_MS, TOOLBOX_PROXY_URL_CACHE_TTL_S, - boxLookupCacheKeyByBoxId, boxLookupCacheKeyById, boxLookupCacheKeyByName, - boxOrgIdCacheKeyByBoxId, boxOrgIdCacheKeyById, boxOrgIdCacheKeyByName, toolboxProxyUrlCacheKey, @@ -316,7 +314,6 @@ export class BoxService { // Defensive invalidation of orgId cache since the box moved from unassigned to a real organization this.boxLookupCacheInvalidationService.invalidateOrgId({ id: warmPoolBox.id, - boxId: warmPoolBox.boxId, organizationId: organization.id, name: warmPoolBox.name, previousOrganizationId: BOX_WARM_POOL_UNASSIGNED_ORGANIZATION, @@ -481,11 +478,6 @@ export class BoxService { const idFilter = ILike(`${id}%`) return [ - { - ...baseFindOptions, - ...nameFilter, - boxId: idFilter, - }, { ...baseFindOptions, ...nameFilter, @@ -544,33 +536,19 @@ export class BoxService { const stateFilter = returnDestroyed ? {} : { state: Not(BoxState.DESTROYED) } const organizationFilter = organizationId ? { organizationId } : {} - // Public Box ID is the user-facing stable identity. UUID and name are legacy-compatible fallbacks. + // Public Box ID is the primary key. Name remains a user-facing fallback within an organization. let box = await this.boxRepository.findOne({ where: { - boxId: boxIdOrName, + id: boxIdOrName, ...organizationFilter, ...stateFilter, }, cache: { - id: boxLookupCacheKeyByBoxId({ organizationId, returnDestroyed, boxId: boxIdOrName }), + id: boxLookupCacheKeyById({ organizationId, returnDestroyed, id: boxIdOrName }), milliseconds: BOX_LOOKUP_CACHE_TTL_MS, }, }) - if (!box) { - box = await this.boxRepository.findOne({ - where: { - id: boxIdOrName, - ...organizationFilter, - ...stateFilter, - }, - cache: { - id: boxLookupCacheKeyById({ organizationId, returnDestroyed, boxId: boxIdOrName }), - milliseconds: BOX_LOOKUP_CACHE_TTL_MS, - }, - }) - } - if (!box) { box = await this.boxRepository.findOne({ where: { @@ -586,7 +564,7 @@ export class BoxService { } if (!box || (!returnDestroyed && box.state === BoxState.ERROR && box.desiredState === BoxDesiredState.DESTROYED)) { - throw new NotFoundException(`Box with Box ID, UUID, or name ${boxIdOrName} not found`) + throw new NotFoundException(`Box with ID or name ${boxIdOrName} not found`) } return box @@ -612,30 +590,16 @@ export class BoxService { let box = await this.boxRepository.findOne({ where: { - boxId: boxIdOrName, + id: boxIdOrName, ...organizationFilter, }, select: ['organizationId'], cache: { - id: boxOrgIdCacheKeyByBoxId({ organizationId, boxId: boxIdOrName }), + id: boxOrgIdCacheKeyById({ organizationId, id: boxIdOrName }), milliseconds: BOX_ORG_ID_CACHE_TTL_MS, }, }) - if (!box) { - box = await this.boxRepository.findOne({ - where: { - id: boxIdOrName, - ...organizationFilter, - }, - select: ['organizationId'], - cache: { - id: boxOrgIdCacheKeyById({ organizationId, boxId: boxIdOrName }), - milliseconds: BOX_ORG_ID_CACHE_TTL_MS, - }, - }) - } - if (!box && organizationId) { box = await this.boxRepository.findOne({ where: { @@ -651,7 +615,7 @@ export class BoxService { } if (!box || !box.organizationId) { - throw new NotFoundException(`Box with Box ID, UUID, or name ${boxIdOrName} not found`) + throw new NotFoundException(`Box with ID or name ${boxIdOrName} not found`) } return box.organizationId @@ -659,13 +623,13 @@ export class BoxService { async getRunnerId(boxIdOrName: string): Promise { const box = await this.boxRepository.findOne({ - where: [{ boxId: boxIdOrName }, { id: boxIdOrName }, { name: boxIdOrName }], + where: [{ id: boxIdOrName }, { name: boxIdOrName }], select: ['runnerId'], loadEagerRelations: false, }) if (!box) { - throw new NotFoundException(`Box with Box ID, UUID, or name ${boxIdOrName} not found`) + throw new NotFoundException(`Box with ID or name ${boxIdOrName} not found`) } return box.runnerId || null @@ -673,13 +637,13 @@ export class BoxService { async getRegionId(boxIdOrName: string): Promise { const box = await this.boxRepository.findOne({ - where: [{ boxId: boxIdOrName }, { id: boxIdOrName }, { name: boxIdOrName }], + where: [{ id: boxIdOrName }, { name: boxIdOrName }], select: ['region'], loadEagerRelations: false, }) if (!box) { - throw new NotFoundException(`Box with Box ID, UUID, or name ${boxIdOrName} not found`) + throw new NotFoundException(`Box with ID or name ${boxIdOrName} not found`) } return box.region diff --git a/apps/api/src/box/utils/box-lookup-cache.util.ts b/apps/api/src/box/utils/box-lookup-cache.util.ts index 876ba3a83..ac2d25318 100644 --- a/apps/api/src/box/utils/box-lookup-cache.util.ts +++ b/apps/api/src/box/utils/box-lookup-cache.util.ts @@ -12,16 +12,10 @@ type BoxLookupCacheKeyArgs = { returnDestroyed?: boolean } -export function boxLookupCacheKeyById(args: BoxLookupCacheKeyArgs & { boxId: string }): string { +export function boxLookupCacheKeyById(args: BoxLookupCacheKeyArgs & { id: string }): string { const organizationId = args.organizationId ?? 'none' const returnDestroyed = args.returnDestroyed ? 1 : 0 - return `box:lookup:by-id:org:${organizationId}:returnDestroyed:${returnDestroyed}:value:${args.boxId}` -} - -export function boxLookupCacheKeyByBoxId(args: BoxLookupCacheKeyArgs & { boxId: string }): string { - const organizationId = args.organizationId ?? 'none' - const returnDestroyed = args.returnDestroyed ? 1 : 0 - return `box:lookup:by-box-id:org:${organizationId}:returnDestroyed:${returnDestroyed}:value:${args.boxId}` + return `box:lookup:by-id:org:${organizationId}:returnDestroyed:${returnDestroyed}:value:${args.id}` } export function boxLookupCacheKeyByName(args: BoxLookupCacheKeyArgs & { boxName: string }): string { @@ -38,14 +32,9 @@ type BoxOrgIdCacheKeyArgs = { organizationId?: string } -export function boxOrgIdCacheKeyById(args: BoxOrgIdCacheKeyArgs & { boxId: string }): string { - const organizationId = args.organizationId ?? 'none' - return `box:orgId:by-id:org:${organizationId}:value:${args.boxId}` -} - -export function boxOrgIdCacheKeyByBoxId(args: BoxOrgIdCacheKeyArgs & { boxId: string }): string { +export function boxOrgIdCacheKeyById(args: BoxOrgIdCacheKeyArgs & { id: string }): string { const organizationId = args.organizationId ?? 'none' - return `box:orgId:by-box-id:org:${organizationId}:value:${args.boxId}` + return `box:orgId:by-id:org:${organizationId}:value:${args.id}` } export function boxOrgIdCacheKeyByName(args: BoxOrgIdCacheKeyArgs & { boxName: string }): string { diff --git a/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts b/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts index 15975b4a9..5adb918fd 100644 --- a/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts +++ b/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts @@ -8,10 +8,9 @@ import { BoxDto } from '../../box/dto/box.dto' import { boxToBoxResponse, createBoxToCreateBox } from './box-to-box.mapper' describe('box-to-box mapper', () => { - it('maps REST box_id from the public box boxId instead of the internal UUID', () => { + it('maps REST box_id from the canonical box id', () => { const response = boxToBoxResponse({ - id: 'fd955d93-e74a-48e7-9f2d-fcbe6dd9e920', - boxId: 'aB3cD4eF5gH6', + id: 'aB3cD4eF5gH6', organizationId: '057963b2-60ca-4356-81fc-11503e15f249', name: 'data-loader', state: 'started', @@ -31,7 +30,6 @@ describe('box-to-box mapper', () => { } as BoxDto) expect(response.box_id).toBe('aB3cD4eF5gH6') - expect(response.box_id).not.toBe('fd955d93-e74a-48e7-9f2d-fcbe6dd9e920') }) it('maps SDK resource settings to box create overrides', () => { diff --git a/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts b/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts index 0d5d735a8..bc3a194be 100644 --- a/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts +++ b/apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts @@ -12,7 +12,7 @@ import { CreateBoxDto } from '../../box/dto/create-box.dto' export function boxToBoxResponse(box: BoxDto): BoxResponseDto { return { - box_id: box.boxId, + box_id: box.id, name: box.name, status: mapState(box.state), created_at: box.createdAt || new Date().toISOString(), diff --git a/apps/api/src/migrations/1741087887225-migration.ts b/apps/api/src/migrations/1741087887225-migration.ts index b1183e322..8c7671e3d 100644 --- a/apps/api/src/migrations/1741087887225-migration.ts +++ b/apps/api/src/migrations/1741087887225-migration.ts @@ -70,7 +70,7 @@ export class Migration1741087887225 implements MigrationInterface { `CREATE TABLE "warm_pool" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "pool" integer NOT NULL, "image" character varying NOT NULL, "target" character varying NOT NULL, "cpu" integer NOT NULL, "mem" integer NOT NULL, "disk" integer NOT NULL, "gpu" integer NOT NULL, "gpuType" character varying NOT NULL, "class" "public"."warm_pool_class_enum" NOT NULL DEFAULT 'small', "osUser" character varying NOT NULL, "errorReason" character varying, "env" text NOT NULL DEFAULT '{}', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "warm_pool_id_pk" PRIMARY KEY ("id"))`, ) await queryRunner.query( - `CREATE TABLE "box" ("id" character varying NOT NULL DEFAULT uuid_generate_v4(), "boxId" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "image" character varying, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("id"))`, + `CREATE TABLE "box" ("id" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "image" character varying, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("id"))`, ) await queryRunner.query( `CREATE TABLE "box_last_activity" ("boxId" character varying NOT NULL, "lastActivityAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "box_last_activity_boxId_pk" PRIMARY KEY ("boxId"), CONSTRAINT "box_last_activity_boxId_fk" FOREIGN KEY ("boxId") REFERENCES "box"("id") ON DELETE CASCADE ON UPDATE NO ACTION)`, @@ -127,13 +127,11 @@ export class Migration1741087887225 implements MigrationInterface { ) await queryRunner.query(`CREATE INDEX "box_resources_idx" ON "box" ("cpu", "mem", "disk", "gpu")`) await queryRunner.query(`CREATE INDEX "box_region_idx" ON "box" ("region")`) - await queryRunner.query(`CREATE INDEX "box_organizationid_boxid_idx" ON "box" ("organizationId", "boxId")`) await queryRunner.query(`CREATE INDEX "box_organizationid_idx" ON "box" ("organizationId")`) await queryRunner.query(`CREATE INDEX "box_runner_state_idx" ON "box" ("runnerId", "state")`) await queryRunner.query(`CREATE INDEX "box_runnerid_idx" ON "box" ("runnerId")`) await queryRunner.query(`CREATE INDEX "box_desiredstate_idx" ON "box" ("desiredState")`) await queryRunner.query(`CREATE INDEX "box_state_idx" ON "box" ("state")`) - await queryRunner.query(`CREATE UNIQUE INDEX "box_boxid_unique_idx" ON "box" ("boxId")`) await queryRunner.query(`CREATE INDEX "box_labels_gin_full_idx" ON "box" USING gin ("labels" jsonb_path_ops)`) await queryRunner.query(`CREATE INDEX "idx_box_volumes_gin" ON "box" USING gin ("volumes" jsonb_path_ops)`) await queryRunner.query( @@ -192,13 +190,11 @@ export class Migration1741087887225 implements MigrationInterface { await queryRunner.query(`DROP TABLE "box_last_activity"`) await queryRunner.query(`DROP INDEX "public"."idx_box_volumes_gin"`) await queryRunner.query(`DROP INDEX "public"."box_labels_gin_full_idx"`) - await queryRunner.query(`DROP INDEX "public"."box_boxid_unique_idx"`) await queryRunner.query(`DROP INDEX "public"."box_state_idx"`) await queryRunner.query(`DROP INDEX "public"."box_desiredstate_idx"`) await queryRunner.query(`DROP INDEX "public"."box_runnerid_idx"`) await queryRunner.query(`DROP INDEX "public"."box_runner_state_idx"`) await queryRunner.query(`DROP INDEX "public"."box_organizationid_idx"`) - await queryRunner.query(`DROP INDEX "public"."box_organizationid_boxid_idx"`) await queryRunner.query(`DROP INDEX "public"."box_region_idx"`) await queryRunner.query(`DROP INDEX "public"."box_resources_idx"`) await queryRunner.query(`DROP INDEX "public"."box_runner_state_desired_idx"`) diff --git a/apps/dashboard/src/components/BoxTable/BoxTableActions.tsx b/apps/dashboard/src/components/BoxTable/BoxTableActions.tsx index 0daa7e3bb..570903793 100644 --- a/apps/dashboard/src/components/BoxTable/BoxTableActions.tsx +++ b/apps/dashboard/src/components/BoxTable/BoxTableActions.tsx @@ -152,7 +152,6 @@ export function BoxTableActions({ deletePermitted, box.state, box.id, - box.boxId, isLoading, box.recoverable, onStart, diff --git a/apps/dashboard/src/components/BoxTable/BoxTableHeader.tsx b/apps/dashboard/src/components/BoxTable/BoxTableHeader.tsx index 7f105e5d3..6932354de 100644 --- a/apps/dashboard/src/components/BoxTable/BoxTableHeader.tsx +++ b/apps/dashboard/src/components/BoxTable/BoxTableHeader.tsx @@ -71,7 +71,7 @@ export function BoxTableHeader({ table, onRefresh, isRefreshing = false, headerA getColumnLabel={(id: string) => { switch (id) { case 'id': - return 'Internal UUID' + return 'Box ID' default: return id } diff --git a/apps/dashboard/src/components/BoxTable/columns.tsx b/apps/dashboard/src/components/BoxTable/columns.tsx index 8915010e0..095cec881 100644 --- a/apps/dashboard/src/components/BoxTable/columns.tsx +++ b/apps/dashboard/src/components/BoxTable/columns.tsx @@ -135,35 +135,18 @@ export function getColumns({ }, }, { - id: 'boxId', + id: 'id', size: 140, enableSorting: true, enableHiding: false, header: ({ column }) => { return }, - accessorKey: 'boxId', - cell: ({ row }) => { - return ( -
- {getBoxPublicIdLabel(row.original)} -
- ) - }, - }, - { - id: 'id', - size: 320, - enableSorting: true, - enableHiding: true, - header: ({ column }) => { - return - }, accessorKey: 'id', cell: ({ row }) => { return (
- {row.original.id} + {getBoxPublicIdLabel(row.original)}
) }, diff --git a/apps/dashboard/src/components/BoxTable/types.ts b/apps/dashboard/src/components/BoxTable/types.ts index 34b33425a..2de1966f3 100644 --- a/apps/dashboard/src/components/BoxTable/types.ts +++ b/apps/dashboard/src/components/BoxTable/types.ts @@ -87,9 +87,6 @@ export const convertTableSortingToApiSorting = (sorting: SortingState): BoxSorti let field: ListBoxesPaginatedSortEnum switch (sort.id) { - case 'boxId': - field = ListBoxesPaginatedSortEnum.BOX_ID - break case 'id': field = ListBoxesPaginatedSortEnum.ID break @@ -208,9 +205,6 @@ export const convertApiSortingToTableSorting = (sorting: BoxSorting): SortingSta let id: string switch (sorting.field) { - case ListBoxesPaginatedSortEnum.BOX_ID: - id = 'boxId' - break case ListBoxesPaginatedSortEnum.ID: id = 'id' break diff --git a/apps/dashboard/src/components/BoxTable/useBoxTable.ts b/apps/dashboard/src/components/BoxTable/useBoxTable.ts index 97eab52b6..42a743684 100644 --- a/apps/dashboard/src/components/BoxTable/useBoxTable.ts +++ b/apps/dashboard/src/components/BoxTable/useBoxTable.ts @@ -78,12 +78,12 @@ export function useBoxTable({ const saved = getLocalStorageItem(LocalStorageKey.BoxTableColumnVisibility) if (saved) { try { - return { ...JSON.parse(saved), boxId: true, id: false, region: true, labels: false } + return { ...JSON.parse(saved), id: true, region: true, labels: false } } catch { - return { boxId: true, id: false, region: true, labels: false } + return { id: true, region: true, labels: false } } } - return { boxId: true, id: false, region: true, labels: false } + return { id: true, region: true, labels: false } }) useEffect(() => { diff --git a/apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx b/apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx index 0914929bc..3185a3451 100644 --- a/apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx +++ b/apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx @@ -197,7 +197,7 @@ const AdminPeopleBoxesView: React.FC = ({ variant="ghost" size="sm" className="h-7 px-2 text-xs" - aria-label={`Diagnose box ${box.boxId ?? box.id}`} + aria-label={`Diagnose box ${box.id}`} onClick={(event) => { event.stopPropagation() onOpenBox(box) diff --git a/apps/dashboard/src/components/admin/adminDiagnoseTarget.ts b/apps/dashboard/src/components/admin/adminDiagnoseTarget.ts index 47b55b256..ccf717623 100644 --- a/apps/dashboard/src/components/admin/adminDiagnoseTarget.ts +++ b/apps/dashboard/src/components/admin/adminDiagnoseTarget.ts @@ -39,7 +39,7 @@ export function createBoxDiagnoseTarget(box: AdminBox): AdminDiagnoseTarget { return { kind: 'box', title: 'Diagnose box', - subtitle: box.boxId ? `${box.boxId} · ${box.id}` : box.id, + subtitle: box.id, state: box.state, box, params: { diff --git a/apps/dashboard/src/components/admin/adminHelpers.spec.ts b/apps/dashboard/src/components/admin/adminHelpers.spec.ts index b24e84c80..aee22feeb 100644 --- a/apps/dashboard/src/components/admin/adminHelpers.spec.ts +++ b/apps/dashboard/src/components/admin/adminHelpers.spec.ts @@ -204,16 +204,17 @@ describe('selectErroringOwners', () => { }) describe('findBoxById', () => { - it('finds exact box ids case-insensitively so pasted UUIDs can open telemetry', () => { + it('finds exact box ids so pasted Box IDs can open telemetry', () => { const groups = groupBoxesByOwner([ box({ - id: '2479F61E-04D9-49F3-B7D9-CFAFBEE74D68', + id: 'aB3cD4eF5gH6', organizationId: 'org-a', state: 'error', owner: { name: 'Brian Luo', email: 'brian@x.io', orgName: 'Brian', personal: true }, }), ]) - expect(findBoxById(groups, '2479f61e-04d9-49f3-b7d9-cfafbee74d68')?.box.state).toBe('error') + expect(findBoxById(groups, ' aB3cD4eF5gH6 ')?.box.state).toBe('error') + expect(findBoxById(groups, 'ab3cd4ef5gh6')).toBeUndefined() }) }) diff --git a/apps/dashboard/src/components/admin/adminHelpers.ts b/apps/dashboard/src/components/admin/adminHelpers.ts index 679a33851..439a38fd2 100644 --- a/apps/dashboard/src/components/admin/adminHelpers.ts +++ b/apps/dashboard/src/components/admin/adminHelpers.ts @@ -30,7 +30,6 @@ export interface AdminBoxOwner { export interface AdminBox { id: string - boxId?: string organizationId: string state: string runnerId: string | null @@ -199,9 +198,7 @@ export function filterOwnerGroups(groups: OwnerGroup[], query: string): OwnerGro result.push(group) continue } - const matchingBoxes = group.boxes.filter( - (b) => b.id.toLowerCase().includes(q) || b.boxId?.toLowerCase().includes(q), - ) + const matchingBoxes = group.boxes.filter((b) => b.id.toLowerCase().includes(q)) if (matchingBoxes.length > 0) { result.push({ ...group, boxes: matchingBoxes, breakdown: getBoxBreakdown(matchingBoxes) }) } @@ -222,9 +219,9 @@ export function selectErroringOwners(groups: OwnerGroup[]): ErroringOwner[] { } export function findBoxById(groups: OwnerGroup[], boxId: string): { box: AdminBox; group: OwnerGroup } | undefined { - const targetBoxId = boxId.trim().toLowerCase() + const targetBoxId = boxId.trim() for (const group of groups) { - const box = group.boxes.find((b) => b.id.toLowerCase() === targetBoxId || b.boxId?.toLowerCase() === targetBoxId) + const box = group.boxes.find((b) => b.id === targetBoxId) if (box) return { box, group } } return undefined diff --git a/apps/dashboard/src/hooks/useBoxWsSync.ts b/apps/dashboard/src/hooks/useBoxWsSync.ts index 0a9c9ff5d..ce05ddcab 100644 --- a/apps/dashboard/src/hooks/useBoxWsSync.ts +++ b/apps/dashboard/src/hooks/useBoxWsSync.ts @@ -44,16 +44,13 @@ export function useBoxWsSync({ boxId, refetchOnCreate = false }: UseBoxWsSyncOpt }) } - const matchesActiveBox = (box: Box) => !boxId || box.id === boxId || box.boxId === boxId + const matchesActiveBox = (box: Box) => !boxId || box.id === boxId const optimisticUpdate = (box: Box, state: BoxState) => { updateStateInListCache(box.id, state) if (boxId) { updateStateInDetailCache(boxId, state) updateStateInDetailCache(box.id, state) - if (box.boxId) { - updateStateInDetailCache(box.boxId, state) - } } } @@ -70,7 +67,7 @@ export function useBoxWsSync({ boxId, refetchOnCreate = false }: UseBoxWsSyncOpt } } - const handleCreated = (_box: Box) => { + const handleCreated = () => { if (boxId) return queryClient.invalidateQueries({ @@ -84,7 +81,7 @@ export function useBoxWsSync({ boxId, refetchOnCreate = false }: UseBoxWsSyncOpt // warm pool boxes — treat as created if (data.oldState === data.newState && data.newState === BoxState.STARTED) { - handleCreated(data.box) + handleCreated() return } diff --git a/apps/dashboard/src/lib/box-identity.test.ts b/apps/dashboard/src/lib/box-identity.test.ts new file mode 100644 index 000000000..8d267f6a9 --- /dev/null +++ b/apps/dashboard/src/lib/box-identity.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'vitest' + +import { getBoxDisplayName, getBoxRouteId } from './box-identity' + +describe('box identity helpers', () => { + it('uses id as the only route identity', () => { + expect(getBoxRouteId({ id: 'Srv123456789', boxId: 'legacy-box' } as any)).toBe('Srv123456789') + }) + + it('uses id as the display fallback when the name is just the id', () => { + expect(getBoxDisplayName({ id: 'Srv123456789', name: 'Srv123456789' } as any)).toBe('Srv123456789') + }) +}) diff --git a/apps/dashboard/src/lib/box-identity.ts b/apps/dashboard/src/lib/box-identity.ts index b39b115fa..201dc98ca 100644 --- a/apps/dashboard/src/lib/box-identity.ts +++ b/apps/dashboard/src/lib/box-identity.ts @@ -6,20 +6,20 @@ import { Box, BoxDesiredState } from '@boxlite-ai/api-client' -type BoxIdentity = Pick & Partial> +type BoxIdentity = Pick & Partial> export const MISSING_BOX_ID_LABEL = 'Not available' -export function getBoxPublicId(box: Partial> | undefined): string { - return box?.boxId || '' +export function getBoxPublicId(box: Partial> | undefined): string { + return box?.id || '' } -export function getBoxPublicIdLabel(box: Partial> | undefined): string { +export function getBoxPublicIdLabel(box: Partial> | undefined): string { return getBoxPublicId(box) || MISSING_BOX_ID_LABEL } -export function getBoxRouteId(box: Partial> | undefined): string { - return box?.boxId || box?.id || '' +export function getBoxRouteId(box: Partial> | undefined): string { + return box?.id || '' } export function getBoxDisplayName(box: BoxIdentity): string { @@ -27,7 +27,7 @@ export function getBoxDisplayName(box: BoxIdentity): string { if (name && name !== box.id && !isUuidLike(name)) { return name } - return getBoxPublicId(box) || 'Box' + return box.id || 'Box' } function getNormalizedBoxName(box: BoxIdentity): string { diff --git a/apps/libs/api-client/src/api/box-api.ts b/apps/libs/api-client/src/api/box-api.ts index f59e6dc27..3bce1bfd1 100644 --- a/apps/libs/api-client/src/api/box-api.ts +++ b/apps/libs/api-client/src/api/box-api.ts @@ -728,7 +728,7 @@ export const BoxApiAxiosParamCreator = function (configuration?: Configuration) * @param {string} [xBoxLiteOrganizationID] Use with JWT to specify the organization ID * @param {number} [page] Page number of the results * @param {number} [limit] Number of results per page - * @param {string} [id] Filter by partial Box ID, internal UUID, or name match + * @param {string} [id] Filter by partial Box ID or name match * @param {string} [name] Filter by partial name match * @param {string} [labels] JSON encoded labels to filter by * @param {boolean} [includeErroredDeleted] Include results with errored state and deleted desired state @@ -1536,7 +1536,7 @@ export const BoxApiFp = function(configuration?: Configuration) { * @param {string} [xBoxLiteOrganizationID] Use with JWT to specify the organization ID * @param {number} [page] Page number of the results * @param {number} [limit] Number of results per page - * @param {string} [id] Filter by partial Box ID, internal UUID, or name match + * @param {string} [id] Filter by partial Box ID or name match * @param {string} [name] Filter by partial name match * @param {string} [labels] JSON encoded labels to filter by * @param {boolean} [includeErroredDeleted] Include results with errored state and deleted desired state @@ -1879,7 +1879,7 @@ export const BoxApiFactory = function (configuration?: Configuration, basePath?: * @param {string} [xBoxLiteOrganizationID] Use with JWT to specify the organization ID * @param {number} [page] Page number of the results * @param {number} [limit] Number of results per page - * @param {string} [id] Filter by partial Box ID, internal UUID, or name match + * @param {string} [id] Filter by partial Box ID or name match * @param {string} [name] Filter by partial name match * @param {string} [labels] JSON encoded labels to filter by * @param {boolean} [includeErroredDeleted] Include results with errored state and deleted desired state @@ -2199,7 +2199,7 @@ export class BoxApi extends BaseAPI { * @param {string} [xBoxLiteOrganizationID] Use with JWT to specify the organization ID * @param {number} [page] Page number of the results * @param {number} [limit] Number of results per page - * @param {string} [id] Filter by partial Box ID, internal UUID, or name match + * @param {string} [id] Filter by partial Box ID or name match * @param {string} [name] Filter by partial name match * @param {string} [labels] JSON encoded labels to filter by * @param {boolean} [includeErroredDeleted] Include results with errored state and deleted desired state @@ -2368,7 +2368,6 @@ export const ListBoxesPaginatedStatesEnum = { export type ListBoxesPaginatedStatesEnum = typeof ListBoxesPaginatedStatesEnum[keyof typeof ListBoxesPaginatedStatesEnum]; export const ListBoxesPaginatedSortEnum = { ID: 'id', - BOX_ID: 'boxId', NAME: 'name', STATE: 'state', REGION: 'region', diff --git a/apps/libs/api-client/src/docs/AdminBoxItem.md b/apps/libs/api-client/src/docs/AdminBoxItem.md index aca935454..336e956c0 100644 --- a/apps/libs/api-client/src/docs/AdminBoxItem.md +++ b/apps/libs/api-client/src/docs/AdminBoxItem.md @@ -6,7 +6,6 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **id** | **string** | Box ID | [default to undefined] -**boxId** | **string** | Public box ID shown to users | [optional] [default to undefined] **organizationId** | **string** | Organization ID | [default to undefined] **state** | [**BoxState**](BoxState.md) | | [default to undefined] **runnerId** | **string** | Runner ID the box is assigned to | [optional] [default to undefined] @@ -22,7 +21,6 @@ import { AdminBoxItem } from './api'; const instance: AdminBoxItem = { id, - boxId, organizationId, state, runnerId, diff --git a/apps/libs/api-client/src/docs/Box.md b/apps/libs/api-client/src/docs/Box.md index 65e0e12df..fc720e058 100644 --- a/apps/libs/api-client/src/docs/Box.md +++ b/apps/libs/api-client/src/docs/Box.md @@ -5,8 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**id** | **string** | The internal UUID of the box | [default to undefined] -**boxId** | **string** | The public Box ID shown to users and SDK clients | [default to undefined] +**id** | **string** | The public 12-character Box ID | [default to undefined] **organizationId** | **string** | The organization ID of the box | [default to undefined] **name** | **string** | The name of the box | [default to undefined] **user** | **string** | The user associated with the project | [default to undefined] @@ -42,7 +41,6 @@ import { Box } from './api'; const instance: Box = { id, - boxId, organizationId, name, user, diff --git a/apps/libs/api-client/src/docs/BoxApi.md b/apps/libs/api-client/src/docs/BoxApi.md index 68181d784..9d419e07e 100644 --- a/apps/libs/api-client/src/docs/BoxApi.md +++ b/apps/libs/api-client/src/docs/BoxApi.md @@ -758,7 +758,7 @@ const apiInstance = new BoxApi(configuration); let xBoxLiteOrganizationID: string; //Use with JWT to specify the organization ID (optional) (default to undefined) let page: number; //Page number of the results (optional) (default to 1) let limit: number; //Number of results per page (optional) (default to 100) -let id: string; //Filter by partial Box ID, internal UUID, or name match (optional) (default to undefined) +let id: string; //Filter by partial Box ID or name match (optional) (default to undefined) let name: string; //Filter by partial name match (optional) (default to undefined) let labels: string; //JSON encoded labels to filter by (optional) (default to undefined) let includeErroredDeleted: boolean; //Include results with errored state and deleted desired state (optional) (default to false) @@ -772,7 +772,7 @@ let minDiskGiB: number; //Minimum disk space in GiB (optional) (default to undef let maxDiskGiB: number; //Maximum disk space in GiB (optional) (default to undefined) let lastEventAfter: Date; //Include items with last event after this timestamp (optional) (default to undefined) let lastEventBefore: Date; //Include items with last event before this timestamp (optional) (default to undefined) -let sort: 'id' | 'boxId' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt'; //Field to sort by (optional) (default to 'createdAt') +let sort: 'id' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt'; //Field to sort by (optional) (default to 'createdAt') let order: 'asc' | 'desc'; //Direction to sort by (optional) (default to 'desc') const { status, data } = await apiInstance.listBoxesPaginated( @@ -805,7 +805,7 @@ const { status, data } = await apiInstance.listBoxesPaginated( | **xBoxLiteOrganizationID** | [**string**] | Use with JWT to specify the organization ID | (optional) defaults to undefined| | **page** | [**number**] | Page number of the results | (optional) defaults to 1| | **limit** | [**number**] | Number of results per page | (optional) defaults to 100| -| **id** | [**string**] | Filter by partial Box ID, internal UUID, or name match | (optional) defaults to undefined| +| **id** | [**string**] | Filter by partial Box ID or name match | (optional) defaults to undefined| | **name** | [**string**] | Filter by partial name match | (optional) defaults to undefined| | **labels** | [**string**] | JSON encoded labels to filter by | (optional) defaults to undefined| | **includeErroredDeleted** | [**boolean**] | Include results with errored state and deleted desired state | (optional) defaults to false| @@ -819,7 +819,7 @@ const { status, data } = await apiInstance.listBoxesPaginated( | **maxDiskGiB** | [**number**] | Maximum disk space in GiB | (optional) defaults to undefined| | **lastEventAfter** | [**Date**] | Include items with last event after this timestamp | (optional) defaults to undefined| | **lastEventBefore** | [**Date**] | Include items with last event before this timestamp | (optional) defaults to undefined| -| **sort** | [**'id' | 'boxId' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt'**]**Array<'id' | 'boxId' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt' | '11184809'>** | Field to sort by | (optional) defaults to 'createdAt'| +| **sort** | [**'id' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt'**]**Array<'id' | 'name' | 'state' | 'region' | 'updatedAt' | 'createdAt' | '11184809'>** | Field to sort by | (optional) defaults to 'createdAt'| | **order** | [**'asc' | 'desc'**]**Array<'asc' | 'desc' | '11184809'>** | Direction to sort by | (optional) defaults to 'desc'| diff --git a/apps/libs/api-client/src/models/admin-box-item.ts b/apps/libs/api-client/src/models/admin-box-item.ts index 3301b1df1..829a8808f 100644 --- a/apps/libs/api-client/src/models/admin-box-item.ts +++ b/apps/libs/api-client/src/models/admin-box-item.ts @@ -25,10 +25,6 @@ export interface AdminBoxItem { * Box ID */ 'id': string; - /** - * Public box ID shown to users - */ - 'boxId'?: string; /** * Organization ID */ diff --git a/apps/libs/api-client/src/models/box.ts b/apps/libs/api-client/src/models/box.ts index 201cf48b4..882d646c9 100644 --- a/apps/libs/api-client/src/models/box.ts +++ b/apps/libs/api-client/src/models/box.ts @@ -25,13 +25,9 @@ import type { BoxVolume } from './box-volume'; export interface Box { /** - * The internal UUID of the box + * The public 12-character Box ID */ 'id': string; - /** - * The public Box ID shown to users and SDK clients - */ - 'boxId': string; /** * The organization ID of the box */ diff --git a/apps/runner/pkg/api/dto/box.go b/apps/runner/pkg/api/dto/box.go index 32a1ba945..596767b18 100644 --- a/apps/runner/pkg/api/dto/box.go +++ b/apps/runner/pkg/api/dto/box.go @@ -6,7 +6,6 @@ package dto type CreateBoxDTO struct { Id string `json:"id" validate:"required"` - BoxId string `json:"boxId,omitempty"` FromVolumeId string `json:"fromVolumeId,omitempty"` Image string `json:"image" validate:"required"` OsUser string `json:"osUser" validate:"required"` diff --git a/apps/runner/pkg/boxlite/client.go b/apps/runner/pkg/boxlite/client.go index 4ac45ad5a..031bcb99f 100644 --- a/apps/runner/pkg/boxlite/client.go +++ b/apps/runner/pkg/boxlite/client.go @@ -24,21 +24,21 @@ import ( // Client wraps the BoxLite Go SDK to provide the same interface as the Docker client. // It manages VMs instead of containers, providing hardware-level isolation. type Client struct { - runtime *boxlite.Runtime - logger *slog.Logger - homeDir string - mu sync.RWMutex - boxes map[string]*boxlite.Box - awsRegion string - awsEndpointUrl string - awsAccessKeyId string - awsSecretAccessKey string - volumeMutexes map[string]*sync.Mutex - volumeMutexesMutex sync.Mutex - volumeCleanupMutex sync.Mutex - toolboxPortMutex sync.Mutex - lastVolumeCleanup time.Time - volumeCleanup volumeCleanupConfig + runtime *boxlite.Runtime + logger *slog.Logger + homeDir string + mu sync.RWMutex + boxes map[string]*boxlite.Box + awsRegion string + awsEndpointUrl string + awsAccessKeyId string + awsSecretAccessKey string + volumeMutexes map[string]*sync.Mutex + volumeMutexesMutex sync.Mutex + volumeCleanupMutex sync.Mutex + toolboxPortMutex sync.Mutex + lastVolumeCleanup time.Time + volumeCleanup volumeCleanupConfig } // ClientConfig holds configuration for the BoxLite client. @@ -155,15 +155,15 @@ func NewClient(ctx context.Context, config ClientConfig) (*Client, error) { } return &Client{ - runtime: rt, - logger: logger, - homeDir: config.HomeDir, - boxes: make(map[string]*boxlite.Box), - awsRegion: config.AWSRegion, - awsEndpointUrl: config.AWSEndpointUrl, - awsAccessKeyId: config.AWSAccessKeyId, - awsSecretAccessKey: config.AWSSecretAccessKey, - volumeMutexes: make(map[string]*sync.Mutex), + runtime: rt, + logger: logger, + homeDir: config.HomeDir, + boxes: make(map[string]*boxlite.Box), + awsRegion: config.AWSRegion, + awsEndpointUrl: config.AWSEndpointUrl, + awsAccessKeyId: config.AWSAccessKeyId, + awsSecretAccessKey: config.AWSSecretAccessKey, + volumeMutexes: make(map[string]*sync.Mutex), volumeCleanup: volumeCleanupConfig{ interval: config.VolumeCleanupInterval, dryRun: config.VolumeCleanupDryRun, @@ -203,11 +203,6 @@ func (c *Client) Close() error { // Create creates a new box (VM) from the given image and configuration. // Returns the box ID and daemon version. func (c *Client) Create(ctx context.Context, boxDto dto.CreateBoxDTO) (string, string, error) { - publicBoxId := boxDto.BoxId - if publicBoxId == "" { - publicBoxId = boxDto.Id - } - // API sends cores / GB / GB as small integers (see apps/api Box entity). cpus := int(boxDto.CpuQuota) if cpus < 1 { @@ -284,8 +279,6 @@ func (c *Client) Create(ctx context.Context, boxDto dto.CreateBoxDTO) (string, s bx.ID(), "boxId", boxDto.Id, - "boxId", - publicBoxId, "name", bx.Name(), "image", diff --git a/apps/runner/pkg/boxlite/daemon_env_test.go b/apps/runner/pkg/boxlite/daemon_env_test.go index 2f673dd34..14449e40b 100644 --- a/apps/runner/pkg/boxlite/daemon_env_test.go +++ b/apps/runner/pkg/boxlite/daemon_env_test.go @@ -5,12 +5,19 @@ package boxlite import ( "context" + "reflect" "testing" "github.com/boxlite-ai/runner/pkg/api/dto" "go.opentelemetry.io/otel/trace" ) +func TestCreateBoxDTOHasSingleBoxIdentity(t *testing.T) { + if _, ok := reflect.TypeOf(dto.CreateBoxDTO{}).FieldByName("BoxId"); ok { + t.Fatalf("CreateBoxDTO must not carry a legacy BoxId field") + } +} + func TestDaemonBoxEnvIncludesRequiredBoxIdentity(t *testing.T) { organizationID := "org-1" regionID := "region-1"