feat: implement docker infrastructure (US-001)

- Add docker-compose.yml with postgres, redis, api, and web services
- Add multi-stage Dockerfile for API (Python 3.11)
- Add multi-stage Dockerfile for web (Node.js 20 + nginx)
- Add Makefile with common development commands
- Add .env.example with all required environment variables
- Add placeholder pyproject.toml and package.json for builds
- Configure health checks for all services
- Setup persistent volumes for postgres, redis, and repos
- Run services as non-root users
This commit is contained in:
2026-05-16 17:44:39 +00:00
parent 212d072417
commit e7819bfc82
246 changed files with 3625 additions and 17311 deletions
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-14
@@ -1,67 +0,0 @@
## Context
code-server is a VS Code instance running in a browser. The platform needs to spawn it as a Docker container with proper mounts, auth, and routing. This builds on the tool registry (FN-003) and deployment config (FN-006).
Current state:
- code-server manifest exists in apps/api/app/tools/manifests/code-server.yml
- ToolInstance model exists with status field
- No spawn orchestration logic
- No frontend UI for spawning
## Goals / Non-Goals
**Goals:**
- Spawn code-server containers via Docker Compose
- Mount user workspace, configs, secrets, and SSH keys
- Route via Traefik subdomain
- Track container status (creating, running, stopped, error)
- Provide spawn UI in frontend
**Non-Goals:**
- Support for other IDEs (deferred post-MVP)
- Container resource limits (CPU/memory) - basic only
- Automatic workspace backup
- Multi-instance load balancing
## Decisions
**1. Docker Compose API for container management**
- Rationale: Higher-level than Docker SDK, handles networking and volumes declaratively
- Alternative: Docker SDK directly - more control but more complex
**2. code-server runs with platform auth proxy**
- Rationale: Don't manage separate code-server passwords. Traefik middleware handles auth.
- Implementation: Traefik forwardAuth to platform API for session validation
**3. Workspace mounted from host directory**
- Rationale: Persistent storage between restarts. Easy backup.
- Path: `/data/workspaces/{user_slug}/{project_slug}`
**4. SSH keys mounted as read-only volume**
- Rationale: code-server needs Git access but shouldn't modify keys
- Mount: `/home/coder/.ssh/` with 0400 permissions
**5. Spawn is synchronous (blocking) API**
- Rationale: Simpler UX. Container creation is fast (< 5s).
- Alternative: Async with polling - more complex, unnecessary for MVP
## Risks / Trade-offs
**[Risk] Docker socket exposure is a security risk**
→ Mitigation: Run API with limited Docker access. Consider Docker socket proxy in production.
**[Risk] Container failures leave dangling resources**
→ Mitigation: Implement cleanup on error. Periodic garbage collection of orphaned containers.
**[Risk] code-server auth bypass**
→ Mitigation: Disable code-server auth (PASSWORD: ""). Rely entirely on Traefik forwardAuth.
## Migration Plan
No migration. New feature.
## Open Questions
1. Should we pre-pull Docker images or let Compose handle it?
2. Do we need container health checks before marking as "running"?
3. Should spawned containers auto-stop after inactivity?
@@ -1,31 +0,0 @@
## Why
Tool registry (FN-003) and deployment config (FN-006) are prerequisites for spawning tools. code-server is the primary user-facing tool in MVP. Without a spawn flow, users cannot launch development environments, which is the core value proposition.
## What Changes
- **code-server manifest refinement**: Update the built-in manifest with proper Docker image, ports, volumes, and config options
- **Spawn flow API**: Backend endpoint that creates a tool instance, generates Docker Compose service, and starts the container
- **Frontend spawn UI**: Form for selecting tool, project, and optional config overrides
- **Runtime integration**: Mount workspace, configs, secrets, and SSH keys into the code-server container
- **Auth proxy**: Ensure code-server is protected behind the platform's auth (no separate code-server password)
- **Status tracking**: Poll container status and expose it via API
## Capabilities
### New Capabilities
- `tool-spawn-api`: Backend endpoint for spawning tool instances
- `codeserver-manifest`: Refined code-server manifest with runtime configuration
- `spawn-ui`: Frontend form for tool selection and spawn configuration
- `container-lifecycle`: Start, stop, and status tracking for tool containers
### Modified Capabilities
- None (extends existing tool registry)
## Impact
- **apps/api/app/tools/manifests/code-server.yml**: Updated manifest
- **apps/api/app/routers/tool_instances.py**: Spawn endpoint enhancements
- **apps/api/app/services/spawn.py**: New spawn orchestration service
- **apps/web/src/**: New spawn UI components
- **docker-compose.yml**: May need updates for Docker socket access
@@ -1,23 +0,0 @@
## ADDED Requirements
### Requirement: code-server manifest defines runtime configuration
The system SHALL provide a complete code-server manifest.
#### Scenario: Manifest includes Docker configuration
- **WHEN** the code-server manifest is loaded
- **THEN** it specifies the Docker image (codercom/code-server)
- **AND** it defines exposed ports (8080)
- **AND** it defines volume mounts (workspace, config, ssh)
#### Scenario: Manifest includes environment variables
- **WHEN** the manifest is used for spawning
- **THEN** it defines required environment variables
- **AND** it defines optional config overrides
### Requirement: code-server manifest is valid
The system SHALL validate the code-server manifest against the tool manifest schema.
#### Scenario: Schema validation
- **WHEN** the manifest is loaded at startup
- **THEN** it passes schema validation
- **AND** any errors prevent application startup
@@ -1,29 +0,0 @@
## ADDED Requirements
### Requirement: Tool instance status is tracked
The system SHALL track the lifecycle status of tool instances.
#### Scenario: Status transitions
- **WHEN** a tool instance is created
- **THEN** its status is "creating"
- **AND** when the container starts, status becomes "running"
- **AND** when stopped, status becomes "stopped"
- **AND** on error, status becomes "error"
#### Scenario: Status polling
- **WHEN** the user views a tool instance
- **THEN** the frontend polls the status endpoint
- **AND** updates the UI when status changes
### Requirement: Tool instances can be stopped and restarted
The system SHALL allow stopping and restarting tool instances.
#### Scenario: Stop instance
- **WHEN** the user clicks "Stop" on a running instance
- **THEN** the system stops the Docker container
- **AND** updates the status to "stopped"
#### Scenario: Restart instance
- **WHEN** the user clicks "Start" on a stopped instance
- **THEN** the system starts the existing container
- **AND** updates the status to "running"
@@ -1,21 +0,0 @@
## ADDED Requirements
### Requirement: User can spawn a tool from the UI
The system SHALL provide a user interface for spawning tools.
#### Scenario: Spawn form
- **WHEN** the user navigates to /tools/spawn
- **THEN** a form is displayed with tool selection
- **AND** project selection dropdown
- **AND** optional config override fields
#### Scenario: Tool selection
- **WHEN** the user selects a tool from the dropdown
- **THEN** the form shows tool-specific configuration options
- **AND** a description of the tool
#### Scenario: Spawn submission
- **WHEN** the user submits the spawn form
- **THEN** the frontend calls POST /api/v1/tool-instances
- **AND** displays a loading state
- **AND** redirects to the tool instance detail page on success
@@ -1,29 +0,0 @@
## ADDED Requirements
### Requirement: API can spawn a tool instance
The system SHALL provide an endpoint to create and start a tool instance.
#### Scenario: Spawn code-server
- **WHEN** a POST request is made to /api/v1/tool-instances with tool_id and project_id
- **THEN** the system creates a ToolInstance record
- **AND** generates a Docker Compose service definition
- **AND** starts the container via Docker Compose API
- **AND** returns the tool instance with status "creating"
#### Scenario: Spawn with config overrides
- **WHEN** a spawn request includes config overrides
- **THEN** the overrides are merged with scope-resolved configs
- **AND** applied to the container environment
### Requirement: Spawn validates prerequisites
The system SHALL validate prerequisites before spawning.
#### Scenario: Valid project
- **WHEN** the spawn request references a project
- **THEN** the project must exist and belong to the user
- **AND** the tool definition must exist in the registry
#### Scenario: Duplicate spawn prevention
- **WHEN** a spawn request is made for an already-running instance
- **THEN** the system returns the existing instance
- **AND** does not create a duplicate container
@@ -1,58 +0,0 @@
## 1. Manifest Refinement
- [x] 1.1 Update apps/api/app/tools/manifests/code-server.yml with complete runtime config
- [x] 1.2 Add Docker image, ports, volumes, env vars to manifest
- [x] 1.3 Validate manifest against ToolManifest schema
- [x] 1.4 Test manifest loading at application startup
## 2. Spawn Service
- [x] 2.1 Create apps/api/app/services/spawn.py with SpawnService class
- [x] 2.2 Implement Docker Compose service generation from manifest
- [x] 2.3 Implement container start/stop via Docker Compose API
- [x] 2.4 Integrate Traefik label generation (FN-006)
- [x] 2.5 Integrate config/secrets runtime injection (FN-009)
- [x] 2.6 Implement workspace volume mounting
- [x] 2.7 Implement SSH key mounting for Git access
- [x] 2.8 Add container status polling
## 3. Backend API
- [x] 3.1 Enhance POST /api/v1/tool-instances with spawn logic
- [x] 3.2 Add DELETE /api/v1/tool-instances/:id/stop endpoint
- [x] 3.3 Add POST /api/v1/tool-instances/:id/start endpoint
- [x] 3.4 Add GET /api/v1/tool-instances/:id/status endpoint
- [x] 3.5 Add validation for project ownership and tool existence
- [x] 3.6 Prevent duplicate spawn of running instances
## 4. Frontend UI
- [x] 4.1 Create ToolSpawn page at /tools/spawn
- [x] 4.2 Implement tool selection dropdown from registry
- [x] 4.3 Implement project selection dropdown
- [x] 4.4 Add config override fields based on manifest
- [x] 4.5 Create ToolInstanceDetail page at /tools/:id
- [x] 4.6 Display instance status, subdomain URL, and controls (stop/start)
- [x] 4.7 Add "Open Tool" button that opens subdomain in new tab
## 5. Auth Integration
- [x] 5.1 Configure Traefik forwardAuth middleware for code-server
- [x] 5.2 Implement auth validation endpoint for Traefik
- [x] 5.3 Disable code-server built-in auth (PASSWORD: "")
- [x] 5.4 Test that unauthenticated requests are blocked
## 6. Testing & Verification
- [x] 6.1 Write backend tests for SpawnService
- [x] 6.2 Write backend tests for tool instance lifecycle endpoints
- [x] 6.3 Test container spawn in local Docker environment
- [x] 6.4 Verify Traefik routing to spawned container
- [x] 6.5 Run full test suite: `make test`
- [x] 6.6 Run linters: `make lint`
## 7. Documentation
- [x] 7.1 Update docs/development.md with spawn workflow
- [x] 7.2 Add code-server setup guide to docs/architecture.md
- [x] 7.3 Document auth proxy configuration
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-14
@@ -1,63 +0,0 @@
## Context
The backend has Config and Secret models (FN-004) with scope fields, but no frontend UI or runtime injection. SSH keys already use Fernet encryption (FN-011), so the encryption pattern is established. This design completes the config/secrets lifecycle.
Current state:
- Config model: key, value, scope (global/user/project/instance), scope_id
- Secret model: key, encrypted_value, scope, scope_id
- Fernet encryption utilities exist in app/encryption.py
- No UI for management
- No runtime injection into containers
## Goals / Non-Goals
**Goals:**
- Allow users to manage configs and secrets via UI
- Inject configs/secrets into tool containers at spawn time
- Support scope-based inheritance (instance overrides project overrides user overrides global)
- Maintain encryption for all secret values
**Non-Goals:**
- Secret versioning or history
- Automatic secret rotation
- Integration with external secret managers (Vault, AWS Secrets Manager)
- Config/secrets for non-tool resources
## Decisions
**1. Mount configs as files, secrets as env vars**
- Rationale: Configs (JSON) are often files (e.g., settings.json). Secrets are typically env vars.
- Config mount: `/app/config/<key>.json`
- Secret env: `<KEY>=<decrypted_value>`
**2. Scope resolution: closest match wins**
- Rationale: Instance-specific values should override project defaults
- Resolution order: instance → project → user → global
**3. Secret values never sent to frontend decrypted**
- Rationale: Security. Frontend only sees masked values (e.g., `••••••`).
- Decryption happens only in backend during runtime injection
**4. Config values are plaintext (not encrypted)**
- Rationale: Configs are not sensitive. Encrypting them adds complexity without security benefit.
## Risks / Trade-offs
**[Risk] Secret injection at spawn time could fail silently**
→ Mitigation: Validate all referenced secrets exist before spawning. Return error if missing.
**[Risk] Config files in containers could be read by other processes**
→ Mitigation: Mount config files with restrictive permissions (0400). Run containers as non-root.
**[Risk] Large configs could exceed container env var limits**
→ Mitigation: Document size limits. Consider config file mounting for large values.
## Migration Plan
No migration needed. This extends existing models.
## Open Questions
1. Should configs support JSON schema validation?
2. Do we need bulk import/export for configs/secrets?
3. Should secret keys be validated against a naming convention?
@@ -1,29 +0,0 @@
## Why
Tool instances need runtime configuration and secrets (API keys, database passwords, etc.). The backend has Config and Secret models (FN-004), but there's no UI for users to manage these values, and no runtime injection mechanism to pass them into spawned containers.
## What Changes
- **Config management UI**: Frontend pages for creating, updating, and deleting config values at global/user/project/instance scopes
- **Secret management UI**: Frontend pages for encrypted secret storage with masked value display
- **Runtime injection**: Backend service that mounts configs and secrets into tool containers at spawn time
- **Scope-based access control**: Configs/secrets respect scope hierarchy (global → user → project → instance)
- **Encryption verification**: Ensure Fernet encryption is properly applied to all secret values
## Capabilities
### New Capabilities
- `config-management`: CRUD operations for configuration values with scope support
- `secret-management`: Encrypted storage and retrieval of sensitive values
- `runtime-injection`: Mount configs and secrets into tool containers at spawn
### Modified Capabilities
- None (extends existing Config/Secret models)
## Impact
- **apps/web/src/**: New config and secret management pages
- **apps/api/app/routers/configs.py**: Enhanced with scope filtering
- **apps/api/app/routers/secrets.py**: Enhanced with scope filtering
- **apps/api/app/services/**: New runtime injection service
- **apps/api/app/models/**: Potential Config/Secret model updates for scope validation
@@ -1,37 +0,0 @@
## ADDED Requirements
### Requirement: User can create config values
The system SHALL allow users to create configuration values at various scopes.
#### Scenario: Create project config
- **WHEN** the user navigates to project settings
- **AND** clicks "Add Config"
- **THEN** a form appears with key, value, and scope fields
- **AND** submitting creates a config at the selected scope
#### Scenario: Config scope validation
- **WHEN** the user creates a config
- **THEN** the scope must be one of: global, user, project, instance
- **AND** the scope_id must match the selected scope type
### Requirement: User can view and update configs
The system SHALL display configs with scope-based filtering.
#### Scenario: List configs
- **WHEN** the user views configs for a project
- **THEN** all configs visible at project scope or above are displayed
- **AND** values are shown as formatted JSON
#### Scenario: Update config
- **WHEN** the user edits a config value
- **THEN** the updated value is saved
- **AND** the change takes effect on next tool spawn
### Requirement: User can delete configs
The system SHALL allow deletion of config values.
#### Scenario: Delete config
- **WHEN** the user clicks delete on a config
- **THEN** a confirmation dialog appears
- **AND** confirming removes the config
- **AND** the config is no longer injected into containers
@@ -1,40 +0,0 @@
## ADDED Requirements
### Requirement: Configs are mounted into tool containers
The system SHALL mount configuration values as files into spawned tool containers.
#### Scenario: Config file mount
- **WHEN** a tool instance is spawned
- **THEN** all applicable configs are written to /app/config/
- **AND** each config is a separate JSON file named by key
- **AND** files have restrictive permissions (0400)
#### Scenario: Config scope resolution
- **WHEN** configs are resolved for a tool instance
- **THEN** the system collects configs from all applicable scopes
- **AND** instance scope overrides project scope
- **AND** project scope overrides user scope
- **AND** user scope overrides global scope
### Requirement: Secrets are injected as environment variables
The system SHALL inject secret values as environment variables into tool containers.
#### Scenario: Secret env var injection
- **WHEN** a tool instance is spawned
- **THEN** all applicable secrets are decrypted
- **AND** injected as environment variables with uppercase keys
- **AND** the container process can access them
#### Scenario: Secret scope resolution
- **WHEN** secrets are resolved for a tool instance
- **THEN** the same scope hierarchy applies as configs
- **AND** closest scope wins on key collision
### Requirement: Missing secrets fail spawn
The system SHALL prevent spawning if referenced secrets are missing.
#### Scenario: Validate secrets before spawn
- **WHEN** a spawn request references a secret by key
- **AND** the secret does not exist in any applicable scope
- **THEN** the spawn fails with a clear error message
- **AND** no container is created
@@ -1,37 +0,0 @@
## ADDED Requirements
### Requirement: User can create secrets
The system SHALL allow users to store encrypted secret values.
#### Scenario: Create secret
- **WHEN** the user navigates to project secrets
- **AND** clicks "Add Secret"
- **THEN** a form appears with key and value fields
- **AND** the value is encrypted with Fernet before storage
- **AND** the user sees a masked value (e.g., ••••••) after creation
#### Scenario: Secret scope
- **WHEN** the user creates a secret
- **THEN** the scope can be user, project, or instance
- **AND** the secret is only visible within that scope hierarchy
### Requirement: Secrets are never exposed decrypted
The system SHALL prevent decrypted secret values from being sent to the frontend.
#### Scenario: Secret list display
- **WHEN** the user views the secrets list
- **THEN** only secret keys and scopes are visible
- **AND** values are always masked
#### Scenario: Secret update
- **WHEN** the user updates a secret
- **THEN** only the new value is sent to the backend
- **AND** the old value is replaced (not displayed)
### Requirement: User can delete secrets
The system SHALL allow deletion of secret values.
#### Scenario: Delete secret
- **WHEN** the user deletes a secret
- **THEN** the encrypted value is permanently removed
- **AND** the secret is no longer injected into containers
@@ -1,48 +0,0 @@
## 1. Backend Enhancements
- [x] 1.1 Update Config model with scope validation methods
- [x] 1.2 Update Secret model with encryption verification
- [x] 1.3 Enhance configs router with scope filtering and hierarchy resolution
- [x] 1.4 Enhance secrets router with scope filtering and hierarchy resolution
- [x] 1.5 Create apps/api/app/services/runtime_injection.py for config/secret resolution
- [x] 1.6 Implement config file generation for container mounts
- [x] 1.7 Implement secret env var generation for container injection
- [x] 1.8 Add validation to fail spawn when referenced secrets are missing
## 2. Frontend - Config Management
- [x] 2.1 Create ConfigList component at /projects/:id/configs
- [x] 2.2 Implement ConfigForm for creating/updating configs
- [x] 2.3 Add scope selector (project/instance/global) to config form
- [x] 2.4 Implement config delete with confirmation
- [x] 2.5 Add JSON formatting for config values
## 3. Frontend - Secret Management
- [x] 3.1 Create SecretList component at /projects/:id/secrets
- [x] 3.2 Implement SecretForm for creating/updating secrets
- [x] 3.3 Add masked value display (never show decrypted)
- [x] 3.4 Implement secret delete with confirmation
- [x] 3.5 Add scope selector to secret form
## 4. Runtime Integration
- [x] 4.1 Integrate runtime injection into tool instance spawn endpoint
- [x] 4.2 Update Docker Compose generation to include config mounts
- [x] 4.3 Update Docker Compose generation to include secret env vars
- [x] 4.4 Test config/secret injection in local Docker environment
## 5. Testing & Verification
- [x] 5.1 Write backend tests for config scope resolution
- [x] 5.2 Write backend tests for secret encryption/decryption
- [x] 5.3 Write backend tests for runtime injection
- [x] 5.4 Write frontend tests for ConfigList and SecretList
- [x] 5.5 Run full test suite: `make test`
- [x] 5.6 Run linters: `make lint`
## 6. Documentation
- [x] 6.1 Update docs/development.md with config/secrets workflow
- [x] 6.2 Add config/secrets UI guide to docs/architecture.md
- [x] 6.3 Document scope hierarchy and resolution rules
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-14
@@ -1,67 +0,0 @@
## Context
The platform routes tool instances via Traefik using subdomain patterns like `https://{tool}-{project}-{user}.{tool_domain}`. Currently, there's no automated label generation or production deployment configuration. This design establishes the deployment architecture.
Current state:
- `docker-compose.yml` for local dev only
- `docker-compose.traefik.yml` exists but is minimal
- `deploy/` directory has skeleton files
- No automated Traefik label generation
## Goals / Non-Goals
**Goals:**
- Generate Traefik labels automatically when spawning tools
- Provide production-ready Docker Compose stack
- Support Portainer-managed deployment
- Enable HTTPS with automatic certificate management
**Non-Goals:**
- Kubernetes deployment (deferred post-MVP)
- Multi-region or high-availability setup
- Custom reverse proxy (Traefik is the only supported option)
- Automatic DNS management
## Decisions
**1. Label generation in backend, not in Docker Compose**
- Rationale: Backend has all metadata (user slug, project slug, tool ID). Generating labels at spawn time is more flexible than static Compose files.
- Implementation: `TraefikLabelGenerator` service class
**2. Subdomain pattern: `{tool}-{project}-{user}.{domain}`**
- Rationale: Unique, deterministic, human-readable
- Example: `code-server-myapp-alice.headquarter.example.com`
**3. Separate Docker networks: `platform` and `tools`**
- Rationale: Network isolation between platform services and user tools
- Platform network: API, web, Traefik, database
- Tools network: Traefik + tool containers only
**4. Portainer as the deployment target**
- Rationale: Docker Compose-native, web UI for operators, supports stacks and webhooks
- Alternative: Raw Docker Compose on VM - less operator-friendly
**5. Let's Encrypt for HTTPS in production**
- Rationale: Free, automatic, Traefik has built-in support
- Alternative: Custom certificates - adds operational burden
## Risks / Trade-offs
**[Risk] Traefik label complexity grows with features**
→ Mitigation: Keep label generation centralized in one service class. Test label output against Traefik schema.
**[Risk] Portainer stack updates require downtime**
→ Mitigation: Use rolling updates where possible. Document blue-green deployment strategy.
**[Risk] Subdomain collision**
→ Mitigation: Enforce unique project slugs per user. Include user slug in subdomain.
## Migration Plan
No migration - new deployment stack is additive.
## Open Questions
1. Should we support custom domains per user/project in MVP?
2. Do we need basic auth or IP allow-listing for Traefik dashboard?
3. Should tool containers run on a separate Docker daemon for security?
@@ -1,31 +0,0 @@
## Why
The scaffold provides local Docker Compose development (FN-002) but lacks production deployment configuration. Without Traefik label generation and production stacks, tool instances cannot receive HTTPS subdomains, blocking the core value proposition of the platform.
## What Changes
- **Traefik label generator**: Backend service that generates Docker labels for subdomain routing based on tool instance metadata
- **Production Docker Compose stack**: `docker-compose.prod.yml` with API, web, Traefik, and PostgreSQL services
- **Portainer stack definition**: Docker Compose file optimized for Portainer deployment
- **Dynamic subdomain routing**: Automatic Traefik rule generation for spawned tool containers
- **HTTPS configuration**: Let's Encrypt or custom certificate support via Traefik
- **Network isolation**: Separate Docker networks for platform and tool containers
## Capabilities
### New Capabilities
- `traefik-label-generator`: Generate Traefik Docker labels for tool subdomain routing
- `production-compose-stack`: Production Docker Compose configuration
- `portainer-deployment`: Portainer-friendly stack definition and deployment guide
- `subdomain-routing`: Dynamic HTTPS subdomain allocation for tool instances
### Modified Capabilities
- None (this extends the existing deployment skeleton)
## Impact
- **apps/api/app/services/**: New Traefik label generation service
- **apps/api/app/routers/tool_instances.py**: Integrate label generation on spawn
- **deploy/**: New production deployment files
- **docker-compose.prod.yml**: Production stack definition
- **docs/deployment.md**: Updated deployment instructions
@@ -1,22 +0,0 @@
## ADDED Requirements
### Requirement: Stack deploys via Portainer
The system SHALL provide a Portainer-compatible stack definition.
#### Scenario: Portainer stack file
- **WHEN** an operator deploys via Portainer
- **THEN** they can paste the stack definition into Portainer's stack editor
- **AND** Portainer can pull and deploy all services
#### Scenario: Environment variables in Portainer
- **WHEN** the stack is deployed via Portainer
- **THEN** environment variables are configured in Portainer's UI
- **AND** the stack references these variables
### Requirement: Deployment documentation is complete
The system SHALL provide operator documentation for deployment.
#### Scenario: Deployment guide
- **WHEN** an operator reads docs/deployment.md
- **THEN** they find step-by-step instructions for Portainer deployment
- **AND** prerequisites and assumptions are clearly stated
@@ -1,28 +0,0 @@
## ADDED Requirements
### Requirement: Production stack includes all required services
The system SHALL provide a production Docker Compose stack with API, web, Traefik, and PostgreSQL.
#### Scenario: Stack services
- **WHEN** the production stack is deployed
- **THEN** the following services run: api, web, traefik, db
- **AND** Traefik routes requests to the appropriate service
- **AND** services communicate via isolated Docker networks
#### Scenario: Environment configuration
- **WHEN** the stack starts
- **THEN** it reads environment variables from .env
- **AND** sensitive values are not hardcoded
### Requirement: Production stack is secure by default
The system SHALL configure security headers and access controls in production.
#### Scenario: HTTPS only
- **WHEN** the stack runs in production
- **THEN** all traffic uses HTTPS
- **AND** HTTP redirects to HTTPS
#### Scenario: Network isolation
- **WHEN** the stack is deployed
- **THEN** platform services and tool containers are on separate networks
- **AND** tool containers cannot access the database directly
@@ -1,23 +0,0 @@
## ADDED Requirements
### Requirement: Each tool instance gets a unique subdomain
The system SHALL assign a unique HTTPS subdomain to each running tool instance.
#### Scenario: Subdomain pattern
- **WHEN** a tool instance is spawned
- **THEN** its subdomain follows `{tool}-{project}-{user}.{domain}`
- **AND** the subdomain is deterministic based on instance metadata
#### Scenario: Subdomain accessibility
- **WHEN** a tool instance reaches running status
- **THEN** its subdomain resolves via DNS
- **AND** Traefik routes the subdomain to the container
- **AND** the user can access the tool via the subdomain URL
### Requirement: Subdomain is released on stop
The system SHALL remove Traefik routing when a tool instance stops.
#### Scenario: Stop removes routing
- **WHEN** a tool instance is stopped
- **THEN** Traefik labels are removed or disabled
- **AND** the subdomain no longer routes to the container
@@ -1,24 +0,0 @@
## ADDED Requirements
### Requirement: Tool spawn generates Traefik labels
The system SHALL generate Docker labels for Traefik when spawning a tool instance.
#### Scenario: Label generation on spawn
- **WHEN** a tool instance is spawned
- **THEN** the backend generates Traefik router and service labels
- **AND** labels include rule, service, port, and TLS configuration
- **AND** labels are stored with the tool instance metadata
#### Scenario: Label format
- **WHEN** labels are generated for a tool instance
- **THEN** router rule uses Host(`{subdomain}.{domain}`)
- **AND** service points to the container's exposed port
- **AND** TLS is enabled with certResolver
### Requirement: Label generation handles multiple instances
The system SHALL generate unique labels for each tool instance.
#### Scenario: Unique router names
- **WHEN** multiple instances of the same tool exist
- **THEN** each instance gets a unique router name
- **AND** no label collisions occur
@@ -1,44 +0,0 @@
## 1. Traefik Label Generator
- [x] 1.1 Create apps/api/app/services/traefik.py with TraefikLabelGenerator class
- [x] 1.2 Implement subdomain generation from tool_id, project_slug, user_slug
- [x] 1.3 Generate router labels (rule, service, tls)
- [x] 1.4 Generate service labels (loadBalancer, port)
- [x] 1.5 Add middleware labels for security headers
- [x] 1.6 Write unit tests for label generation
## 2. Backend Integration
- [x] 2.1 Integrate label generation into tool instance spawn endpoint
- [x] 2.2 Store generated labels in tool_instance metadata
- [x] 2.3 Remove/disable labels on tool instance stop
- [x] 2.4 Update ToolInstance model to store labels JSON
## 3. Production Docker Compose
- [x] 3.1 Create docker-compose.prod.yml with api, web, traefik, db services
- [x] 3.2 Configure Traefik service with Let's Encrypt certificates
- [x] 3.3 Set up platform and tools networks
- [x] 3.4 Add health checks for all services
- [x] 3.5 Configure logging (JSON format, rotation)
## 4. Portainer Deployment
- [x] 4.1 Create deploy/portainer-stack.yml
- [x] 4.2 Add Portainer-specific environment variable documentation
- [x] 4.3 Create deploy/.env.example for production
- [x] 4.4 Test stack deployment locally with docker compose -f docker-compose.prod.yml
## 5. Documentation
- [x] 5.1 Update docs/deployment.md with production deployment steps
- [x] 5.2 Add Traefik configuration guide
- [x] 5.3 Document subdomain scheme and DNS requirements
- [x] 5.4 Update README.md with deployment section
## 6. Testing & Verification
- [x] 6.1 Test label generation for all built-in tools
- [x] 6.2 Verify Traefik routes correctly in local stack
- [x] 6.3 Run backend tests: `cd apps/api && pytest`
- [x] 6.4 Run linters: `ruff check app/` and `mypy app/`
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-14
@@ -1,75 +0,0 @@
## Context
The frontend is currently a static scaffold (FN-002) with no routing, auth, or API integration. The backend has complete domain models, CRUD routers, and auth dependencies (FN-004, FN-011). This design bridges the gap by establishing the frontend architecture needed for all user-facing features.
Current frontend state:
- Single App.tsx with static HTML
- No routing, state management, or API client
- No auth integration
- Tests only verify static rendering
## Goals / Non-Goals
**Goals:**
- Deliver a functional auth flow (login/logout via Authentik OIDC)
- Provide a responsive dashboard shell with navigation
- Enable project CRUD operations from the UI
- Establish typed API client patterns
- Set up environment-based configuration
**Non-Goals:**
- Full tool spawn UI (deferred to FN-010/FN-008)
- Config/secrets management UI (deferred to FN-009)
- Repository connection UI (deferred to future task)
- Real-time updates or WebSockets
- Mobile-optimized responsive design (basic responsiveness only)
## Decisions
**1. React Router v7 for routing**
- Rationale: Industry standard, integrates well with React 19, supports nested routes and loaders
- Alternative: TanStack Router - more type-safe but steeper learning curve, overkill for MVP
**2. TanStack Query (React Query) for server state**
- Rationale: Standard for API caching, background refetching, and optimistic updates
- Alternative: SWR - similar but TanStack Query has better TypeScript support and devtools
**3. Zustand for client state**
- Rationale: Lightweight, TypeScript-friendly, minimal boilerplate vs Redux
- Alternative: Context API - sufficient for auth but Zustand scales better for future features
**4. HTTP client: fetch API with thin wrapper**
- Rationale: No extra dependency needed, native fetch is sufficient
- Alternative: Axios - adds bundle size, fetch handles our use cases
**5. Auth: OIDC Authorization Code flow with PKCE**
- Rationale: Secure, recommended by OAuth 2.1, Authentik supports it
- Implementation: redirect to Authentik authorize endpoint, callback handles code exchange
**6. Component library: Headless UI + Tailwind CSS**
- Rationale: Unstyled primitives give full control, Tailwind is already in Vite scaffold
- Alternative: Material UI - opinionated, harder to customize
## Risks / Trade-offs
**[Risk] Auth token storage in browser**
→ Mitigation: Use httpOnly cookies (set by backend callback) or secure storage. Never localStorage. Implement CSRF protection.
**[Risk] OIDC library bundle size**
→ Mitigation: Use lightweight oauth4webapi or implement PKCE manually (~2KB vs 50KB+ for oidc-client-ts)
**[Risk] CORS complexity between web and API**
→ Mitigation: Configure CORS in FastAPI to allow web origin. Use same-origin deployment in production (Traefik routes both).
**[Risk] Test complexity with auth flows**
→ Mitigation: Mock auth context in tests, test components in isolation. E2E tests deferred post-MVP.
## Migration Plan
No migration needed - this is additive to the scaffold.
## Open Questions
1. Should we use a pre-built OIDC client library or implement PKCE manually?
2. Do we need refresh token rotation or are short-lived access tokens sufficient?
3. Should the API client auto-retry on 401 or redirect immediately?
@@ -1,32 +0,0 @@
## Why
The backend API is fully scaffolded with domain models, routers, and authentication dependencies, but the frontend remains a static scaffold page (FN-002). Users cannot sign in, view projects, or interact with any backend functionality. This change delivers the foundational frontend architecture needed to unlock all user-facing MVP features.
## What Changes
- **Authentik OIDC integration**: Auth provider with login/logout flow, token management, and automatic user provisioning
- **Dashboard shell**: Responsive layout with header, navigation sidebar, and main content area
- **Navigation routes**: Dashboard, Projects, Repositories, Tools, Settings pages with React Router
- **Typed API client**: Generated or hand-written client for all `/api/v1/*` endpoints
- **Project list UI**: Display user's projects with create/edit capabilities
- **Environment config layer**: Vite env var integration for API URL, auth endpoints
- **Auth-guarded routes**: Redirect unauthenticated users to login
## Capabilities
### New Capabilities
- `auth-oidc`: Authentik OIDC authentication flow, token storage, session management
- `dashboard-shell`: Responsive layout with navigation, header, and content area
- `project-management-ui`: Project list, create, edit, delete views
- `api-client`: Typed HTTP client for backend API consumption
- `route-guards`: Authentication-based route protection and redirects
### Modified Capabilities
- None (this is purely additive to the existing scaffold)
## Impact
- **apps/web/src/**: All new frontend code
- **apps/web/package.json**: New dependencies (react-router-dom, @tanstack/react-query, etc.)
- **apps/api/app/auth/dependencies.py**: CORS and auth flow alignment
- **docs/development.md**: Updated frontend development instructions
@@ -1,28 +0,0 @@
## ADDED Requirements
### Requirement: API client handles all backend endpoints
The system SHALL provide a typed HTTP client for all backend API endpoints.
#### Scenario: GET request
- **WHEN** the client calls api.get('/projects')
- **THEN** it sends a GET request to /api/v1/projects
- **AND** returns typed Project[] data
- **AND** includes the Authorization header with the current access token
#### Scenario: POST request
- **WHEN** the client calls api.post('/projects', data)
- **THEN** it sends a POST request with JSON body
- **AND** returns typed Project data
#### Scenario: Error handling
- **WHEN** a request returns 4xx or 5xx
- **THEN** the client throws an ApiError with status code and message
- **AND** the error can be caught and displayed to the user
### Requirement: API client supports request/response types
The system SHALL use TypeScript interfaces matching the backend Pydantic schemas.
#### Scenario: Type safety
- **WHEN** a developer uses the API client
- **THEN** request and response types are checked at compile time
- **AND** mismatches produce TypeScript errors
@@ -1,36 +0,0 @@
## ADDED Requirements
### Requirement: User can authenticate via Authentik OIDC
The system SHALL provide an authentication flow using Authentik as the OIDC provider.
#### Scenario: Successful login
- **WHEN** an unauthenticated user clicks "Sign In"
- **THEN** the system redirects to Authentik's authorization endpoint with PKCE parameters
- **AND** after successful authentication, Authentik redirects back with an authorization code
- **AND** the system exchanges the code for tokens
- **AND** the user is redirected to the dashboard
#### Scenario: Automatic user provisioning
- **WHEN** a user authenticates for the first time
- **THEN** the backend creates a User record automatically
- **AND** the user can access their projects immediately
#### Scenario: Logout
- **WHEN** an authenticated user clicks "Sign Out"
- **THEN** the system clears all session data
- **AND** redirects to Authentik's end_session_endpoint
- **AND** the user is redirected back to the login page
### Requirement: Auth state is managed globally
The system SHALL maintain authentication state accessible throughout the application.
#### Scenario: Auth context available
- **WHEN** the application loads
- **THEN** an auth context provider wraps the component tree
- **AND** child components can read the current auth state (loading, authenticated, unauthenticated, error)
#### Scenario: Token refresh
- **WHEN** an API request returns 401 due to expired token
- **THEN** the system attempts token refresh
- **AND** retries the original request with the new token
- **AND** if refresh fails, redirects to login
@@ -1,28 +0,0 @@
## ADDED Requirements
### Requirement: Dashboard provides responsive layout
The system SHALL provide a consistent layout with header, navigation, and content area.
#### Scenario: Layout structure
- **WHEN** the user views any authenticated page
- **THEN** a header displays the application name and user avatar
- **AND** a sidebar shows navigation links (Dashboard, Projects, Repositories, Tools, Settings)
- **AND** the main content area renders the current route's component
#### Scenario: Collapsible sidebar
- **WHEN** the user is on a mobile device
- **THEN** the sidebar is initially collapsed
- **AND** a hamburger menu toggles the sidebar visibility
### Requirement: Navigation reflects auth state
The system SHALL show/hide navigation items based on authentication status.
#### Scenario: Authenticated navigation
- **WHEN** the user is authenticated
- **THEN** all navigation links are visible
- **AND** "Sign Out" is available in the user menu
#### Scenario: Unauthenticated navigation
- **WHEN** the user is not authenticated
- **THEN** only "Sign In" is shown
- **AND** accessing protected routes redirects to login
@@ -1,39 +0,0 @@
## ADDED Requirements
### Requirement: User can view their projects
The system SHALL display a list of projects belonging to the authenticated user.
#### Scenario: Project list page
- **WHEN** the user navigates to /projects
- **THEN** the system fetches projects from /api/v1/projects
- **AND** displays each project with name, description, and created date
- **AND** shows an empty state when no projects exist
#### Scenario: Project detail
- **WHEN** the user clicks on a project
- **THEN** the system navigates to /projects/:id
- **AND** displays project details including repositories and tool instances
### Requirement: User can create a project
The system SHALL allow authenticated users to create new projects.
#### Scenario: Create project form
- **WHEN** the user clicks "New Project"
- **THEN** a form appears with name and description fields
- **AND** the name field validates for non-empty and URL-friendly slug generation
- **AND** submitting the form POSTs to /api/v1/projects
- **AND** on success, the user is redirected to the new project
### Requirement: User can edit and delete projects
The system SHALL allow project owners to modify or remove their projects.
#### Scenario: Edit project
- **WHEN** the user clicks "Edit" on a project
- **THEN** a pre-filled form appears
- **AND** submitting updates the project via PUT /api/v1/projects/:id
#### Scenario: Delete project
- **WHEN** the user clicks "Delete" on a project
- **THEN** a confirmation dialog appears
- **AND** confirming sends DELETE /api/v1/projects/:id
- **AND** the project is removed from the list
@@ -1,24 +0,0 @@
## ADDED Requirements
### Requirement: Protected routes require authentication
The system SHALL prevent unauthenticated users from accessing protected pages.
#### Scenario: Unauthenticated access attempt
- **WHEN** an unauthenticated user navigates to /projects
- **THEN** the system redirects to /login
- **AND** stores the intended destination for post-login redirect
#### Scenario: Authenticated access
- **WHEN** an authenticated user navigates to /projects
- **THEN** the route renders normally
### Requirement: Public routes are accessible
The system SHALL allow unauthenticated access to public pages.
#### Scenario: Login page
- **WHEN** an unauthenticated user navigates to /login
- **THEN** the login page renders without redirect
#### Scenario: Health/status pages
- **WHEN** any user navigates to /health
- **THEN** the page renders without authentication
@@ -1,72 +0,0 @@
## 1. Setup & Dependencies
- [x] 1.1 Install frontend dependencies: react-router-dom, @tanstack/react-query, zustand, @headlessui/react
- [x] 1.2 Set up Tailwind CSS configuration (tailwind.config.js, postcss.config.js)
- [x] 1.3 Create environment type definitions in apps/web/src/env.d.ts
- [x] 1.4 Add Vite environment variables to .env.example (VITE_API_URL, VITE_OIDC_ISSUER, etc.)
## 2. API Client & Types
- [x] 2.1 Create apps/web/src/api/client.ts with typed fetch wrapper and auth header injection
- [x] 2.2 Generate or create TypeScript interfaces matching backend schemas (Project, User, etc.)
- [x] 2.3 Implement error handling with ApiError class
- [x] 2.4 Add request/response logging in debug mode
## 3. Authentication
- [x] 3.1 Create apps/web/src/auth/oidc.ts with PKCE code generation and token exchange
- [x] 3.2 Implement auth store (Zustand) with state: loading, authenticated, unauthenticated, error
- [x] 3.3 Create AuthProvider component wrapping the app
- [x] 3.4 Implement login redirect to Authentik authorize endpoint
- [x] 3.5 Implement callback handler (/callback route) for code exchange
- [x] 3.6 Implement logout with end_session_endpoint redirect
- [x] 3.7 Add token refresh logic for expired access tokens
## 4. Routing & Layout
- [x] 4.1 Set up React Router with route definitions in apps/web/src/router.tsx
- [x] 4.2 Create DashboardLayout component with header, sidebar, and outlet
- [x] 4.3 Implement RouteGuard component for protected routes
- [x] 4.4 Add public routes: /login, /callback
- [x] 4.5 Add protected routes: /, /projects, /projects/:id, /tools, /settings
## 5. Dashboard Shell
- [x] 5.1 Create Header component with app name and user avatar dropdown
- [x] 5.2 Create Sidebar component with navigation links
- [x] 5.3 Implement mobile-responsive sidebar toggle
- [x] 5.4 Add active route highlighting in sidebar
- [x] 5.5 Create Dashboard home page with welcome content
## 6. Project Management UI
- [x] 6.1 Create ProjectList page fetching from /api/v1/projects
- [x] 6.2 Implement ProjectCard component for list view
- [x] 6.3 Add empty state when no projects exist
- [x] 6.4 Create ProjectDetail page at /projects/:id
- [x] 6.5 Implement NewProject form with validation (name, description)
- [x] 6.6 Implement EditProject form with pre-filled data
- [x] 6.7 Add delete confirmation dialog for projects
- [x] 6.8 Wire up TanStack Query mutations for create/update/delete
## 7. Placeholder Pages
- [x] 7.1 Create Tools page placeholder
- [x] 7.2 Create Settings page placeholder
- [x] 7.3 Create Repositories page placeholder
## 8. Testing & Verification
- [x] 8.1 Write unit tests for auth store
- [x] 8.2 Write unit tests for API client error handling
- [x] 8.3 Write tests for RouteGuard component
- [x] 8.4 Update App.test.tsx to test routing
- [x] 8.5 Run `pnpm test` and fix any failures
- [x] 8.6 Run `pnpm lint` and fix any issues
- [x] 8.7 Run `pnpm typecheck` and fix any errors
## 9. Documentation
- [x] 9.1 Update docs/development.md with frontend auth setup instructions
- [x] 9.2 Update README.md with new environment variables
- [x] 9.3 Add frontend architecture notes to docs/architecture.md
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-14
@@ -1,65 +0,0 @@
## Context
OpenCode is an AI-powered terminal-based development environment with a web interface. Unlike code-server which is a full web IDE, OpenCode provides a terminal experience accessible through the browser. This POC validates that the spawn system handles different runtime types including web terminal forwarding.
Current state:
- OpenCode manifest exists in apps/api/app/tools/manifests/opencode.yml
- No container image or runtime defined yet
- No health reporting mechanism
- Spawn infrastructure will be built in FN-010
## Goals / Non-Goals
**Goals:**
- Define OpenCode as a spawnable tool
- Provide web terminal interface in container
- Report health status (running/idle/error)
- Support interactive terminal sessions
**Non-Goals:**
- Full task queue or job scheduler
- Persistent process management
- Log streaming (deferred)
- Multi-language support beyond terminal
## Decisions
**1. Use official OpenCode Docker image**
- Rationale: Maintained, includes AI features and web terminal
- Alternative: Custom image - unnecessary for POC
**2. OpenCode runs as a persistent container**
- Rationale: Easier to manage lifecycle (start/stop/status). Terminal sessions need persistent container.
- Implementation: Container runs OpenCode with web interface on port 3000
**3. Health check via HTTP endpoint**
- Rationale: Standard Docker health check mechanism. Traefik can use it.
- Endpoint: `GET /` returns 200 when ready
**4. Workspace mounted from host (same as code-server)**
- Rationale: Consistency. Shared workspace between tools.
- Path: `/data/workspaces/{user_slug}/{project_slug}`
**5. Configs/secrets injected same as code-server**
- Rationale: Reuse FN-009 infrastructure. No special handling needed.
## Risks / Trade-offs
**[Risk] OpenCode container requires significant resources**
→ Mitigation: Set resource limits (4GB RAM, 2 CPU). Document requirements.
**[Risk] Web terminal performance over slow connections**
→ Mitigation: Use modern terminal emulation with compression. Document bandwidth requirements.
**[Risk] AI features require API keys**
→ Mitigation: Support secret injection for API keys. Document configuration.
## Migration Plan
No migration. New feature.
## Open Questions
1. Should OpenCode support multiple terminal sessions?
2. Do we pre-configure common development tools?
3. Should OpenCode integrate with the platform's AI provider?
@@ -1,28 +0,0 @@
## Why
OpenCode is an AI-powered terminal-based development environment that provides a web interface for interactive development. It demonstrates the platform's extensibility beyond standard tools like code-server. As a POC, it validates the manifest-driven spawn system with a non-trivial runtime that requires web terminal forwarding.
## What Changes
- **OpenCode manifest**: Define the tool with terminal web interface, workspace mounts, and health checks
- **Container image**: Reference to OpenCode image with built-in web terminal
- **Health reporting**: Endpoint that reports tool health to the platform
- **Spawn integration**: Reuse the spawn flow from FN-010 but with OpenCode-specific configuration
- **Web terminal**: Support for browser-based terminal access
## Capabilities
### New Capabilities
- `opencode-manifest`: OpenCode tool manifest with web terminal config
- `web-terminal`: Support for browser-based terminal interfaces
- `health-reporting`: Tool health status reporting mechanism
### Modified Capabilities
- None (reuses spawn infrastructure from FN-010)
## Impact
- **apps/api/app/tools/manifests/opencode.yml**: Updated manifest
- **apps/api/app/services/spawn.py**: Minor updates for OpenCode-specific mounts
- **apps/web/src/**: OpenCode appears in tool selection UI
- **Docker images**: Uses official OpenCode image
@@ -1,27 +0,0 @@
## ADDED Requirements
### Requirement: Container provides web terminal interface
The system SHALL provide a web terminal interface in the OpenCode container.
#### Scenario: Terminal available
- **WHEN** the OpenCode container is running
- **THEN** a web terminal is accessible via HTTP on port 3000
- **AND** the user can execute shell commands through the browser
#### Scenario: Workspace access
- **WHEN** the container runs
- **THEN** the project workspace is mounted at /workspace
- **AND** the user can read/write files in the workspace
### Requirement: Container supports AI features
The system SHALL allow AI-powered development features in the OpenCode environment.
#### Scenario: AI assistance
- **WHEN** the user interacts with OpenCode
- **THEN** AI features are available for code completion and assistance
- **AND** the user can configure AI provider settings
#### Scenario: Terminal session persistence
- **WHEN** the user opens a terminal session
- **THEN** the session persists while the container runs
- **AND** multiple sessions can be opened
@@ -1,22 +0,0 @@
## ADDED Requirements
### Requirement: Tool reports health status
The system SHALL provide a mechanism for OpenCode to report its health.
#### Scenario: Health endpoint
- **WHEN** the OpenCode container is running
- **THEN** it exposes a / endpoint for health checks
- **AND** returns 200 when the web terminal is ready
#### Scenario: Health check in Traefik
- **WHEN** the container is spawned
- **THEN** Traefik uses the health endpoint for routing decisions
- **AND** unhealthy containers are removed from the load balancer
### Requirement: Platform tracks tool health
The system SHALL track and display the health of OpenCode instances.
#### Scenario: Status display
- **WHEN** the user views an OpenCode instance
- **THEN** the current status is displayed (healthy, unhealthy, starting)
- **AND** the status updates automatically
@@ -1,15 +0,0 @@
## ADDED Requirements
### Requirement: OpenCode manifest defines web terminal environment
The system SHALL provide an OpenCode manifest with web terminal configuration.
#### Scenario: Manifest includes terminal config
- **WHEN** the OpenCode manifest is loaded
- **THEN** it specifies an OpenCode Docker image with web interface
- **AND** it defines exposed ports for the HTTP interface (port 3000)
- **AND** it defines volume mounts (workspace, config)
#### Scenario: Manifest includes health check
- **WHEN** the manifest is used for spawning
- **THEN** it defines a health check endpoint
- **AND** specifies health check interval and timeout
@@ -1,42 +0,0 @@
## 1. Manifest Definition
- [x] 1.1 Create apps/api/app/tools/manifests/opencode.yml with web terminal config
- [x] 1.2 Add Docker image (ghcr.io/opencode-ai/opencode:latest), ports (3000), volumes
- [x] 1.3 Add health check configuration to manifest
- [x] 1.4 Validate manifest against ToolManifest schema
## 2. Container Setup
- [x] 2.1 Verify OpenCode image availability and configuration
- [x] 2.2 Document web terminal access pattern
- [x] 2.3 Configure environment variables for terminal support
- [x] 2.4 Test container locally with docker run
- [x] 2.5 Verify web terminal accessibility
## 3. Spawn Integration
- [x] 3.1 Verify SpawnService (FN-010) can spawn OpenCode instances
- [x] 3.2 Add OpenCode-specific volume mounts (config)
- [x] 3.3 Test spawn via API endpoint
- [x] 3.4 Verify Traefik routing to OpenCode container
## 4. Frontend Integration
- [x] 4.1 Add OpenCode to tool selection dropdown
- [x] 4.2 Display OpenCode-specific options in spawn form
- [x] 4.3 Show OpenCode instance status in detail page
## 5. Testing & Verification
- [x] 5.1 Test terminal availability in spawned container
- [x] 5.2 Test web interface accessibility
- [x] 5.3 Test health endpoint response
- [x] 5.4 Verify workspace mount is accessible
- [x] 5.5 Run full test suite: `make test`
- [x] 5.6 Run linters: `make lint`
## 6. Documentation
- [x] 6.1 Document OpenCode setup in docs/development.md
- [x] 6.2 Add OpenCode usage guide
- [x] 6.3 Document terminal configuration and AI features
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-16
@@ -1,2 +0,0 @@
schema: spec-driven
created: 2026-05-16
@@ -1,79 +0,0 @@
## Context
The platform has a Git abstraction layer in `apps/api/app/git/` with:
- `provider.py`: GitProvider ABC (validate_connection, list_repos, create_deploy_key, delete_deploy_key, get_default_branch)
- `connection.py`: ConnectionManager (connect/disconnect/get_connection)
- `credentials.py`: GitCredential, AccessTokenCredential, CredentialStorage ABC
- `ssh_key.py`: SshKeyPair, SshKeyLifecycle with Ed25519 generation
- `operations.py`: GitOperations ABC, LocalGitOperations (clone/fetch/push are NotImplementedError)
- `types.py`: ProviderKind, CredentialKind, ConnectionStatus, SshKeyStatus enums
Current gaps:
- No concrete provider adapters (GitHub, GitLab)
- CredentialStorage has no database implementation
- LocalGitOperations is incomplete
- No RepositoryConnection router or API endpoints
- SSH key generation uses base64 placeholder
## Goals / Non-Goals
**Goals:**
- Implement concrete GitHub and GitLab provider adapters
- Create database-backed credential storage with encryption
- Complete LocalGitOperations with credential-aware subprocess
- Add RepositoryConnection router with CRUD endpoints
- Generate Ed25519 SSH keys and register deploy keys
- Frontend UI for repository connections and SSH key management
**Non-Goals:**
- Support for Gitea/Forgejo (deferred post-MVP)
- GitHub/GitLab OAuth app integration (use personal access tokens)
- Webhook management
- Repository mirroring
- Branch protection management
## Decisions
**1. Use httpx for provider API calls**
- Rationale: Already in dependencies, async support, consistent with FastAPI
- Alternative: requests - blocking, would need thread pool
**2. Store credentials encrypted with Fernet (same as secrets)**
- Rationale: Consistent with existing secret storage in FN-009
- Implementation: Reuse encryption service from app/encryption.py
**3. SSH keys generated per-repository (not per-user)**
- Rationale: Fine-grained access control, easy revocation per repo
- Alternative: Per-user keys - broader blast radius on compromise
**4. Provider adapters implement GitProvider ABC**
- Rationale: Clean abstraction, easy to add new providers
- Implementation: GitHubAdapter, GitLabAdapter with unified interface
**5. Git operations use subprocess with SSH key in temp file**
- Rationale: Standard git CLI is most reliable
- Implementation: Write key to temp file, set GIT_SSH_COMMAND env var
## Risks / Trade-offs
**[Risk] Personal access tokens have broad permissions**
→ Mitigation: Document minimal required scopes (repo read/write, deploy key management)
**[Risk] SSH keys in temp files are briefly exposed on disk**
→ Mitigation: Use 0600 permissions, clean up immediately after operation
**[Risk] Provider API rate limits**
→ Mitigation: Cache repository lists, implement exponential backoff
**[Risk] Token storage compromise**
→ Mitigation: Fernet encryption with rotation support
## Migration Plan
No migration. New feature.
## Open Questions
1. Should we support SSH key passphrases?
2. Do we need to validate repository URLs before connection?
3. Should we auto-detect provider from URL?
@@ -1,39 +0,0 @@
## Why
The platform needs a provider-independent Git connection model so users can connect repositories from GitHub, GitLab, Gitea, or Forgejo without vendor lock-in. Currently, the Git abstraction layer exists but lacks concrete provider adapters, credential storage, and API endpoints for managing connections. This is the foundation for repository cloning, branch management, and automated deploy key registration.
## What Changes
- **Provider adapter framework**: Concrete implementations for GitHub and GitLab APIs with unified interface
- **Credential storage backend**: Database-backed storage for access tokens and SSH keys with encryption
- **Repository connection API**: REST endpoints for creating, listing, and deleting repository connections
- **SSH key lifecycle**: Ed25519 key generation, public key retrieval, and deploy key registration
- **Git operations**: Complete LocalGitOperations with credential-aware clone, fetch, and push
- **Frontend repository UI**: Interface for connecting repositories and managing SSH keys
## Capabilities
### New Capabilities
- `git-provider-adapter`: Unified interface for Git provider APIs (GitHub, GitLab)
- `credential-storage`: Encrypted storage for access tokens and SSH keys
- `repository-connection`: API for managing repository connections
- `ssh-key-lifecycle`: SSH key generation and deploy key management
- `git-operations`: Credential-aware Git operations (clone, fetch, push)
### Modified Capabilities
- None (extends existing Git abstraction)
## Impact
- **apps/api/app/git/**: New provider adapters and completed operations
- **apps/api/app/routers/repository_connections.py**: New router
- **apps/api/app/models/repository_connection.py**: Enhanced model
- **apps/api/app/schemas/repository_connection.py**: New schemas
- **apps/web/src/pages/RepositoriesPage.tsx**: Enhanced UI
- **apps/web/src/pages/RepositoryConnectionPage.tsx**: New page
## Dependencies
- FN-003: Tool Registry (manifest system)
- FN-004: Backend Foundation (models, auth)
- FN-009: Config & Secrets (encryption, credential storage)
@@ -1,30 +0,0 @@
## ADDED Requirements
### Requirement: Credentials are stored encrypted
The system SHALL store access tokens and SSH keys encrypted at rest.
#### Scenario: Store access token
- **WHEN** a user saves an access token
- **THEN** the system encrypts it with Fernet
- **AND** stores the encrypted value in the database
#### Scenario: Retrieve access token
- **WHEN** the system retrieves a credential for API calls
- **THEN** it decrypts the value
- **AND** returns the plaintext token
#### Scenario: List credentials without exposing values
- **WHEN** a user lists their credentials
- **THEN** the system returns metadata (name, provider, created_at)
- **AND** masks the token value (showing only last 4 characters)
### Requirement: Credential storage supports multiple providers
The system SHALL support storing credentials for different Git providers.
#### Scenario: Store GitHub token
- **WHEN** a user adds a GitHub personal access token
- **THEN** the system stores it with provider_type="github"
#### Scenario: Store GitLab token
- **WHEN** a user adds a GitLab personal access token
- **THEN** the system stores it with provider_type="gitlab"
@@ -1,29 +0,0 @@
## ADDED Requirements
### Requirement: Git clone uses SSH credentials
The system SHALL clone repositories using SSH keys.
#### Scenario: Clone repository
- **WHEN** the system clones a repository
- **THEN** it writes the SSH private key to a temporary file
- **AND** sets GIT_SSH_COMMAND to use the key
- **AND** executes git clone
- **AND** cleans up the temporary key file
#### Scenario: Clone fails with invalid key
- **WHEN** a clone operation fails due to authentication
- **THEN** the system returns a clear error message
- **AND** suggests checking deploy key permissions
### Requirement: Git fetch and push use credentials
The system SHALL support fetch and push operations with SSH credentials.
#### Scenario: Fetch updates
- **WHEN** the system fetches from a remote
- **THEN** it uses the stored SSH key for authentication
- **AND** returns the fetch result
#### Scenario: Push changes
- **WHEN** the system pushes to a remote
- **THEN** it uses the stored SSH key for authentication
- **AND** returns the push result
@@ -1,35 +0,0 @@
## ADDED Requirements
### Requirement: GitHub adapter implements provider interface
The system SHALL provide a GitHub adapter that implements the GitProvider interface.
#### Scenario: List repositories
- **WHEN** the adapter lists repositories for an authenticated user
- **THEN** it returns a list of repository objects with name, url, and default_branch
#### Scenario: Create deploy key
- **WHEN** the adapter creates a deploy key for a repository
- **THEN** it registers the SSH public key with GitHub
- **AND** returns the key ID
#### Scenario: Validate connection
- **WHEN** the adapter validates a token
- **THEN** it verifies the token with GitHub API
- **AND** returns user information
### Requirement: GitLab adapter implements provider interface
The system SHALL provide a GitLab adapter that implements the GitProvider interface.
#### Scenario: List repositories
- **WHEN** the adapter lists repositories for an authenticated user
- **THEN** it returns a list of repository objects with name, url, and default_branch
#### Scenario: Create deploy key
- **WHEN** the adapter creates a deploy key for a repository
- **THEN** it registers the SSH public key with GitLab
- **AND** returns the key ID
#### Scenario: Validate connection
- **WHEN** the adapter validates a token
- **THEN** it verifies the token with GitLab API
- **AND** returns user information
@@ -1,42 +0,0 @@
## ADDED Requirements
### Requirement: Repository connections can be created
The system SHALL allow users to create connections to Git repositories.
#### Scenario: Connect GitHub repository
- **WHEN** a user provides a GitHub repository URL and access token
- **THEN** the system validates the URL and token
- **AND** creates a RepositoryConnection record
- **AND** generates an SSH key pair
- **AND** registers the deploy key with GitHub
#### Scenario: Connect GitLab repository
- **WHEN** a user provides a GitLab repository URL and access token
- **THEN** the system validates the URL and token
- **AND** creates a RepositoryConnection record
- **AND** generates an SSH key pair
- **AND** registers the deploy key with GitLab
#### Scenario: Reject invalid URL
- **WHEN** a user provides an invalid repository URL
- **THEN** the system returns a 400 error with validation message
### Requirement: Repository connections can be listed and retrieved
The system SHALL allow users to list and view their repository connections.
#### Scenario: List connections
- **WHEN** a user requests their repository connections
- **THEN** the system returns a list with status and metadata
#### Scenario: Get connection details
- **WHEN** a user requests a specific connection
- **THEN** the system returns full details including SSH public key
### Requirement: Repository connections can be deleted
The system SHALL allow users to delete repository connections.
#### Scenario: Delete connection
- **WHEN** a user deletes a connection
- **THEN** the system removes the deploy key from the provider
- **AND** deletes the SSH key pair
- **AND** marks the connection as deleted
@@ -1,28 +0,0 @@
## ADDED Requirements
### Requirement: SSH keys are generated per repository
The system SHALL generate Ed25519 SSH key pairs for each repository connection.
#### Scenario: Generate key pair
- **WHEN** a repository connection is created
- **THEN** the system generates an Ed25519 key pair
- **AND** stores the private key encrypted
- **AND** returns the public key for deploy key registration
#### Scenario: Retrieve public key
- **WHEN** a user requests the public key for a connection
- **THEN** the system returns the SSH public key string
### Requirement: SSH keys support lifecycle operations
The system SHALL support rotating and revoking SSH keys.
#### Scenario: Rotate key
- **WHEN** a user rotates an SSH key
- **THEN** the system generates a new key pair
- **AND** updates the deploy key on the provider
- **AND** deletes the old key pair
#### Scenario: Revoke key
- **WHEN** a connection is deleted
- **THEN** the system deletes the deploy key from the provider
- **AND** securely deletes the local key pair
@@ -1,63 +0,0 @@
## 1. Provider Adapters
- [ ] 1.1 Implement GitHubAdapter in apps/api/app/git/providers/github.py
- [ ] 1.2 Implement GitLabAdapter in apps/api/app/git/providers/gitlab.py
- [ ] 1.3 Add provider factory in apps/api/app/git/providers/__init__.py
- [ ] 1.4 Write tests for GitHubAdapter (mock API responses)
- [ ] 1.5 Write tests for GitLabAdapter (mock API responses)
## 2. Credential Storage
- [ ] 2.1 Create CredentialStorage implementation in apps/api/app/git/credentials.py
- [ ] 2.2 Add database model for GitCredential if needed
- [ ] 2.3 Integrate Fernet encryption from app/encryption.py
- [ ] 2.4 Add credential router in apps/api/app/routers/credentials.py
- [ ] 2.5 Write tests for credential storage
## 3. SSH Key Lifecycle
- [ ] 3.1 Complete SshKeyLifecycle.generate_key_pair() with real Ed25519
- [ ] 3.2 Add SSH key endpoints in apps/api/app/routers/ssh_keys.py
- [ ] 3.3 Implement key rotation logic
- [ ] 3.4 Write tests for SSH key generation
## 4. Repository Connection API
- [ ] 4.1 Create RepositoryConnection router in apps/api/app/routers/repository_connections.py
- [ ] 4.2 Implement POST /api/v1/repository-connections endpoint
- [ ] 4.3 Implement GET /api/v1/repository-connections endpoint
- [ ] 4.4 Implement GET /api/v1/repository-connections/:id endpoint
- [ ] 4.5 Implement DELETE /api/v1/repository-connections/:id endpoint
- [ ] 4.6 Add validation for repository URLs and tokens
- [ ] 4.7 Write tests for repository connection endpoints
## 5. Git Operations
- [ ] 5.1 Complete LocalGitOperations.clone() with SSH key
- [ ] 5.2 Complete LocalGitOperations.fetch() with SSH key
- [ ] 5.3 Complete LocalGitOperations.push() with SSH key
- [ ] 5.4 Add error handling for auth failures
- [ ] 5.5 Write tests for git operations
## 6. Frontend UI
- [ ] 6.1 Create RepositoryConnectionListPage.tsx
- [ ] 6.2 Create RepositoryConnectionFormPage.tsx
- [ ] 6.3 Add repository connection routes to router.tsx
- [ ] 6.4 Add API client methods for repository connections
- [ ] 6.5 Add types for repository connections
## 7. Documentation
- [ ] 7.1 Update docs/architecture.md with Git connection model
- [ ] 7.2 Update docs/development.md with setup instructions
- [ ] 7.3 Add provider setup guide (GitHub/GitLab tokens)
## 8. Testing & Verification
- [ ] 8.1 Run all backend tests (target: 90+)
- [ ] 8.2 Run ruff linter
- [ ] 8.3 Run mypy type checker
- [ ] 8.4 Run frontend tests
- [ ] 8.5 Verify API endpoints with manual testing
- [ ] 8.6 Update project specsheet