merge: align dev branch with main
This commit is contained in:
@@ -111,36 +111,23 @@ 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.
|
||||
### Requirement: Projects Listing Page Layout
|
||||
The projects listing page SHALL display project cards with Settings, Delete, and Open Workspace actions, where Open Workspace is the rightmost action.
|
||||
|
||||
#### 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
|
||||
#### Scenario: Project card action layout
|
||||
- GIVEN the projects listing page
|
||||
- WHEN project cards are rendered
|
||||
- THEN each card shows actions in order: Settings link, Delete button, Open Workspace button (rightmost)
|
||||
|
||||
### Requirement: Conditional Port Fields
|
||||
The tool workshop SHALL conditionally show or hide port-related fields based on the selected interface type.
|
||||
#### Scenario: Navigate to project settings
|
||||
- GIVEN the projects listing page
|
||||
- WHEN a user clicks the Settings link
|
||||
- THEN they navigate to `/projects/:id/settings`
|
||||
|
||||
#### 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
|
||||
#### Scenario: No inline edit modal
|
||||
- GIVEN the projects listing page
|
||||
- WHEN a user views a project card
|
||||
- THEN no inline Edit button or modal dialog is available
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Manage git repositories as bare repos on disk with metadata in database.
|
||||
## Requirements
|
||||
### Requirement: Repository Creation
|
||||
|
||||
The system SHALL allow creating new bare git repositories with an optional SSH key association.
|
||||
The system SHALL allow creating new bare git repositories.
|
||||
|
||||
#### Scenario: Create repository
|
||||
- GIVEN an authenticated user with a project
|
||||
@@ -14,25 +14,6 @@ The system SHALL allow creating new bare git repositories with an optional SSH k
|
||||
- THEN a bare repo is initialized on disk at `/data/repos/{user_id}/{project_id}/{repo_name}.git`
|
||||
- AND metadata is stored in the database
|
||||
|
||||
#### Scenario: Create repository with SSH key
|
||||
- **GIVEN** an authenticated user with a project
|
||||
- **WHEN** they create a new repository with `ssh_key_id`
|
||||
- **THEN** a bare repo is initialized on disk
|
||||
- **AND** the SSH key association is stored in the database
|
||||
|
||||
### Requirement: Repository SSH key assignment
|
||||
The system SHALL allow associating an SSH key with a GitRepository for clone operations and container git access.
|
||||
|
||||
#### Scenario: Assign SSH key at repository creation
|
||||
- **GIVEN** an authenticated user creating a repository
|
||||
- **WHEN** they provide an `ssh_key_id`
|
||||
- **THEN** the repository is associated with that SSH key
|
||||
|
||||
#### Scenario: Update repository SSH key
|
||||
- **GIVEN** an authenticated user with an existing repository
|
||||
- **WHEN** they call `PATCH /repositories/{id}/ssh-key` with a new `ssh_key_id`
|
||||
- **THEN** the repository's SSH key association is updated
|
||||
|
||||
### Requirement: Repository Cloning
|
||||
|
||||
The system SHALL support cloning external repositories.
|
||||
|
||||
@@ -31,17 +31,38 @@ The system SHALL list projects owned by the authenticated user, including relate
|
||||
- WHEN one user requests their project list
|
||||
- THEN only that user's projects are returned
|
||||
|
||||
### Requirement: Project Updates
|
||||
The system SHALL support updating project details for project owners only.
|
||||
### Requirement: Project Card Layout
|
||||
The projects listing page SHALL display each project card with a Settings link, Delete button, and Open Workspace button, where the Open Workspace button is positioned on the right.
|
||||
|
||||
#### Scenario: Update project
|
||||
- GIVEN a project owner
|
||||
- WHEN they update the name or description
|
||||
#### Scenario: View project card actions
|
||||
- GIVEN the projects listing page
|
||||
- WHEN a project card is rendered
|
||||
- THEN it displays:
|
||||
- A Settings link navigating to `/projects/:id/settings`
|
||||
- A Delete button with confirmation
|
||||
- An Open Workspace button positioned on the right side
|
||||
|
||||
#### Scenario: Navigate to project settings
|
||||
- GIVEN the projects listing page
|
||||
- WHEN a user clicks the Settings link on a project card
|
||||
- THEN they are navigated to the project settings page
|
||||
|
||||
#### Scenario: No inline edit on project cards
|
||||
- GIVEN the projects listing page
|
||||
- WHEN a project card is rendered
|
||||
- THEN no inline Edit button or modal dialog is present
|
||||
|
||||
### Requirement: Project Updates
|
||||
The system SHALL support updating project details for project owners via the project settings page.
|
||||
|
||||
#### Scenario: Update project via settings
|
||||
- GIVEN a project owner viewing the project settings page
|
||||
- WHEN they update the name or description and save
|
||||
- THEN the changes are persisted
|
||||
|
||||
#### Scenario: Non-owner update denied
|
||||
- GIVEN a user who is not the project owner
|
||||
- WHEN they attempt to update project details
|
||||
- WHEN they attempt to update project details via the settings page
|
||||
- THEN the system responds with forbidden status
|
||||
|
||||
### Requirement: Project Deletion
|
||||
|
||||
@@ -1,95 +1,90 @@
|
||||
## MODIFIED Requirements
|
||||
# Tool Instance Management Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Launch, monitor, and manage development tool instances in Docker containers.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Tool Instance Creation
|
||||
|
||||
The system SHALL create and launch tool instances from repositories.
|
||||
|
||||
#### Scenario: Launch tool
|
||||
- GIVEN an authenticated user with a project and repository
|
||||
- WHEN they create a tool instance
|
||||
- THEN:
|
||||
1. A unique subdomain is generated: `{tool-name}-{tool-id}.hq.local`
|
||||
2. The Docker Compose template is rendered with project values
|
||||
3. `docker compose up -d` is executed
|
||||
4. Container ID and status are stored
|
||||
|
||||
### Requirement: Tool Lifecycle
|
||||
|
||||
The system SHALL manage tool lifecycle operations.
|
||||
|
||||
#### Scenario: Stop tool
|
||||
- GIVEN a running tool instance
|
||||
- WHEN the user stops it
|
||||
- THEN `docker compose stop` is executed
|
||||
- AND status is updated to "stopped"
|
||||
|
||||
#### Scenario: Start tool
|
||||
- GIVEN a stopped tool instance
|
||||
- WHEN the user starts it
|
||||
- THEN `docker compose start` is executed
|
||||
- AND status is updated to "running"
|
||||
|
||||
#### Scenario: Delete tool
|
||||
- GIVEN a tool instance
|
||||
- WHEN the user deletes it
|
||||
- THEN the container and volumes are removed
|
||||
- AND the database record is deleted
|
||||
|
||||
### Requirement: Traefik Integration
|
||||
|
||||
The system SHALL auto-generate Traefik labels for routing.
|
||||
|
||||
#### Scenario: Route generation
|
||||
- GIVEN a running tool instance
|
||||
- THEN these labels are set:
|
||||
- `traefik.enable=true`
|
||||
- `traefik.http.routers.{tool_id}.rule=Host(\`{subdomain}.hq.local\`)`
|
||||
- `traefik.http.routers.{tool_id}.entrypoints=web`
|
||||
- `traefik.http.services.{tool_id}.loadbalancer.server.port={port}`
|
||||
|
||||
### Requirement: Status Monitoring
|
||||
The system SHALL track tool status with startup and health states.
|
||||
|
||||
#### Scenario: Status check with health details
|
||||
- **GIVEN** a tool instance
|
||||
- **WHEN** status is queried
|
||||
- **THEN** the real-time container status is returned:
|
||||
- `pending`: Instance created, container not yet started
|
||||
- `starting`: Container is running, readiness probe in progress
|
||||
- `running`: Container is running and probe passed (or terminal tool)
|
||||
- `unhealthy`: Container is running but probe failed/timed out
|
||||
- `stopped`: Container was stopped by user
|
||||
- `error`: Container failed to start or crashed
|
||||
The system SHALL track tool status.
|
||||
|
||||
## ADDED Requirements
|
||||
#### Scenario: Status check
|
||||
- GIVEN a tool instance
|
||||
- WHEN status is queried
|
||||
- THEN the real-time container status is returned:
|
||||
- pending, building, running, stopped, error
|
||||
|
||||
### Requirement: Health check endpoint enhancement
|
||||
The system SHALL provide detailed health information through the health check endpoint.
|
||||
### Requirement: Log Access
|
||||
|
||||
#### Scenario: Health check with container and tunnel status
|
||||
- **GIVEN** a running instance
|
||||
- **WHEN** `GET /instances/{id}/health` is called
|
||||
- **THEN** the response includes:
|
||||
- `healthy`: boolean - overall health
|
||||
- `container_status`: "running", "exited", "restarting", or "not_found"
|
||||
- `tunnel_status`: "healthy", "unreachable", "error_response", or "not_applicable"
|
||||
- `tunnel_status_code`: HTTP status code or null
|
||||
- `probe_status`: "passed", "failed", "pending", or "not_configured"
|
||||
- `last_probe_output`: string or null
|
||||
The system SHALL provide access to container logs.
|
||||
|
||||
### Requirement: Smart tunnel recreation
|
||||
The system SHALL only allow tunnel recreation when the tunnel itself is broken.
|
||||
#### Scenario: View logs
|
||||
- GIVEN a tool instance
|
||||
- WHEN logs are requested
|
||||
- THEN the last 100 lines are returned
|
||||
- AND live streaming is available via WebSocket
|
||||
|
||||
#### Scenario: Recreate tunnel for unreachable tunnel
|
||||
- **GIVEN** an instance with `tunnel_status: "unreachable"`
|
||||
- **WHEN** the recreate tunnel endpoint is called
|
||||
- **THEN** the tunnel is recreated
|
||||
- **AND** the new URL is returned
|
||||
## Dependencies
|
||||
|
||||
#### Scenario: Block recreation for application errors
|
||||
- **GIVEN** an instance with `tunnel_status: "error_response"` (e.g., HTTP 502)
|
||||
- **WHEN** the recreate tunnel endpoint is called
|
||||
- **THEN** the request is rejected with 400 Bad Request
|
||||
- **AND** the error message explains the tunnel is working but the application is returning errors
|
||||
- tool-types (tool definitions)
|
||||
- git-repo (repository access)
|
||||
- project-management (project context)
|
||||
- Docker runtime
|
||||
- Traefik reverse proxy
|
||||
|
||||
### Requirement: Clone mode instance creation
|
||||
The system SHALL support creating tool instances with a clone mode that clones the repository into the instance directory.
|
||||
## Quality Gates
|
||||
|
||||
#### Scenario: Create instance in clone mode
|
||||
- **GIVEN** an authenticated user with a repository that has an SSH key and remote URL
|
||||
- **WHEN** they create an instance with `clone_mode: "clone"` and `branch: "main"`
|
||||
- **THEN** the system clones the repository into the instance directory
|
||||
- **AND** the compose file uses the clone path as `REPO_PATH`
|
||||
- **AND** the instance record stores `clone_mode="clone"` and `branch="main"`
|
||||
|
||||
#### Scenario: Create instance in mount mode
|
||||
- **GIVEN** an authenticated user with a repository
|
||||
- **WHEN** they create an instance with `clone_mode: "mount"` (or omit the field)
|
||||
- **THEN** the compose file uses the host repository path as `REPO_PATH`
|
||||
- **AND** the instance record stores `clone_mode="mount"`
|
||||
|
||||
### Requirement: SSH key mounting for git operations
|
||||
The system SHALL mount the repository's SSH key into clone-mode containers for git operations.
|
||||
|
||||
#### Scenario: Start clone-mode instance
|
||||
- **GIVEN** a clone-mode instance with an associated SSH key
|
||||
- **WHEN** the instance is started
|
||||
- **THEN** the SSH key is decrypted and written to `instance_dir/.ssh/`
|
||||
- **AND** the `.ssh` directory is mounted into the container
|
||||
- **AND** the container can perform git push/pull operations
|
||||
|
||||
### Requirement: Dirty check on clone deletion
|
||||
The system SHALL check for uncommitted changes before deleting a clone-mode instance.
|
||||
|
||||
#### Scenario: Delete clean clone
|
||||
- **GIVEN** a clone-mode instance with no uncommitted changes
|
||||
- **WHEN** the user requests deletion
|
||||
- **THEN** the instance is deleted successfully
|
||||
|
||||
#### Scenario: Delete dirty clone with confirmation
|
||||
- **GIVEN** a clone-mode instance with uncommitted changes
|
||||
- **WHEN** the user requests deletion
|
||||
- **THEN** the system returns a warning with change details
|
||||
- **AND** the user must confirm deletion
|
||||
|
||||
#### Scenario: Force delete dirty clone
|
||||
- **GIVEN** a clone-mode instance with uncommitted changes
|
||||
- **WHEN** the user requests deletion with `force=true`
|
||||
- **THEN** the instance is deleted regardless of uncommitted changes
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
None.
|
||||
- `pytest` must pass
|
||||
- `mypy .` must pass
|
||||
- `ruff check .` must pass
|
||||
- `npm run typecheck` must pass
|
||||
- `npm run lint` must pass
|
||||
|
||||
@@ -16,19 +16,6 @@ The system SHALL provide terminal sessions via WebSocket.
|
||||
- THEN a WebSocket connection is established
|
||||
- AND a shell is spawned in the container via `docker exec`
|
||||
|
||||
#### Scenario: Open terminal with startup command
|
||||
- GIVEN a running tool instance with a tool type that has `startup_command` set
|
||||
- WHEN the user opens the terminal
|
||||
- THEN a WebSocket connection is established
|
||||
- AND the startup command is executed before the interactive shell
|
||||
- AND the shell is spawned in the container via `docker exec`
|
||||
|
||||
#### Scenario: Open terminal without startup command
|
||||
- GIVEN a running tool instance with a tool type that has no `startup_command`
|
||||
- WHEN the user opens the terminal
|
||||
- THEN a WebSocket connection is established
|
||||
- AND the shell spawns directly without any startup execution
|
||||
|
||||
### Requirement: Terminal I/O
|
||||
|
||||
The system SHALL stream terminal I/O via WebSocket.
|
||||
@@ -64,12 +51,6 @@ The system SHALL manage terminal sessions.
|
||||
- THEN the session is cleaned up
|
||||
- AND the shell process is terminated
|
||||
|
||||
#### Scenario: Reset terminal session runs startup command
|
||||
- GIVEN an active terminal session
|
||||
- WHEN the user resets the session
|
||||
- THEN a new shell is spawned
|
||||
- AND the startup command executes before the new interactive shell
|
||||
|
||||
### Requirement: Access Control
|
||||
|
||||
The system SHALL restrict terminal access.
|
||||
|
||||
@@ -11,14 +11,8 @@ 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
|
||||
- `startup_command`: optional text — command to run before interactive shell for terminal sessions
|
||||
- `is_builtin`: boolean flag for system-defined types
|
||||
- `created_at`/`updated_at`: timestamps
|
||||
|
||||
@@ -36,11 +30,8 @@ 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 `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 the compose template YAML
|
||||
- AND validates all required variables are present in template
|
||||
- AND accepts optional `startup_command` field
|
||||
- AND returns 201 Created with the new tool type
|
||||
|
||||
#### Scenario: Get tool type
|
||||
@@ -53,14 +44,9 @@ 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 accepts optional `startup_command` field
|
||||
- AND re-validates the compose template
|
||||
- AND returns 200 OK with updated tool type
|
||||
|
||||
#### Scenario: Get tool type includes startup command
|
||||
- GIVEN an authenticated user
|
||||
- WHEN they GET /api/tool-types/{id}
|
||||
- THEN the response includes `startup_command` if set
|
||||
|
||||
#### Scenario: Delete tool type
|
||||
- GIVEN an admin user
|
||||
- WHEN they DELETE /api/tool-types/{id}
|
||||
@@ -112,33 +98,3 @@ 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
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
## MODIFIED Requirements
|
||||
# Tool Type Definition Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Define and register tool types using Docker Compose templates for launching development tools.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Tool Type Model
|
||||
|
||||
The system SHALL store tool type definitions in the database without built-in vs custom distinction.
|
||||
The system SHALL store tool type definitions in the database.
|
||||
|
||||
#### Scenario: Create tool type
|
||||
- GIVEN an admin user
|
||||
@@ -14,18 +20,32 @@ The system SHALL store tool type definitions in the database without built-in vs
|
||||
- icon: Visual identifier
|
||||
- category: Tool category
|
||||
- default_env_vars: Default environment variables
|
||||
- default_port: **Required** primary port the tool listens on
|
||||
- interfaces: List of supported interfaces ("web", "terminal")
|
||||
- default_ports: Exposed ports
|
||||
|
||||
#### Scenario: Tool type without port rejected
|
||||
- GIVEN a user creating a tool type without `default_port`
|
||||
- WHEN the request is submitted
|
||||
- THEN the system rejects with a 422 validation error
|
||||
### Requirement: Template Variables
|
||||
|
||||
The system SHALL support template variable substitution.
|
||||
|
||||
#### Scenario: Variable substitution
|
||||
- GIVEN a Docker Compose template
|
||||
- WHEN it's rendered for a tool instance
|
||||
- THEN these variables are substituted:
|
||||
- `{{REPO_PATH}}`: Path to the git repository
|
||||
- `{{WORKSPACE_DIR}}`: Working directory inside container
|
||||
- `{{USER_ID}}`: User identifier
|
||||
- `{{PROJECT_ID}}`: Project identifier
|
||||
- `{{TOOL_ID}}`: Tool instance identifier
|
||||
|
||||
### Requirement: Built-in Tools
|
||||
|
||||
**Reason**: Built-in tools are now regular preconfigured tool types in the database, not special privileged types.
|
||||
**Migration**: Built-in tool types (code-server, jupyter-notebook, opencode) are seeded as regular database records during migration. They can be edited or deleted like any other tool type.
|
||||
The system SHALL include default tool types.
|
||||
|
||||
#### Scenario: Built-in tools
|
||||
- GIVEN a fresh installation
|
||||
- THEN these tool types are pre-configured:
|
||||
- code-server: VS Code in browser
|
||||
- jupyter-notebook: Jupyter notebooks
|
||||
- opencode: OpenCode agent environment
|
||||
|
||||
### Requirement: Template Validation
|
||||
|
||||
@@ -36,7 +56,12 @@ The system SHALL validate Docker Compose templates.
|
||||
- WHEN a user tries to create/update a tool type
|
||||
- THEN the system rejects with validation errors
|
||||
|
||||
#### Scenario: Port not exposed in template
|
||||
- GIVEN a tool type with `default_port: 8443`
|
||||
- WHEN the compose template does not expose port 8443
|
||||
- THEN the system rejects with a validation error indicating the port mismatch
|
||||
## Dependencies
|
||||
|
||||
- Database models: ToolType
|
||||
|
||||
## Quality Gates
|
||||
|
||||
- `pytest` must pass
|
||||
- `mypy .` must pass
|
||||
- `ruff check .` must pass
|
||||
|
||||
Reference in New Issue
Block a user