// 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" )