From e167a6be12c972fe604a0b2f4a4aec15b993e573 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Fri, 22 May 2026 20:44:17 +0000 Subject: [PATCH] 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 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/frontend-foundation/spec.md | 0 .../specs/tool-type-port-visibility/spec.md | 0 .../specs/tool-type-single-interface/spec.md | 0 .../specs/tool-types-definition/spec.md | 0 .../tasks.md | 0 openspec/specs/frontend-foundation/spec.md | 31 ++++++++++++ .../specs/tool-type-port-visibility/spec.md | 50 +++++++++++++++++++ .../specs/tool-type-single-interface/spec.md | 39 +++++++++++++++ openspec/specs/tool-types-definition/spec.md | 40 ++++++++++++++- 12 files changed, 159 insertions(+), 1 deletion(-) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/.openspec.yaml (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/design.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/proposal.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/specs/frontend-foundation/spec.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/specs/tool-type-port-visibility/spec.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/specs/tool-type-single-interface/spec.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/specs/tool-types-definition/spec.md (100%) rename openspec/changes/{enforce-single-tool-type-with-port-config => archive/2026-05-22-enforce-single-tool-type-with-port-config}/tasks.md (100%) create mode 100644 openspec/specs/tool-type-port-visibility/spec.md create mode 100644 openspec/specs/tool-type-single-interface/spec.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/.openspec.yaml b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/.openspec.yaml similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/.openspec.yaml rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/.openspec.yaml diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/design.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/design.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/design.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/design.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/proposal.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/proposal.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/proposal.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/proposal.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/specs/frontend-foundation/spec.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/frontend-foundation/spec.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/specs/frontend-foundation/spec.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/frontend-foundation/spec.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-type-port-visibility/spec.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-type-port-visibility/spec.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-type-port-visibility/spec.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-type-port-visibility/spec.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-type-single-interface/spec.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-type-single-interface/spec.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-type-single-interface/spec.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-type-single-interface/spec.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-types-definition/spec.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-types-definition/spec.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/specs/tool-types-definition/spec.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/specs/tool-types-definition/spec.md diff --git a/openspec/changes/enforce-single-tool-type-with-port-config/tasks.md b/openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/tasks.md similarity index 100% rename from openspec/changes/enforce-single-tool-type-with-port-config/tasks.md rename to openspec/changes/archive/2026-05-22-enforce-single-tool-type-with-port-config/tasks.md diff --git a/openspec/specs/frontend-foundation/spec.md b/openspec/specs/frontend-foundation/spec.md index 9c0f9fa..3839f0a 100644 --- a/openspec/specs/frontend-foundation/spec.md +++ b/openspec/specs/frontend-foundation/spec.md @@ -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+ diff --git a/openspec/specs/tool-type-port-visibility/spec.md b/openspec/specs/tool-type-port-visibility/spec.md new file mode 100644 index 0000000..3463e56 --- /dev/null +++ b/openspec/specs/tool-type-port-visibility/spec.md @@ -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 diff --git a/openspec/specs/tool-type-single-interface/spec.md b/openspec/specs/tool-type-single-interface/spec.md new file mode 100644 index 0000000..15c460a --- /dev/null +++ b/openspec/specs/tool-type-single-interface/spec.md @@ -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 diff --git a/openspec/specs/tool-types-definition/spec.md b/openspec/specs/tool-types-definition/spec.md index 394ab5d..da05e01 100644 --- a/openspec/specs/tool-types-definition/spec.md +++ b/openspec/specs/tool-types-definition/spec.md @@ -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