package daemon

import (
	"errors"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	daemonTransportCapabilityV1             = "daemon-transport:v1"
	daemonTransportElasticRequestCapability = "daemon-transport:elastic-request-link:v1"
	daemonEndpointsCapabilityV1             = "daemon-endpoints:v1"
	defaultEndpointRegistryVersion          = "daemon-endpoints:v1"
)

type RemoteEndpointLinkPolicy string

const (
	RemoteEndpointLinkPolicySessionOnly      RemoteEndpointLinkPolicy = "session_only"
	RemoteEndpointLinkPolicySessionPreferred RemoteEndpointLinkPolicy = "session_preferred"
	RemoteEndpointLinkPolicyElasticPreferred RemoteEndpointLinkPolicy = "elastic_preferred"
	RemoteEndpointLinkPolicyElasticRequired  RemoteEndpointLinkPolicy = "elastic_required"
)

type RemoteEndpointLimits struct {
	RequestBytes  int64 `json:"requestBytes"`
	ResponseBytes int64 `json:"responseBytes"`
}

type RemoteEndpointWarmSafety string

const (
	RemoteEndpointWarmSafetyReadOnly      RemoteEndpointWarmSafety = "read_only"
	RemoteEndpointWarmSafetyIdempotent    RemoteEndpointWarmSafety = "idempotent"
	RemoteEndpointWarmSafetyDemandOnly    RemoteEndpointWarmSafety = "demand_only"
	RemoteEndpointWarmSafetyNonIdempotent RemoteEndpointWarmSafety = "non_idempotent"
)

const defaultWarmEndpointProfileName = "user_interactive_read_review_v1"

type RemoteEndpointWarmMetadata struct {
	Eligible  bool
	Profile   string
	Safety    RemoteEndpointWarmSafety
	Rationale string
}

// WarmEndpointProfile is generated from registry descriptors instead of a
// controller-owned handwritten list. Keeping the allowlist next to the endpoint
// contract lets reviewers see why each endpoint is safe and keeps destructive
// handlers demand-only unless the spec and tests intentionally change that
// boundary.
type WarmEndpointProfile struct {
	Name            string
	EndpointScopes  []RemoteEndpointScope
	SafetyClasses   []RemoteEndpointWarmSafety
	MutatingAllowed bool
}

type RemoteEndpointDescriptor struct {
	EndpointID      string
	Version         int
	Owner           string
	CapabilityGate  string
	RegistryVersion string
	LinkPolicy      RemoteEndpointLinkPolicy
	FallbackCommand string
	Timeout         time.Duration
	DialTimeout     time.Duration
	Limits          RemoteEndpointLimits
	MetricsName     string
	WarmMetadata    RemoteEndpointWarmMetadata

	// SafeRetryFallback is true only when it is safe to retry the same logical
	// request through the legacy Session Link command after Elastic Request Link
	// setup or assignment fails. Destructive or non-idempotent endpoints keep this
	// false so accepted/in-flight failures surface as stable errors instead of
	// silently running twice.
	SafeRetryFallback bool
}

type RemoteEndpointRegistry struct {
	byKey map[string]RemoteEndpointDescriptor
}

var (
	ErrRemoteEndpointDescriptorInvalid = errors.New("daemon remote endpoint descriptor invalid")
	ErrRemoteEndpointAlreadyRegistered = errors.New("daemon remote endpoint already registered")
	ErrWarmEndpointProfileInvalid      = errors.New("daemon warm endpoint profile invalid")
)

func NewRemoteEndpointRegistry(descriptors []RemoteEndpointDescriptor) (*RemoteEndpointRegistry, error) {
	registry := &RemoteEndpointRegistry{byKey: make(map[string]RemoteEndpointDescriptor, len(descriptors))}
	for _, descriptor := range descriptors {
		descriptor.EndpointID = strings.TrimSpace(descriptor.EndpointID)
		descriptor.Owner = strings.TrimSpace(descriptor.Owner)
		descriptor.CapabilityGate = strings.TrimSpace(descriptor.CapabilityGate)
		descriptor.RegistryVersion = strings.TrimSpace(descriptor.RegistryVersion)
		descriptor.FallbackCommand = strings.TrimSpace(descriptor.FallbackCommand)
		descriptor.MetricsName = strings.TrimSpace(descriptor.MetricsName)
		descriptor.WarmMetadata.Profile = strings.TrimSpace(descriptor.WarmMetadata.Profile)
		descriptor.WarmMetadata.Rationale = strings.TrimSpace(descriptor.WarmMetadata.Rationale)
		if err := validateRemoteEndpointDescriptor(descriptor); err != nil {
			return nil, err
		}
		key := remoteEndpointKey(descriptor.EndpointID, descriptor.Version)
		if _, exists := registry.byKey[key]; exists {
			return nil, ErrRemoteEndpointAlreadyRegistered
		}
		registry.byKey[key] = descriptor
	}
	return registry, nil
}

func validateRemoteEndpointDescriptor(descriptor RemoteEndpointDescriptor) error {
	if descriptor.EndpointID == "" || descriptor.Version <= 0 || descriptor.Owner == "" || descriptor.CapabilityGate == "" || descriptor.RegistryVersion == "" || descriptor.LinkPolicy == "" || descriptor.FallbackCommand == "" || descriptor.Timeout <= 0 || descriptor.DialTimeout <= 0 || descriptor.Limits.RequestBytes <= 0 || descriptor.Limits.ResponseBytes <= 0 || descriptor.MetricsName == "" {
		return ErrRemoteEndpointDescriptorInvalid
	}
	if err := validateRemoteEndpointWarmMetadata(descriptor); err != nil {
		return err
	}
	return nil
}

func validateRemoteEndpointWarmMetadata(descriptor RemoteEndpointDescriptor) error {
	metadata := descriptor.WarmMetadata
	if !validRemoteEndpointWarmSafety(metadata.Safety) {
		return ErrRemoteEndpointDescriptorInvalid
	}
	if !metadata.Eligible {
		if metadata.Profile != "" {
			return ErrRemoteEndpointDescriptorInvalid
		}
		return nil
	}
	if metadata.Profile == "" || metadata.Rationale == "" {
		return ErrRemoteEndpointDescriptorInvalid
	}
	switch metadata.Safety {
	case RemoteEndpointWarmSafetyReadOnly, RemoteEndpointWarmSafetyIdempotent:
	default:
		return ErrRemoteEndpointDescriptorInvalid
	}
	// The default warm profile is a carrier-preparation allowlist, not a retry
	// policy override. Destructive endpoints remain demand-only unless a future
	// spec intentionally expands the profile and proves fallback semantics safe.
	if metadata.Profile == defaultWarmEndpointProfileName && !descriptor.SafeRetryFallback {
		return ErrRemoteEndpointDescriptorInvalid
	}
	return nil
}

func validRemoteEndpointWarmSafety(safety RemoteEndpointWarmSafety) bool {
	switch safety {
	case "", RemoteEndpointWarmSafetyReadOnly, RemoteEndpointWarmSafetyIdempotent, RemoteEndpointWarmSafetyDemandOnly, RemoteEndpointWarmSafetyNonIdempotent:
		return true
	default:
		return false
	}
}

func (registry *RemoteEndpointRegistry) Lookup(endpointID string, version int) (RemoteEndpointDescriptor, bool) {
	if registry == nil {
		return RemoteEndpointDescriptor{}, false
	}
	descriptor, ok := registry.byKey[remoteEndpointKey(endpointID, version)]
	return descriptor, ok
}

func (registry *RemoteEndpointRegistry) Descriptors() []RemoteEndpointDescriptor {
	if registry == nil || len(registry.byKey) == 0 {
		return nil
	}
	descriptors := make([]RemoteEndpointDescriptor, 0, len(registry.byKey))
	for _, descriptor := range registry.byKey {
		descriptors = append(descriptors, descriptor)
	}
	sort.Slice(descriptors, func(i, j int) bool {
		if descriptors[i].EndpointID == descriptors[j].EndpointID {
			return descriptors[i].Version < descriptors[j].Version
		}
		return descriptors[i].EndpointID < descriptors[j].EndpointID
	})
	return descriptors
}

func (registry *RemoteEndpointRegistry) WarmEndpointProfile(name string) (WarmEndpointProfile, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return WarmEndpointProfile{}, ErrWarmEndpointProfileInvalid
	}
	profile := WarmEndpointProfile{Name: name}
	safetySeen := make(map[RemoteEndpointWarmSafety]struct{})
	for _, descriptor := range registry.Descriptors() {
		metadata := descriptor.WarmMetadata
		if !metadata.Eligible || metadata.Profile != name {
			continue
		}
		if metadata.Safety == RemoteEndpointWarmSafetyDemandOnly || metadata.Safety == RemoteEndpointWarmSafetyNonIdempotent {
			return WarmEndpointProfile{}, ErrWarmEndpointProfileInvalid
		}
		if name == defaultWarmEndpointProfileName && !descriptor.SafeRetryFallback {
			return WarmEndpointProfile{}, ErrWarmEndpointProfileInvalid
		}
		profile.EndpointScopes = append(profile.EndpointScopes, RemoteEndpointScope{EndpointID: descriptor.EndpointID, Version: descriptor.Version})
		safetySeen[metadata.Safety] = struct{}{}
	}
	if len(profile.EndpointScopes) == 0 {
		return WarmEndpointProfile{}, ErrWarmEndpointProfileInvalid
	}
	for safety := range safetySeen {
		profile.SafetyClasses = append(profile.SafetyClasses, safety)
	}
	sort.Slice(profile.SafetyClasses, func(i, j int) bool {
		return profile.SafetyClasses[i] < profile.SafetyClasses[j]
	})
	return profile, nil
}

func remoteEndpointKey(endpointID string, version int) string {
	endpointID = strings.TrimSpace(endpointID)
	return endpointID + "@" + strconv.Itoa(version)
}

func defaultRemoteEndpointRegistry() *RemoteEndpointRegistry {
	return defaultRemoteEndpoints
}

func mustRemoteEndpointRegistry(descriptors []RemoteEndpointDescriptor) *RemoteEndpointRegistry {
	registry, err := NewRemoteEndpointRegistry(descriptors)
	if err != nil {
		panic(err)
	}
	return registry
}

func warmEligible(safety RemoteEndpointWarmSafety, rationale string) RemoteEndpointWarmMetadata {
	return RemoteEndpointWarmMetadata{
		Eligible:  true,
		Profile:   defaultWarmEndpointProfileName,
		Safety:    safety,
		Rationale: rationale,
	}
}

func warmDemandOnly(safety RemoteEndpointWarmSafety, rationale string) RemoteEndpointWarmMetadata {
	return RemoteEndpointWarmMetadata{
		Safety:    safety,
		Rationale: rationale,
	}
}

const (
	remoteEndpointWorkspaceList                = "workspace.list"
	remoteEndpointWorkspaceRead                = "workspace.read"
	remoteEndpointWorkspaceApplySeedFiles      = "workspace.apply_seed_files"
	remoteEndpointWorkspaceDelete              = "workspace.delete"
	remoteEndpointReviewSummary                = "review.summary"
	remoteEndpointReviewSnapshot               = "review.snapshot"
	remoteEndpointReviewFingerprint            = "review.fingerprint"
	remoteEndpointReviewFiles                  = "review.files"
	remoteEndpointReviewDiff                   = "review.diff"
	remoteEndpointReviewDiffBatch              = "review.diff_batch"
	remoteEndpointOutputTargetValidate         = "output_target.validate"
	remoteEndpointMachineWorkspaceScan         = "machine.workspace.scan"
	remoteEndpointMachineWorkspaceDelete       = "machine.workspace.delete"
	remoteEndpointMachineDirectoryList         = "machine.directory.list"
	remoteEndpointMachineDashboard             = "machine.dashboard"
	remoteEndpointMachineDashboardVersion      = 1
	remoteEndpointMachineSettings              = "machine.settings"
	remoteEndpointMachineSettingsUpdate        = "machine.settings.update"
	remoteEndpointMachineRuntimesBundle        = "machine.runtimes.bundle"
	remoteEndpointMachineRuntimeConfigRead     = "machine.runtimes.config.read"
	remoteEndpointMachineRuntimeConfigUpdate   = "machine.runtimes.config.update"
	remoteEndpointMachineRuntimeConfigValidate = "machine.runtimes.config.validate"
	remoteEndpointMachineRuntimesDetect        = "machine.runtimes.detect"
	remoteEndpointMachineMCPServerList         = "machine.mcp_servers.list"
	remoteEndpointMachineMCPServerUpsert       = "machine.mcp_servers.upsert"
	remoteEndpointMachineMCPServerDelete       = "machine.mcp_servers.delete"
	remoteEndpointMachineMCPServerTest         = "machine.mcp_servers.test"
	remoteEndpointRuntimeTrajectoryHistory     = "runtime_access.trajectory_history"
	remoteEndpointRuntimeRestoreRecoveryPoint  = "runtime_access.restore_recovery_point"
	remoteEndpointRuntimeMemorySyncStatus      = "runtime_access.memory_sync_status"
	remoteEndpointRuntimeMemorySyncRetry       = "runtime_access.memory_sync_retry"
)

var defaultRemoteEndpoints = mustRemoteEndpointRegistry([]RemoteEndpointDescriptor{
	{
		EndpointID:      remoteEndpointRuntimeMemorySyncStatus,
		Version:         1,
		Owner:           commandOwnerRuntimeAccess,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         5 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  4 * 1024,
			ResponseBytes: 4 * 1024,
		},
		MetricsName:       "runtime_access.memory_sync_status",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "Memory sync status is requested only while the Memory panel is visible"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointRuntimeMemorySyncRetry,
		Version:         1,
		Owner:           commandOwnerRuntimeAccess,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         5 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  4 * 1024,
			ResponseBytes: 4 * 1024,
		},
		MetricsName:       "runtime_access.memory_sync_retry",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyIdempotent, "Memory retry is an explicit idempotent user action"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointWorkspaceList,
		Version:         1,
		Owner:           commandOwnerWorkspace,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: sessionLinkCommandAgentWorkspaceList,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "workspace.list",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "directory listings are read-only and safe for user-active warm carriers"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointWorkspaceRead,
		Version:         1,
		Owner:           commandOwnerWorkspace,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentWorkspaceRead,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 2 * 1024 * 1024,
		},
		MetricsName:       "workspace.read",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "file reads are read-only and safe for user-active warm carriers"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointWorkspaceApplySeedFiles,
		Version:         1,
		Owner:           commandOwnerWorkspace,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentWorkspaceApplySeedFiles,
		Timeout:         30 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  5 * 1024 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "workspace.apply_seed_files",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "seed file writes are mutating and must not be assigned through a proactive warm profile"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointWorkspaceDelete,
		Version:         1,
		Owner:           commandOwnerWorkspace,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: sessionLinkCommandAgentWorkspaceDelete,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "workspace.delete",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "workspace delete is destructive and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointReviewSummary,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentReviewSummary,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "review.summary",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "review summary reads local git state without mutation"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointReviewSnapshot,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         30 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 8 * 1024 * 1024,
		},
		MetricsName:       "review.snapshot",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "review snapshots remain demand-only because older desktops require API-layer fallback"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointReviewFingerprint,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		// Large-repo cold starts (first poll after a daemon restart, before the
		// commit-pair LRU warms) can run a full base..head DiffTree + worktree
		// scan in well over 5s; 5s was too tight and turned cold polls into
		// timeouts that collapsed to a 501. 15s keeps the cheap-warm path snappy
		// while letting a cold scan complete.
		Timeout:     15 * time.Second,
		DialTimeout: defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes: 64 * 1024,
			// A fingerprint is a few hundred bytes; 4 KiB is plenty while
			// leaving headroom for future fields without forcing another
			// endpoint-version bump.
			ResponseBytes: 4 * 1024,
		},
		MetricsName:       "review.fingerprint",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "review fingerprints are read-only compact signatures safe for warm reuse"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointReviewFiles,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentReviewFiles,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 512 * 1024,
		},
		MetricsName:       "review.files",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "review file listings are read-only and safe for warm reuse"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointReviewDiff,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentReviewDiff,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 2 * 1024 * 1024,
		},
		MetricsName:       "review.diff",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "review diffs are read-only and safe for warm reuse"),
		SafeRetryFallback: true,
	},
	{
		// review.diff_batch resolves multiple per-file diffs in a single
		// desktop round-trip. Desktop versions that do not register this
		// endpoint are detected via the standard remote-endpoint capability
		// check and the server-side Hub falls back to one session-link
		// review.diff call per path. The response budget mirrors review.diff
		// scaled up: a typical batch is 5-10 paths, each up to ~256 KiB of
		// diff text; the cap leaves headroom for larger PRs without forcing
		// a future endpoint-version bump.
		EndpointID:      remoteEndpointReviewDiffBatch,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         30 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  256 * 1024,
			ResponseBytes: 8 * 1024 * 1024,
		},
		MetricsName:       "review.diff_batch",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "review diff batches are read-only and safe for warm reuse"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointOutputTargetValidate,
		Version:         1,
		Owner:           commandOwnerReview,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: sessionLinkCommandOutputTargetValidate,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "output_target.validate",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyIdempotent, "output target validation is side-effect free for repeated eligibility checks"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointMachineWorkspaceScan,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandMachineWorkspaceScan,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 512 * 1024,
		},
		MetricsName:       "machine.workspace.scan",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "workspace scans enumerate machine-local directories without mutation"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointMachineWorkspaceDelete,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: sessionLinkCommandMachineWorkspaceDelete,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "machine.workspace.delete",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine workspace delete is destructive and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineDirectoryList,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes: 64 * 1024,
			// Workspacefs caps each page envelope at 448 KiB; 512 KiB leaves
			// transport headroom for the wire shape without forcing an
			// endpoint-version bump if a future schema adds bounded metadata.
			ResponseBytes: 512 * 1024,
		},
		MetricsName:  "machine.directory.list",
		WarmMetadata: warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine directory browsing is an explicit user request and remains demand-only"),
		// No legacy Session Link command carries this payload. On elastic
		// carrier miss the Hub falls back to the typed RemoteRequestAssignment
		// envelope over the session link when the daemon advertises the
		// endpoint, or surfaces a typed ErrMachineDirectoryUnsupported
		// sentinel otherwise. SafeRetryFallback is false because there is no
		// execution-after retry: an accepted assignment must surface its
		// outcome once instead of being re-dispatched through a second path
		// that could re-enumerate the same directory.
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineDashboard,
		Version:         remoteEndpointMachineDashboardVersion,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 2 * 1024 * 1024,
		},
		MetricsName:       "machine.dashboard",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine dashboards query local metrics on explicit overview entry"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineSettings,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 512 * 1024,
		},
		MetricsName:       "machine.settings",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine settings reads local desktop config on explicit settings tab entry"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineSettingsUpdate,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  128 * 1024,
			ResponseBytes: 512 * 1024,
		},
		MetricsName:       "machine.settings.update",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine settings update persists desktop config and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineRuntimesBundle,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 1 * 1024 * 1024,
		},
		MetricsName:       "machine.runtimes.bundle",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine runtimes bundle reads runtime state on explicit runtimes tab entry"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineRuntimeConfigRead,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "machine.runtimes.config.read",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine runtime config reads local runtime config without mutation"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineRuntimeConfigUpdate,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  128 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "machine.runtimes.config.update",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine runtime config update persists runtime config and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineRuntimeConfigValidate,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  128 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "machine.runtimes.config.validate",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine runtime config validation is side-effect free for repeated eligibility checks"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineRuntimesDetect,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 1 * 1024 * 1024,
		},
		MetricsName:       "machine.runtimes.detect",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine runtime detection triggers a PATH scan and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineMCPServerList,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 1 * 1024 * 1024,
		},
		MetricsName:       "machine.mcp_servers.list",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine mcp server list reads local server config on explicit mcp tab entry"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineMCPServerUpsert,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  256 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "machine.mcp_servers.upsert",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine mcp server upsert persists server config and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineMCPServerDelete,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "machine.mcp_servers.delete",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "machine mcp server delete is destructive and remains demand-only"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointMachineMCPServerTest,
		Version:         1,
		Owner:           commandOwnerMachine,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: commandFallbackNone,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 256 * 1024,
		},
		MetricsName:       "machine.mcp_servers.test",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyReadOnly, "machine mcp server test probes connectivity without mutation"),
		SafeRetryFallback: false,
	},
	{
		EndpointID:      remoteEndpointRuntimeTrajectoryHistory,
		Version:         1,
		Owner:           commandOwnerRuntimeAccess,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicyElasticPreferred,
		FallbackCommand: sessionLinkCommandAgentTrajectoryHistory,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 2 * 1024 * 1024,
		},
		MetricsName:       "runtime_access.trajectory_history",
		WarmMetadata:      warmEligible(RemoteEndpointWarmSafetyReadOnly, "trajectory history reads bounded runtime state without mutation"),
		SafeRetryFallback: true,
	},
	{
		EndpointID:      remoteEndpointRuntimeRestoreRecoveryPoint,
		Version:         1,
		Owner:           commandOwnerRuntimeAccess,
		CapabilityGate:  daemonEndpointsCapabilityV1,
		RegistryVersion: defaultEndpointRegistryVersion,
		LinkPolicy:      RemoteEndpointLinkPolicySessionPreferred,
		FallbackCommand: sessionLinkCommandAgentRestoreRecoveryPoint,
		Timeout:         15 * time.Second,
		DialTimeout:     defaultElasticRequestLinkConnectTimeout,
		Limits: RemoteEndpointLimits{
			RequestBytes:  64 * 1024,
			ResponseBytes: 64 * 1024,
		},
		MetricsName:       "runtime_access.restore_recovery_point",
		WarmMetadata:      warmDemandOnly(RemoteEndpointWarmSafetyNonIdempotent, "restore recovery point mutates runtime state and remains demand-only"),
		SafeRetryFallback: false,
	},
})
