feat: enforce single tool type with port configuration

- Replace interfaces array with single interface_type string (web/terminal)
- Add requires_port boolean to indicate port/tunnel needs
- Create Alembic migration for database schema change
- Update backend model, API validation, and seed data
- Update frontend types and tool workshop UI with dropdown
- Add conditional port field rendering based on interface type
- Update all frontend and backend tests

OpenSpec change: enforce-single-tool-type-with-port-config
Quality gates: frontend typecheck PASS, lint PASS, tests 37/37 PASS
This commit is contained in:
2026-05-22 20:44:17 +00:00
parent 0fa926284c
commit e167a6be12
12 changed files with 159 additions and 1 deletions
@@ -111,6 +111,37 @@ The system SHALL provide a dashboard overview.
- Recent activity
- Quick action buttons
### Requirement: Tool Interface Type Dropdown
The tool workshop SHALL provide a dropdown for selecting a single interface type.
#### Scenario: Interface type dropdown
- GIVEN the tool workshop page
- WHEN a user creates or edits a tool type
- THEN the interface type field is a dropdown (not checkboxes)
- AND the options are "web" and "terminal"
- AND only one option can be selected
### Requirement: Conditional Port Fields
The tool workshop SHALL conditionally show or hide port-related fields based on the selected interface type.
#### Scenario: Web tool shows port fields
- GIVEN a tool type with interface type "web"
- WHEN the user views the tool editor
- THEN the Default Port field is visible and required
- AND port-related config fields are shown
#### Scenario: Terminal tool hides port fields
- GIVEN a tool type with interface type "terminal"
- WHEN the user views the tool editor
- THEN the Default Port field is hidden
- AND port-related config fields are hidden or disabled
#### Scenario: Changing interface type updates visibility
- GIVEN a user changes interface type from "web" to "terminal"
- WHEN the change is applied
- THEN port fields are immediately hidden
- AND any port value is preserved but not validated
## Dependencies
- React 18+
@@ -0,0 +1,50 @@
## ADDED Requirements
### Requirement: Port Configuration Visibility
The system SHALL control whether port configuration is relevant for a tool type.
#### Scenario: Web tool requires port
- GIVEN a tool type with `requires_port` = true
- WHEN the tool type is displayed in the UI
- THEN port configuration fields are shown
- AND default_port is validated as required
#### Scenario: Terminal tool does not require port
- GIVEN a tool type with `requires_port` = false
- WHEN the tool type is displayed in the UI
- THEN port configuration fields are hidden
- AND default_port validation is skipped
- AND port_override in tool configs is not shown
### Requirement: Port Validation Based on requires_port
The API SHALL validate port fields conditionally based on requires_port.
#### Scenario: Validate port for web tools
- GIVEN a tool type with `requires_port` = true
- WHEN creating or updating without a default_port
- THEN the system returns 400 Bad Request
#### Scenario: Skip port validation for terminal tools
- GIVEN a tool type with `requires_port` = false
- WHEN creating or updating without a default_port
- THEN the request succeeds
- AND default_port defaults to 0 or null
### Requirement: UI Conditional Rendering
The frontend SHALL conditionally render port-related UI elements.
#### Scenario: Hide port in tool list
- GIVEN a terminal tool type
- WHEN displayed in the tool workshop list
- THEN port information is not shown
#### Scenario: Hide port in editor
- GIVEN a terminal tool type being edited
- WHEN the editor form is rendered
- THEN the Default Port field is hidden
- AND the readiness probe fields are shown (still relevant)
#### Scenario: Show port for web tools
- GIVEN a web tool type being edited
- WHEN the editor form is rendered
- THEN the Default Port field is visible and required
@@ -0,0 +1,39 @@
## ADDED Requirements
### Requirement: Single Interface Type Enforcement
The system SHALL enforce that each tool type has exactly one interface type.
#### Scenario: Create with single interface
- GIVEN a tool type creation request with `interface_type` = "web"
- WHEN the request is processed
- THEN the tool type is created successfully
- AND the interface type is stored as a single string
#### Scenario: Reject multiple interfaces
- GIVEN a legacy request with `interfaces` array
- WHEN the request is processed
- THEN the system returns 400 Bad Request
- AND the error message indicates that `interface_type` (string) should be used instead
### Requirement: Interface Type Validation
The system SHALL validate that interface_type is one of the allowed values.
#### Scenario: Valid interface types
- GIVEN interface_type values "web" or "terminal"
- WHEN a tool type is created or updated
- THEN the request is accepted
#### Scenario: Invalid interface type
- GIVEN interface_type value "ssh"
- WHEN a tool type is created or updated
- THEN the system returns 400 Bad Request
### Requirement: Data Migration
The system SHALL migrate existing tool types from interfaces array to single interface_type.
#### Scenario: Migrate existing records
- GIVEN existing tool types with interfaces = ["web"] or ["terminal"]
- WHEN the migration runs
- THEN each record gets interface_type = interfaces[0]
- AND requires_port is set based on the interface type
- AND the old interfaces column is removed
+39 -1
View File
@@ -11,7 +11,12 @@ The system SHALL provide a `ToolType` model to store tool definitions.
- `name`: unique string (e.g., "code-server")
- `display_name`: human-readable string (e.g., "VS Code Server")
- `description`: optional text
- `category`: string (e.g., "editor", "notebook")
- `interface_type`: single string — "web" or "terminal"
- `requires_port`: boolean indicating if port/tunnel configuration is needed
- `compose_template`: Docker Compose YAML string
- `dockerfile_template`: Dockerfile string
- `definition_type`: string — "compose" or "dockerfile"
- `required_variables`: list of required template variables
- `is_builtin`: boolean flag for system-defined types
- `created_at`/`updated_at`: timestamps
@@ -30,7 +35,9 @@ The system SHALL provide REST API endpoints for tool type management.
- GIVEN an admin user
- WHEN they POST /api/tool-types with valid data
- THEN the system creates a new tool type
- AND validates the compose template YAML
- AND validates `interface_type` is "web" or "terminal"
- AND validates `requires_port` is boolean
- AND validates the compose template YAML (if definition_type is "compose")
- AND validates all required variables are present in template
- AND returns 201 Created with the new tool type
@@ -44,6 +51,7 @@ The system SHALL provide REST API endpoints for tool type management.
- GIVEN an admin user
- WHEN they PUT /api/tool-types/{id} with valid data
- THEN the system updates the tool type
- AND validates `interface_type` is "web" or "terminal" if provided
- AND re-validates the compose template
- AND returns 200 OK with updated tool type
@@ -98,3 +106,33 @@ The system SHALL validate Docker Compose templates.
- THEN the system SHALL require:
- `services` key present
- At least one service defined
### Requirement: Port requirement indication
The system SHALL allow tool types to indicate whether they require port configuration.
#### Scenario: Web tool requires port
- GIVEN a tool type with `interface_type` = "web"
- WHEN the tool type is created or updated
- THEN `requires_port` SHALL default to true
- AND port-related configuration is shown in the UI
#### Scenario: Terminal tool does not require port
- GIVEN a tool type with `interface_type` = "terminal"
- WHEN the tool type is created or updated
- THEN `requires_port` SHALL default to false
- AND port-related configuration is hidden in the UI
### Requirement: Single interface validation
The system SHALL enforce that each tool type has exactly one interface type.
#### Scenario: Invalid interface type
- GIVEN a tool type creation request with `interface_type` = "invalid"
- WHEN the request is processed
- THEN the system returns 400 Bad Request
- AND the error message indicates valid values are "web" or "terminal"
#### Scenario: Missing interface type
- GIVEN a tool type creation request without `interface_type`
- WHEN the request is processed
- THEN the system returns 400 Bad Request
- AND the error message indicates interface_type is required