Files
run/protocol/autonomous_lifecycle.go
T
2026-08-26 09:56:43 +08:00

255 lines
10 KiB
Go

package protocol
import "strings"
type RunAutonomousLifecyclePlan struct {
SchemaVersion string `json:"schemaVersion"`
ServerInstanceID string `json:"serverInstanceId"`
PluginID string `json:"pluginId"`
PluginVersion string `json:"pluginVersion"`
RunEndpointID string `json:"runEndpointId"`
ProfileKey string `json:"profileKey,omitempty"`
TargetOS string `json:"targetOs"`
TargetArch string `json:"targetArch"`
TargetRelease string `json:"targetRelease"`
DeploymentRevision int `json:"deploymentRevision,omitempty"`
Bootstrap *RunAutonomousLifecycleAction `json:"bootstrap,omitempty"`
Actions []RunAutonomousLifecycleAction `json:"actions,omitempty"`
DependencyProbes []DependencyProbe `json:"dependencyProbes,omitempty"`
InstallPlans []DependencyInstallPlan `json:"installPlans,omitempty"`
LogSources []RuntimeLogSourcePlan `json:"logSources,omitempty"`
DLLExtensions []RuntimeDLLExtensionPlan `json:"dllExtensions,omitempty"`
DataTargets []RunAutonomousDataTarget `json:"dataTargets,omitempty"`
RuntimeBindings map[string]string `json:"runtimeBindings,omitempty"`
Deployment *RunAutonomousDeployment `json:"deployment,omitempty"`
}
type RunAutonomousLifecycleAction struct {
Action string `json:"action"`
Operation string `json:"operation"`
Capability string `json:"capability"`
TargetKey string `json:"targetKey"`
}
type RunAutonomousDeployment struct {
SchemaVersion string `json:"schemaVersion"`
Mode string `json:"mode"`
ProfileKey string `json:"profileKey,omitempty"`
RuntimeBindings map[string]string `json:"runtimeBindings,omitempty"`
CreateInputs map[string]string `json:"createInputs,omitempty"`
ServerRoot string `json:"serverRoot,omitempty"`
WorkingDirectory string `json:"workingDirectory,omitempty"`
InstallCommand string `json:"installCommand,omitempty"`
StartCommand string `json:"startCommand,omitempty"`
StopCommand string `json:"stopCommand,omitempty"`
StatusCommand string `json:"statusCommand,omitempty"`
Shell string `json:"shell,omitempty"`
Revision int `json:"revision,omitempty"`
}
type RunAutonomousDataTarget struct {
Key string `json:"key"`
Kind string `json:"kind"`
TransportKey string `json:"transportKey"`
SourceRootKey string `json:"sourceRootKey"`
SourcePath string `json:"sourcePath"`
WorkspaceKey string `json:"workspaceKey"`
RefreshPolicy string `json:"refreshPolicy"`
MaxBytes int64 `json:"maxBytes,omitempty"`
Platforms []string `json:"platforms,omitempty"`
}
const maxAutonomousDataTargetBytes = int64(1024 * 1024 * 1024)
func ValidateRunAutonomousLifecyclePlan(plan RunAutonomousLifecyclePlan) error {
if plan.SchemaVersion != "1" {
return ValidationError("autonomous lifecycle plan schema is unsupported")
}
if !ValidLogicalFileKey(plan.ServerInstanceID) || !ValidLogicalFileKey(plan.RunEndpointID) || !validAutonomousPluginID(plan.PluginID) {
return ValidationError("autonomous lifecycle plan identity is invalid")
}
if plan.ProfileKey != "" && !ValidLogicalFileKey(plan.ProfileKey) {
return ValidationError("autonomous lifecycle profile key is invalid")
}
if !validAutonomousTarget(plan.TargetOS, plan.TargetArch) {
return ValidationError("autonomous lifecycle target platform is invalid")
}
if plan.TargetRelease != "" && !validAutonomousToken(plan.TargetRelease, 240) {
return ValidationError("autonomous lifecycle target release is invalid")
}
if plan.PluginVersion != "" && !validAutonomousToken(plan.PluginVersion, 120) {
return ValidationError("autonomous lifecycle plugin version is invalid")
}
if plan.Bootstrap != nil {
if err := validateAutonomousLifecycleAction(*plan.Bootstrap); err != nil {
return err
}
if plan.Bootstrap.Capability != RunCapabilityProcessInstall && plan.Bootstrap.Capability != RunCapabilityProcessStart {
return ValidationError("autonomous lifecycle bootstrap action is invalid")
}
}
if len(plan.Actions) > 16 || len(plan.DependencyProbes) > 64 || len(plan.InstallPlans) > 64 || len(plan.LogSources) > 16 || len(plan.DLLExtensions) > 16 || len(plan.DataTargets) > 16 {
return ValidationError("autonomous lifecycle plan is too large")
}
for _, action := range plan.Actions {
if err := validateAutonomousLifecycleAction(action); err != nil {
return err
}
}
for _, probe := range plan.DependencyProbes {
if !ValidLogicalFileKey(probe.Key) || !ValidLogicalFileKey(probe.TargetKey) || !validAutonomousToken(probe.Kind, 80) || probe.MinimumVersion != "" && !validAutonomousToken(probe.MinimumVersion, 80) {
return ValidationError("autonomous dependency probe is invalid")
}
}
for _, installPlan := range plan.InstallPlans {
if !ValidLogicalFileKey(installPlan.Key) || len(installPlan.Steps) > 64 {
return ValidationError("autonomous install plan is invalid")
}
for _, step := range installPlan.Steps {
if !ValidLogicalFileKey(step.TargetKey) || !validAutonomousToken(step.Type, 80) || step.PackageManager != "" && !validAutonomousToken(step.PackageManager, 80) || step.PackageName != "" && !validAutonomousToken(step.PackageName, 160) || step.Version != "" && !validAutonomousToken(step.Version, 120) {
return ValidationError("autonomous install step is invalid")
}
}
}
for _, source := range plan.LogSources {
if err := validateRuntimeProcessLogSourcePlan(source); err != nil {
return err
}
}
seenDataTargets := map[string]struct{}{}
seenWorkspaceTargets := map[string]struct{}{}
for _, target := range plan.DataTargets {
if err := validateAutonomousDataTarget(target); err != nil {
return err
}
if _, exists := seenDataTargets[target.Key]; exists {
return ValidationError("autonomous data target key is duplicated")
}
if _, exists := seenWorkspaceTargets[target.WorkspaceKey]; exists {
return ValidationError("autonomous data target workspace is duplicated")
}
seenDataTargets[target.Key] = struct{}{}
seenWorkspaceTargets[target.WorkspaceKey] = struct{}{}
}
if plan.Deployment != nil {
if err := validateAutonomousDeployment(*plan.Deployment); err != nil {
return err
}
}
return nil
}
func validateAutonomousDataTarget(target RunAutonomousDataTarget) error {
if !ValidLogicalFileKey(target.Key) || !ValidLogicalFileKey(target.TransportKey) || !ValidLogicalFileKey(target.SourceRootKey) || !ValidLogicalFileKey(target.SourcePath) || !ValidLogicalFileKey(target.WorkspaceKey) {
return ValidationError("autonomous data target is invalid")
}
if target.Kind != "sqlite.snapshot" || target.RefreshPolicy != "on-demand-snapshot" || !strings.HasPrefix(target.WorkspaceKey, "databases/") {
return ValidationError("autonomous data target is invalid")
}
if target.MaxBytes <= 0 || target.MaxBytes > maxAutonomousDataTargetBytes {
return ValidationError("autonomous data target byte limit is invalid")
}
for _, platform := range target.Platforms {
if platform != "windows" && platform != "linux" && platform != "darwin" {
return ValidationError("autonomous data target platform is invalid")
}
}
return nil
}
func validateAutonomousLifecycleAction(action RunAutonomousLifecycleAction) error {
if !ValidLogicalFileKey(action.TargetKey) || !validAutonomousLifecycleCapability(action.Capability) || !validAutonomousLifecycleName(action.Action) || !validAutonomousLifecycleName(action.Operation) {
return ValidationError("autonomous lifecycle action is invalid")
}
if expected := autonomousCapabilityForAction(action.Action); expected != "" && expected != action.Capability {
return ValidationError("autonomous lifecycle action capability mismatch")
}
return nil
}
func autonomousCapabilityForAction(action string) string {
switch action {
case "create":
return RunCapabilityProcessInstall
case "start":
return RunCapabilityProcessStart
case "stop":
return RunCapabilityProcessStop
case "status":
return RunCapabilityProcessStatus
default:
return ""
}
}
func validAutonomousLifecycleCapability(capability string) bool {
switch capability {
case RunCapabilityProcessInstall, RunCapabilityProcessStart, RunCapabilityProcessStop, RunCapabilityProcessStatus:
return true
default:
return false
}
}
func validAutonomousLifecycleName(value string) bool {
switch value {
case "create", "install", "start", "stop", "status":
return true
default:
return false
}
}
func validateAutonomousDeployment(deployment RunAutonomousDeployment) error {
if deployment.SchemaVersion != "1" || deployment.Mode == "" || deployment.Revision < 0 || deployment.ProfileKey != "" && !ValidLogicalFileKey(deployment.ProfileKey) {
return ValidationError("autonomous deployment is invalid")
}
for key := range deployment.RuntimeBindings {
if !ValidLogicalFileKey(key) {
return ValidationError("autonomous deployment binding is invalid")
}
}
for key := range deployment.CreateInputs {
if !ValidLogicalFileKey(key) {
return ValidationError("autonomous deployment input is invalid")
}
}
return nil
}
func validAutonomousPluginID(value string) bool {
return ValidLogicalFileKey(value)
}
func validAutonomousTarget(osName string, arch string) bool {
switch osName {
case "windows", "linux", "darwin":
default:
return false
}
switch arch {
case "amd64", "arm64":
return true
default:
return false
}
}
func validAutonomousToken(value string, maxRunes int) bool {
trimmed := strings.TrimSpace(value)
if trimmed == "" || trimmed != value || len([]rune(value)) > maxRunes {
return false
}
lower := strings.ToLower(value)
if strings.Contains(value, "..") || strings.Contains(value, `\`) || strings.Contains(value, "://") || strings.Contains(lower, "/users/") || strings.Contains(lower, "password=") || strings.Contains(lower, "secret=") || strings.Contains(lower, "bearer ") || strings.Contains(lower, "sk-") {
return false
}
for _, char := range value {
if char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char >= '0' && char <= '9' || char == '_' || char == '-' || char == '.' || char == '/' || char == ':' {
continue
}
return false
}
return true
}