rdev/internal/domain/project.go
jordan bc47e426b0 feat: Add CI pipeline proxy, DNS alias management, and worker executor system
- Add ListPipelines/GetPipeline to CIProvider port with Woodpecker adapter
- Add DNS alias endpoints: GET/POST/DELETE /projects/{id}/domains
- Implement worker executor daemon, build executor, and git operations
- Add build service, worker service, and build audit tracking
- Add worker registry with PostgreSQL adapter and migration
- Add multi-provider code agent interface (Claude Code + OpenCode)
- Add create-and-build combo endpoint
- Update landing-page cookbook to reflect all gaps closed
- Fix tech debt: unified validation, auth scopes, error wrapping, slog patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:05:28 -07:00

119 lines
3.8 KiB
Go

// Package domain contains pure domain models with no external dependencies.
// These types represent the core business concepts of the application.
package domain
import "regexp"
// ProjectID is a strongly-typed identifier for projects.
type ProjectID string
// Project name/ID constraints.
const (
// MaxProjectNameLen is the maximum length for project names (K8s name limit).
MaxProjectNameLen = 63
)
// projectIDRegex validates project IDs used for referencing existing projects.
// Allows uppercase, lowercase, digits, dashes, and underscores. Must start with a letter.
var projectIDRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`)
// projectNameRegex validates project names for DNS/K8s resource creation.
// Lowercase only, digits, dashes. Must start with a lowercase letter.
var projectNameRegex = regexp.MustCompile(`^[a-z][a-z0-9-]*$`)
// reservedProjectNames are names that cannot be used for new projects.
var reservedProjectNames = map[string]bool{
"www": true, "api": true, "git": true, "ci": true,
"registry": true, "admin": true, "root": true,
"rdev": true, "pantheon": true,
}
// ValidateProjectID validates a project ID for referencing existing projects.
// Allows letters, digits, dashes, underscores. Must start with a letter. Max 63 chars.
func ValidateProjectID(id string) error {
if id == "" {
return ErrInvalidProjectName
}
if len(id) > MaxProjectNameLen {
return ErrInvalidProjectName
}
if !projectIDRegex.MatchString(id) {
return ErrInvalidProjectName
}
return nil
}
// ValidateProjectName validates a project name for DNS/K8s resource creation.
// Lowercase letters, digits, dashes only. Must start with a lowercase letter.
// Max 63 chars. Rejects reserved names.
func ValidateProjectName(name string) error {
if name == "" {
return ErrInvalidProjectName
}
if len(name) > MaxProjectNameLen {
return ErrInvalidProjectName
}
if !projectNameRegex.MatchString(name) {
return ErrInvalidProjectName
}
if reservedProjectNames[name] {
return ErrInvalidProjectName
}
return nil
}
// Project represents a claudebox project that can execute commands.
type Project struct {
ID ProjectID
Name string
Description string
PodName string
Status ProjectStatus
Workspace string
// AgentProvider specifies which code agent to use for this project.
// Empty string means use the system default (typically Claude Code).
AgentProvider AgentProvider
}
// ProjectStatus represents the current state of a project's pod.
type ProjectStatus string
const (
ProjectStatusRunning ProjectStatus = "running"
ProjectStatusPending ProjectStatus = "pending"
ProjectStatusFailed ProjectStatus = "failed"
ProjectStatusNotFound ProjectStatus = "not_found"
ProjectStatusUnknown ProjectStatus = "unknown"
ProjectStatusError ProjectStatus = "error"
)
// IsAvailable returns true if the project can accept commands.
func (s ProjectStatus) IsAvailable() bool {
return s == ProjectStatusRunning
}
// IsTerminal returns true if the status is a final state.
func (s ProjectStatus) IsTerminal() bool {
return s == ProjectStatusFailed || s == ProjectStatusNotFound
}
// K8s label and annotation constants for project discovery.
// Pods with these labels are discovered as rdev projects.
const (
// LabelProject marks a pod as an rdev project when set to "true".
LabelProject = "rdev.orchard9.ai/project"
// LabelName specifies the project name (used as project ID).
LabelName = "rdev.orchard9.ai/name"
// LabelWorkspace specifies the workspace path inside the pod.
LabelWorkspace = "rdev.orchard9.ai/workspace"
// LabelAgentProvider specifies which code agent to use ("claudecode", "opencode").
LabelAgentProvider = "rdev.orchard9.ai/agent-provider"
// AnnotDescription provides a human-readable description of the project.
AnnotDescription = "rdev.orchard9.ai/description"
)