//! Lab portability contracts.
//!
//! Owns the data describing whether a command can be offloaded to a Lab
//! runner (`LabCommandContract`, `LabCommandPortability`, `LabSourcePathMode`,
//! `LabWorkspaceModePolicy`), the per-command
//! `Commands::lab_contract` resolution, and the helpers that surface
//! Lab-specific information through `CommandDescriptor` and the public
//! `Commands` accessors (`supports_lab_runner`,
//! `lab_runner_unsupported_reason`, `lab_offload_mutation_flag`).

use crate::cli_surface::Commands;
use crate::command_contract::{CommandDescriptor, CommandOutputFileMode};
use crate::commands::agent_task;
use crate::core::agent_tasks::provider::{default_backend, provider_requires_cwd_git_checkout};
use crate::core::engine::execution_context::{self, ResolveOptions};
use crate::core::extension::ExtensionCapability;
use crate::core::run_outcome_envelope::RunOutcomeEnvelope;
use crate::core::runner_execution_envelope::{
    PathMaterializationPlan, RunnerExecutionArtifactRef, RunnerExecutionNextAction,
    RunnerExecutionRecord,
};
use std::collections::BTreeSet;

use super::spec::{
    CommandLabSupportSummary, AGENT_TASK_AUTH_STATUS_LAB_LABEL,
    AGENT_TASK_CONTROLLER_FROM_SPEC_LAB_LABEL, AGENT_TASK_CONTROLLER_RESUME_LAB_LABEL,
    AGENT_TASK_FANOUT_COOK_BATCH_LAB_LABEL, AGENT_TASK_FANOUT_RUN_PLAN_LAB_LABEL,
    AGENT_TASK_FANOUT_STATUS_LAB_LABEL, AGENT_TASK_FANOUT_SUBMIT_BATCH_LAB_LABEL,
    AGENT_TASK_PROVIDERS_LAB_LABEL, AGENT_TASK_RUN_LAB_LABEL, AGENT_TASK_STATUS_LAB_LABEL,
    AUDIT_LAB_LABEL, BENCH_LAB_LABEL, COMMAND_SPECS, FUZZ_LAB_LABEL, LINT_LAB_LABEL,
    REFACTOR_LAB_LABEL, REVIEW_LAB_LABEL, RIG_CHECK_LAB_LABEL, RIG_RUN_LAB_LABEL,
    RUNTIME_REFRESH_LAB_LABEL, TEST_LAB_LABEL, TRACE_LAB_LABEL,
};

pub const RUNNER_WORKLOAD_SCHEMA: &str = "homeboy/runner-workload/v1";
pub const RUNNER_HANDOFF_ENVELOPE_SCHEMA: &str = "homeboy/runner-exec-handoff/v1";
pub const RUN_LOCATION_INDEX_SCHEMA: &str = "homeboy/run-location-index/v1";
pub const RUNNER_ARTIFACT_MANIFEST_REF_NAME: &str = "runner-artifact-manifest-ref";
pub const RUNNER_ARTIFACT_MANIFEST_REF_SCHEMA: &str = "homeboy/runner-artifact-manifest-ref/v1";
pub const RUNNER_ARTIFACT_MANIFEST_SCHEMA: &str = crate::core::artifacts::ARTIFACT_MANIFEST_SCHEMA;
pub const RUNNER_ARTIFACT_MANIFEST_FILE: &str = crate::core::artifacts::ARTIFACT_MANIFEST_FILE;
pub const RUNNER_ARTIFACT_ROOT_DIR_SUFFIX: &str = "-homeboy-artifacts";

/// Routing-policy flags shared by every Lab command representation
/// (`LabCommandContract`, `LabRoutePlan`, `LabOffloadCommand`). These four
/// booleans travel together as one cohesive policy as a command is resolved
/// from its contract into a route plan and finally an offload command, so they
/// live in a single embedded struct rather than being duplicated field-by-field
/// across the three layers.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LabRoutingPolicy {
    /// Whether the command offloads to a default Lab runner without an explicit
    /// `--runner` selection.
    pub default_lab_offload: bool,
    /// Whether source-path tool inference applies to this command.
    pub infer_source_path_tools: bool,
    /// Whether this command is a release gate whose routing fidelity matters
    /// for validating a release (lint/test/audit). When true, force-local
    /// bypass and stale-runner local fallback fail closed under the
    /// `/release_gate/local_hot` policy if a default Lab runner is configured.
    /// See issues #4603 / #4605.
    pub release_gate: bool,
    /// Whether the command requires extension parity between controller and
    /// runner before offloading.
    pub requires_extension_parity: bool,
    /// Whether runner-resident reads should avoid durable mirror runs and
    /// handoff boilerplate unless the command itself emits them.
    pub read_only_polling: bool,
}

#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub struct LabCommandRouteContract {
    pub command: LabCommandContract,
    pub required_extensions: Vec<String>,
    pub required_capabilities: Vec<RunnerWorkloadCapability>,
}

#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
pub enum LabSecretEnvSource {
    AgentTask,
    Trace,
    Tunnel,
}

pub const LAB_NO_SECRET_ENV_SOURCES: &[LabSecretEnvSource] = &[];
pub(crate) const LAB_AGENT_TASK_SECRET_ENV_SOURCES: &[LabSecretEnvSource] =
    &[LabSecretEnvSource::AgentTask];
pub(crate) const LAB_TRACE_SECRET_ENV_SOURCES: &[LabSecretEnvSource] = &[LabSecretEnvSource::Trace];
pub(crate) const LAB_TUNNEL_SECRET_ENV_SOURCES: &[LabSecretEnvSource] =
    &[LabSecretEnvSource::Tunnel];

#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
pub struct LabCommandContract {
    pub hot_label: &'static str,
    pub portability: LabCommandPortability,
    pub source_path_mode: LabSourcePathMode,
    pub workspace_mode_policy: LabWorkspaceModePolicy,
    pub capture_mutation_patch: bool,
    pub mutation_flag: Option<&'static str>,
    pub extra_required_capabilities: &'static [&'static str],
    pub secret_env_sources: &'static [LabSecretEnvSource],
    /// Routing-policy flags shared across the Lab command layers.
    pub routing_policy: LabRoutingPolicy,
}

#[derive(Debug, Clone, Copy, Default, serde::Serialize, PartialEq, Eq)]
pub struct CommandPortabilityContract {
    lab_command: Option<LabCommandContract>,
}

impl CommandPortabilityContract {
    pub const fn none() -> Self {
        Self { lab_command: None }
    }

    pub const fn lab(command: LabCommandContract) -> Self {
        Self {
            lab_command: Some(command),
        }
    }

    pub const fn lab_optional(command: Option<LabCommandContract>) -> Self {
        Self {
            lab_command: command,
        }
    }

    pub const fn lab_command(self) -> Option<LabCommandContract> {
        self.lab_command
    }
}

mod portability_enums {
    #[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
    pub enum LabCommandPortability {
        Portable,
        LocalOnly(&'static str),
    }

    #[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
    pub enum LabSourcePathMode {
        CwdOrPathFlag,
        RunnerResident,
    }

    #[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq)]
    pub enum LabWorkspaceModePolicy {
        ChangedSinceGitElseSnapshot,
        Git,
        GitCheckoutRequired,
        RunnerResident,
    }
}
pub use portability_enums::*;

mod runner_workload_types {
    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkload {
        pub schema: String,
        pub workload_id: String,
        pub kind: RunnerWorkloadKind,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub agent_task: Option<RunnerWorkloadAgentTask>,
        pub workspace_mappings: RunnerWorkloadWorkspaceMappings,
        pub required_capabilities: Vec<RunnerWorkloadCapability>,
        pub required_secrets: RunnerWorkloadSecrets,
        pub required_extensions: Vec<String>,
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pub required_extension_revisions: Vec<RunnerWorkloadExtensionRevision>,
        pub mutation_policy: RunnerWorkloadMutationPolicy,
        pub assignment: RunnerWorkloadAssignment,
        pub state: RunnerWorkloadState,
        pub result_refs: RunnerWorkloadResultRefs,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadExtensionRevision {
        pub extension_id: String,
        pub source_revision: String,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadKind {
        pub command_label: String,
        pub command_family: RunnerWorkloadCommandFamily,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadAgentTask {
        pub run_id: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub plan_ref: Option<String>,
        pub dispatch_kind: RunnerWorkloadAgentTaskDispatchKind,
        pub lifecycle_mirror_policy: RunnerWorkloadAgentTaskLifecycleMirrorPolicy,
    }

    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum RunnerWorkloadAgentTaskDispatchKind {
        Cook,
        Dispatch,
        RunPlan,
    }

    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum RunnerWorkloadAgentTaskLifecycleMirrorPolicy {
        None,
        RunPlanAggregate,
    }

    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    #[serde(rename_all = "snake_case")]
    pub enum RunnerWorkloadCommandFamily {
        AgentTask,
        Quality,
        Workspace,
        Service,
        Unknown,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadWorkspaceMappings {
        pub source_path_mode: String,
        pub workspace_mode_policy: String,
        pub mapping_ref: Option<String>,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadCapability {
        pub name: String,
        pub required: bool,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadSecrets {
        pub categories: Vec<String>,
        #[serde(default)]
        pub secret_env_plan: crate::core::secret_env_plan::SecretEnvPlan,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadMutationPolicy {
        pub capture_patch: bool,
        pub mutation_flag: Option<String>,
        pub allow_dirty_lab_workspace: bool,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadAssignment {
        pub runner_id: Option<String>,
        pub runner_mode: Option<String>,
        pub source: Option<String>,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadState {
        pub status: String,
        pub remote_workspace: Option<String>,
        pub fallback_reason: Option<String>,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadResultRefs {
        pub plan_id: String,
        pub proof_id: Option<String>,
        pub workspace_mapping_ref: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub job_id: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub mirror_run_id: Option<String>,
        #[serde(default, skip_serializing_if = "Vec::is_empty")]
        pub artifacts: Vec<RunnerWorkloadArtifactRef>,
    }

    #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
    pub struct RunnerWorkloadArtifactRef {
        pub id: String,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub name: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub path: Option<String>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        pub url: Option<String>,
    }
}
pub use runner_workload_types::*;

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunnerHandoffEnvelope {
    pub schema: String,
    pub status: String,
    pub execution_location: String,
    #[serde(default)]
    pub identity: AgentTaskDispatchIdentity,
    pub runner_id: String,
    pub job_id: String,
    pub durable_run_id: Option<String>,
    pub persisted_run_id: Option<String>,
    pub mirror_run_id: Option<String>,
    pub remote_cwd: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub path_materialization_plan: Option<PathMaterializationPlan>,
    pub artifact_manifest: RunnerHandoffArtifactManifestRef,
    pub run_location_index: RunLocationIndex,
    pub evidence: RunnerHandoffEvidence,
    pub follow_commands: RunnerHandoffFollowCommands,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunnerHandoffArtifactManifestRef {
    pub schema: String,
    pub manifest_schema: String,
    pub path: String,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunnerHandoffEvidence {
    pub schema: String,
    pub status: String,
    pub runner_id: String,
    pub runner_job_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub run_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub persisted_run_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mirror_run_id: Option<String>,
    pub remote_cwd: String,
    pub artifact_manifest: RunnerHandoffArtifactManifestRef,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub artifact_refs: Vec<RunnerWorkloadArtifactRef>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub next_commands: Vec<RunnerHandoffNextCommand>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunnerHandoffNextCommand {
    pub label: String,
    pub command: Vec<String>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AgentTaskDispatchIdentity {
    pub runner_id: String,
    pub runner_job_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub persisted_run_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub run_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub handoff_id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunnerHandoffFollowCommands {
    pub job_logs: String,
    pub job_cancel: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub logs: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub artifacts: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunLocationIndex {
    pub schema: String,
    pub run_id: String,
    pub controller_location: String,
    pub runner_id: String,
    pub remote_job_id: String,
    pub remote_cwd: String,
    pub artifact_manifest_ref: RunnerHandoffArtifactManifestRef,
    pub liveness_heartbeat_timestamp: String,
    pub follow_commands: RunnerHandoffFollowCommands,
}

impl RunnerHandoffEnvelope {
    pub fn detached_lab_offload(
        runner_id: &str,
        job_id: &str,
        remote_cwd: String,
        path_materialization_plan: Option<PathMaterializationPlan>,
        mirror_run_id: Option<String>,
        liveness_heartbeat_timestamp: String,
    ) -> Self {
        let job_logs_command = vec![
            "homeboy".to_string(),
            "runner".to_string(),
            "job".to_string(),
            "logs".to_string(),
            runner_id.to_string(),
            job_id.to_string(),
            "--follow".to_string(),
        ];
        let job_cancel_command = vec![
            "homeboy".to_string(),
            "runner".to_string(),
            "job".to_string(),
            "cancel".to_string(),
            runner_id.to_string(),
            job_id.to_string(),
        ];
        let mut next_commands = vec![
            RunnerHandoffNextCommand {
                label: "runner_job_logs".to_string(),
                command: job_logs_command.clone(),
            },
            RunnerHandoffNextCommand {
                label: "runner_job_cancel".to_string(),
                command: job_cancel_command.clone(),
            },
        ];
        if let Some(run_id) = mirror_run_id.as_ref() {
            next_commands.extend([
                RunnerHandoffNextCommand {
                    label: "run_status".to_string(),
                    command: vec![
                        "homeboy".to_string(),
                        "agent-task".to_string(),
                        "status".to_string(),
                        run_id.clone(),
                    ],
                },
                RunnerHandoffNextCommand {
                    label: "run_logs".to_string(),
                    command: vec![
                        "homeboy".to_string(),
                        "agent-task".to_string(),
                        "logs".to_string(),
                        run_id.clone(),
                    ],
                },
                RunnerHandoffNextCommand {
                    label: "run_artifacts".to_string(),
                    command: vec![
                        "homeboy".to_string(),
                        "agent-task".to_string(),
                        "artifacts".to_string(),
                        run_id.clone(),
                    ],
                },
            ]);
        }
        let artifact_manifest = RunnerHandoffArtifactManifestRef::for_remote_cwd(&remote_cwd);
        let run_location_index_path = run_location_index_path(&remote_cwd);
        let follow_commands = RunnerHandoffFollowCommands {
            job_logs: format!("homeboy runner job logs {runner_id} {job_id} --follow"),
            job_cancel: format!("homeboy runner job cancel {runner_id} {job_id}"),
            status: mirror_run_id
                .as_ref()
                .map(|run_id| format!("homeboy agent-task status {run_id}")),
            logs: mirror_run_id
                .as_ref()
                .map(|run_id| format!("homeboy agent-task logs {run_id}")),
            artifacts: mirror_run_id
                .as_ref()
                .map(|run_id| format!("homeboy agent-task artifacts {run_id}")),
        };
        let run_location_index = RunLocationIndex {
            schema: RUN_LOCATION_INDEX_SCHEMA.to_string(),
            run_id: mirror_run_id
                .clone()
                .unwrap_or_else(|| format!("runner:{runner_id}:job:{job_id}")),
            controller_location: "controller:local".to_string(),
            runner_id: runner_id.to_string(),
            remote_job_id: job_id.to_string(),
            remote_cwd: remote_cwd.clone(),
            artifact_manifest_ref: artifact_manifest.clone(),
            liveness_heartbeat_timestamp,
            follow_commands: follow_commands.clone(),
        };
        let evidence = RunnerHandoffEvidence {
            schema: "homeboy/runner-handoff-evidence/v1".to_string(),
            status: "running".to_string(),
            runner_id: runner_id.to_string(),
            runner_job_id: job_id.to_string(),
            run_id: mirror_run_id.clone(),
            persisted_run_id: mirror_run_id.clone(),
            mirror_run_id: mirror_run_id.clone(),
            remote_cwd: remote_cwd.clone(),
            artifact_manifest: artifact_manifest.clone(),
            artifact_refs: vec![
                RunnerWorkloadArtifactRef {
                    id: "artifact_manifest".to_string(),
                    name: Some("runner artifact manifest".to_string()),
                    path: Some(artifact_manifest.path.clone()),
                    url: None,
                },
                RunnerWorkloadArtifactRef {
                    id: "run_location_index".to_string(),
                    name: Some("run location index".to_string()),
                    path: Some(run_location_index_path),
                    url: None,
                },
            ],
            next_commands,
        };
        Self {
            schema: RUNNER_HANDOFF_ENVELOPE_SCHEMA.to_string(),
            status: "running".to_string(),
            execution_location: format!("runner:{runner_id}"),
            identity: AgentTaskDispatchIdentity {
                runner_id: runner_id.to_string(),
                runner_job_id: job_id.to_string(),
                persisted_run_id: mirror_run_id.clone(),
                run_id: mirror_run_id.clone(),
                handoff_id: Some(format!("runner:{runner_id}:job:{job_id}")),
            },
            runner_id: runner_id.to_string(),
            job_id: job_id.to_string(),
            durable_run_id: mirror_run_id.clone(),
            persisted_run_id: mirror_run_id.clone(),
            mirror_run_id,
            artifact_manifest,
            run_location_index,
            remote_cwd,
            path_materialization_plan,
            evidence,
            follow_commands,
        }
    }

    pub fn runner_execution_record(&self) -> RunnerExecutionRecord {
        let record = if self.status == "running" {
            RunnerExecutionRecord::in_flight(self.job_id.clone(), self.runner_id.clone(), "daemon")
        } else {
            RunnerExecutionRecord::terminal(
                self.job_id.clone(),
                self.runner_id.clone(),
                "daemon",
                if self.status == "succeeded" { 0 } else { 1 },
            )
        };
        record
            .with_job_id(self.job_id.clone())
            .with_mirror_run_id(
                self.mirror_run_id
                    .clone()
                    .or_else(|| self.persisted_run_id.clone())
                    .or_else(|| self.durable_run_id.clone()),
            )
            .with_path_materialization_plan(self.path_materialization_plan.clone())
            .with_artifact_refs(self.evidence.artifact_refs.iter().map(|artifact| {
                RunnerExecutionArtifactRef {
                    id: artifact.id.clone(),
                    name: artifact.name.clone(),
                    path: artifact.path.clone(),
                    url: artifact.url.clone(),
                }
            }))
            .with_next_actions(self.evidence.next_commands.iter().map(|command| {
                RunnerExecutionNextAction {
                    label: command.label.clone(),
                    command: command.command.clone(),
                }
            }))
    }

    pub fn run_outcome_envelope(&self) -> RunOutcomeEnvelope {
        RunOutcomeEnvelope::from_runner_execution_record(&self.runner_execution_record())
    }
}

impl RunnerHandoffArtifactManifestRef {
    pub fn for_remote_cwd(remote_cwd: &str) -> Self {
        Self {
            schema: RUNNER_ARTIFACT_MANIFEST_REF_SCHEMA.to_string(),
            manifest_schema: RUNNER_ARTIFACT_MANIFEST_SCHEMA.to_string(),
            path: format!(
                "{}{RUNNER_ARTIFACT_ROOT_DIR_SUFFIX}/{RUNNER_ARTIFACT_MANIFEST_FILE}",
                remote_cwd.trim_end_matches('/')
            ),
        }
    }
}

pub fn run_location_index_path(remote_cwd: &str) -> String {
    format!(
        "{}{RUNNER_ARTIFACT_ROOT_DIR_SUFFIX}/homeboy-run-location-index.json",
        remote_cwd.trim_end_matches('/')
    )
}

mod local_execution_enums {
    #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "snake_case", tag = "mode")]
    pub enum LabLocalExecutionPolicy {
        Allow {
            local_hot: LabLocalHotPolicy,
            selected_runner_fallback: LabSelectedRunnerFallbackPolicy,
        },
        Deny,
    }

    #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "snake_case")]
    pub enum LabLocalHotPolicy {
        RequireRemoteWhenDefaultRunnerExists,
        AllowControllerOverride,
    }

    #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "snake_case")]
    pub enum LabSelectedRunnerFallbackPolicy {
        Deny,
        AllowControllerFallback,
    }
}
pub use local_execution_enums::*;

impl Default for LabLocalExecutionPolicy {
    fn default() -> Self {
        Self::Allow {
            local_hot: LabLocalHotPolicy::RequireRemoteWhenDefaultRunnerExists,
            selected_runner_fallback: LabSelectedRunnerFallbackPolicy::Deny,
        }
    }
}

impl LabLocalExecutionPolicy {
    pub const fn from_flags(
        allow_local_hot: bool,
        allow_local_fallback: bool,
        deny_local_execution: bool,
    ) -> Self {
        if deny_local_execution {
            return Self::Deny;
        }
        Self::Allow {
            local_hot: if allow_local_hot {
                LabLocalHotPolicy::AllowControllerOverride
            } else {
                LabLocalHotPolicy::RequireRemoteWhenDefaultRunnerExists
            },
            selected_runner_fallback: if allow_local_fallback {
                LabSelectedRunnerFallbackPolicy::AllowControllerFallback
            } else {
                LabSelectedRunnerFallbackPolicy::Deny
            },
        }
    }

    pub const fn allow_local_hot(self) -> bool {
        matches!(
            self,
            Self::Allow {
                local_hot: LabLocalHotPolicy::AllowControllerOverride,
                ..
            }
        )
    }

    pub const fn allow_local_fallback(self) -> bool {
        matches!(
            self,
            Self::Allow {
                selected_runner_fallback: LabSelectedRunnerFallbackPolicy::AllowControllerFallback,
                ..
            }
        )
    }

    pub const fn deny_local_execution(self) -> bool {
        matches!(self, Self::Deny)
    }
}

pub const LAB_CAPABILITY_PLAYWRIGHT: &str = "playwright";
pub const LAB_TRACE_EXTRA_CAPABILITIES: &[&str] = &[LAB_CAPABILITY_PLAYWRIGHT];
pub(crate) const LAB_NO_EXTRA_CAPABILITIES: &[&str] = &[];
pub(crate) const RIG_UP_LAB_UNSUPPORTED_REASON: &str = "`rig up` stays local because rig pipelines manage local services, leases, ports, and declared filesystem paths that the current single-workspace Lab snapshot cannot safely mirror. For Lab/offloaded dependency preparation and verification, run `homeboy rig check <rig-id> --runner <runner-id>` or the rig's benchmark profile through `homeboy rig run <rig-id> --runner <runner-id>`.";
const AGENT_TASK_COOK_MISSING_VERIFY_GATE_REASON: &str =
    "agent-task cook requires at least one deterministic --verify or --private-verify gate";
const AGENT_TASK_COOK_FINALIZATION_CONTROLLER_REASON: &str =
    "agent-task cook finalization commits, pushes, and opens/updates GitHub PRs from the trusted controller; use --no-finalize for Lab patch evidence and finalize from the controller";
const AGENT_TASK_FANOUT_COOK_BATCH_DRY_RUN_CONTROLLER_REASON: &str =
    "agent-task fanout cook-batch --dry-run is controller-local planning; it does not execute cooks and should not offload or materialize the controller cwd";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LabRunnerSupportSummary {
    pub supported_labels: Vec<&'static str>,
    pub unsupported_message: String,
    pub hint: String,
}

mod runner_support {
    use super::*;

    pub fn lab_runner_supported_labels() -> Vec<&'static str> {
        lab_support_summaries()
            .map(|summary| summary.message_label)
            .collect()
    }

    pub fn lab_runner_supported_contract_labels() -> Vec<&'static str> {
        lab_support_summaries()
            .flat_map(|summary| summary.contract_labels.iter().copied())
            .collect::<BTreeSet<_>>()
            .into_iter()
            .collect()
    }

    pub fn lab_runner_supports_contract_label(contract_label: &str) -> bool {
        lab_support_summaries().any(|summary| summary.contract_labels.contains(&contract_label))
    }

    pub fn lab_runner_support_summary() -> LabRunnerSupportSummary {
        let supported_labels = lab_runner_supported_labels();
        let hint_labels = lab_runner_supported_hint_labels();

        LabRunnerSupportSummary {
            unsupported_message: format!(
                "--runner is only supported for commands with portable Lab offload support: {}",
                human_join(&supported_labels)
            ),
            hint: format!("Current Lab offload support: {}.", human_join(&hint_labels)),
            supported_labels,
        }
    }

    pub fn lab_runner_unsupported_message() -> String {
        lab_runner_support_summary().unsupported_message
    }

    pub fn lab_runner_unsupported_hint() -> String {
        lab_runner_support_summary().hint
    }

    fn lab_runner_supported_hint_labels() -> Vec<&'static str> {
        lab_support_summaries()
            .map(|summary| summary.hint_label)
            .collect()
    }

    fn lab_support_summaries() -> impl Iterator<Item = &'static CommandLabSupportSummary> {
        COMMAND_SPECS
            .iter()
            .flat_map(|spec| spec.lab_support_summary.iter())
    }

    fn human_join(labels: &[&str]) -> String {
        match labels {
            [] => String::new(),
            [label] => (*label).to_string(),
            [first, second] => format!("{first} and {second}"),
            [rest @ .., last] => format!("{}, and {last}", rest.join(", ")),
        }
    }
}
pub use runner_support::*;

impl Commands {
    pub fn lab_contract(&self) -> Option<LabCommandContract> {
        let mut contract = self.portability_contract().lab_command()?;

        // Agent-task commands whose provider needs a real git checkout of the
        // cwd workspace upgrade to the GitCheckoutRequired policy. This applies
        // uniformly to every resolved agent-task contract; the predicate only
        // returns true for the run/from-spec commands that own a portable or
        // explicit-runner base, so the other arms (which set their own
        // runner-resident policy) are left untouched.
        //
        // The loop-controller spec-materialization family (from-spec --resume /
        // run-from-spec / materialize) always materializes a real worktree to
        // apply patches, so it requires a git checkout regardless of which
        // dispatch provider (if any) is configured — the provider predicate only
        // covers the backend-specific path, which is `None` when no
        // `--dispatch-backend` is supplied.
        if let Commands::AgentTask(args) = self {
            if agent_task_controller_materializes_worktree(&args.command)
                || agent_task_provider_requires_cwd_git_checkout(&args.command)
            {
                contract.workspace_mode_policy = LabWorkspaceModePolicy::GitCheckoutRequired;
            }
        }

        Some(contract)
    }

    pub fn portability_contract(&self) -> CommandPortabilityContract {
        let contract = match self {
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command: agent_task::AgentTaskCommand::Cook(args),
            }) if !args.gates.has_deterministic_gate() => LabCommandContract::local_only(
                AGENT_TASK_RUN_LAB_LABEL,
                AGENT_TASK_COOK_MISSING_VERIFY_GATE_REASON,
            ),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command: agent_task::AgentTaskCommand::Cook(args),
            }) if !args.no_finalize => LabCommandContract::local_only(
                AGENT_TASK_RUN_LAB_LABEL,
                AGENT_TASK_COOK_FINALIZATION_CONTROLLER_REASON,
            ),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Cook(_)
                    | agent_task::AgentTaskCommand::RunPlan(_)
                    | agent_task::AgentTaskCommand::Retry(agent_task::RetryArgs { run: true, .. }),
            }) => LabCommandContract::portable(
                AGENT_TASK_RUN_LAB_LABEL,
                None,
                true,
                LAB_NO_EXTRA_CAPABILITIES,
            )
            .with_secret_env_sources(LAB_AGENT_TASK_SECRET_ENV_SOURCES),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command: agent_task::AgentTaskCommand::Providers(_),
            }) => LabCommandContract::explicit_runner_simple(AGENT_TASK_PROVIDERS_LAB_LABEL),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Fanout(agent_task::AgentTaskFanoutArgs {
                        command: agent_task::AgentTaskFanoutCommand::RunPlan(_),
                    }),
            }) => LabCommandContract::portable(
                AGENT_TASK_FANOUT_RUN_PLAN_LAB_LABEL,
                None,
                true,
                LAB_NO_EXTRA_CAPABILITIES,
            )
            .with_secret_env_sources(LAB_AGENT_TASK_SECRET_ENV_SOURCES),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Fanout(agent_task::AgentTaskFanoutArgs {
                        command:
                            agent_task::AgentTaskFanoutCommand::CookBatch(
                                agent_task::AgentTaskFanoutCookBatchArgs { dry_run: true, .. },
                            ),
                    }),
            }) => LabCommandContract::local_only(
                AGENT_TASK_FANOUT_COOK_BATCH_LAB_LABEL,
                AGENT_TASK_FANOUT_COOK_BATCH_DRY_RUN_CONTROLLER_REASON,
            ),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Fanout(agent_task::AgentTaskFanoutArgs {
                        command:
                            agent_task::AgentTaskFanoutCommand::CookBatch(
                                agent_task::AgentTaskFanoutCookBatchArgs { dry_run: false, .. },
                            ),
                    }),
            }) => LabCommandContract::portable(
                AGENT_TASK_FANOUT_COOK_BATCH_LAB_LABEL,
                None,
                true,
                LAB_NO_EXTRA_CAPABILITIES,
            )
            .with_secret_env_sources(LAB_AGENT_TASK_SECRET_ENV_SOURCES),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Fanout(agent_task::AgentTaskFanoutArgs {
                        command: agent_task::AgentTaskFanoutCommand::SubmitBatch(_),
                    }),
            }) => LabCommandContract::explicit_runner_simple(
                AGENT_TASK_FANOUT_SUBMIT_BATCH_LAB_LABEL,
            )
            .with_secret_env_sources(LAB_AGENT_TASK_SECRET_ENV_SOURCES),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Fanout(agent_task::AgentTaskFanoutArgs {
                        command:
                            agent_task::AgentTaskFanoutCommand::Status(_)
                            | agent_task::AgentTaskFanoutCommand::Artifacts(_),
                    }),
            }) => LabCommandContract::runner_resident_read_polling(
                AGENT_TASK_FANOUT_STATUS_LAB_LABEL,
            ),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Controller(agent_task::AgentTaskControllerArgs {
                        command:
                            agent_task::AgentTaskControllerCommand::FromSpec(
                                agent_task::AgentTaskControllerFromSpecArgs {
                                    resume: true, ..
                                },
                            )
                            | agent_task::AgentTaskControllerCommand::RunFromSpec(_)
                            | agent_task::AgentTaskControllerCommand::Materialize(_),
                    }),
            }) => LabCommandContract::portable(
                AGENT_TASK_CONTROLLER_FROM_SPEC_LAB_LABEL,
                None,
                false,
                LAB_NO_EXTRA_CAPABILITIES,
            )
            .with_secret_env_sources(LAB_AGENT_TASK_SECRET_ENV_SOURCES),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Controller(agent_task::AgentTaskControllerArgs {
                        command: agent_task::AgentTaskControllerCommand::Resume(_),
                    }),
            }) => LabCommandContract::runner_resident(AGENT_TASK_CONTROLLER_RESUME_LAB_LABEL),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Run(_)
                    | agent_task::AgentTaskCommand::RunNext,
            }) => LabCommandContract::runner_resident(AGENT_TASK_STATUS_LAB_LABEL),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Status(_)
                    | agent_task::AgentTaskCommand::Logs(_)
                    | agent_task::AgentTaskCommand::Artifacts(_)
                    | agent_task::AgentTaskCommand::Evidence(_)
                    | agent_task::AgentTaskCommand::Review(_)
                    // Discovery commands (list/active/latest) are runner-resident
                    // reads too: a freshly-offloaded Lab run's durable record
                    // lives on the runner, so `--runner <id> agent-task
                    // list/active/latest` must resolve against that runner to
                    // make discovery consistent with status/logs/etc (#5681).
                    | agent_task::AgentTaskCommand::List(_)
                    | agent_task::AgentTaskCommand::Active(_)
                    | agent_task::AgentTaskCommand::Latest(_),
            }) => LabCommandContract::runner_resident_read_polling(AGENT_TASK_STATUS_LAB_LABEL),
            Commands::AgentTask(agent_task::AgentTaskArgs {
                command:
                    agent_task::AgentTaskCommand::Auth(agent_task::AgentTaskAuthArgs {
                        command: agent_task::AgentTaskAuthCommand::Status(_),
                    }),
            }) => LabCommandContract::explicit_runner_simple(AGENT_TASK_AUTH_STATUS_LAB_LABEL),
            Commands::Audit(args) => return CommandPortabilityContract::lab_optional(args.lab_contract()),
            Commands::Bench(args) => return args.portability_contract(),
            Commands::Fuzz(args) => return CommandPortabilityContract::lab_optional(args.lab_contract()),
            Commands::Extension(args) if args.is_update_command() => {
                LabCommandContract::explicit_runner_simple(args.update_command_label())
            }
            Commands::Runtime(args) if args.is_refresh_command() => {
                LabCommandContract::explicit_runner_simple(RUNTIME_REFRESH_LAB_LABEL)
            }
            Commands::Worktree(args) => {
                return CommandPortabilityContract::lab_optional(args.lab_contract());
            }
            Commands::Fleet(args) => {
                let contract = crate::commands::fleet::adapter(CommandOutputFileMode::None)
                    .lab_contract(args);
                return CommandPortabilityContract::lab_optional(contract);
            }
            Commands::Lint(args) => return args.portability_contract(),
            Commands::Review(args) => return CommandPortabilityContract::lab(args.lab_contract()),
            Commands::Refactor(args) if args.is_hot_resource_command() => {
                LabCommandContract::portable(
                    "refactor",
                    args.lab_offload_writes_local_state()
                        .then_some("--write/--commit"),
                    false,
                    LAB_NO_EXTRA_CAPABILITIES,
                )
            }
            Commands::Rig(args) => return args.portability_contract(),
            Commands::Test(args) => return CommandPortabilityContract::lab(args.lab_contract()),
            Commands::Trace(args) => return args.portability_contract(),
            Commands::Tunnel(args) => return args.portability_contract(),
            _ => return CommandPortabilityContract::none(),
        };
        CommandPortabilityContract::lab(contract)
    }
}

mod agent_task_checkout {
    use super::*;

    /// The loop-controller spec-materialization family always lays down a real git
    /// checkout of the target workspace so the controller can apply patch artifacts
    /// across actions. This holds regardless of dispatch backend, so it is decided
    /// purely from the parsed command shape (not provider resolution).
    pub(crate) fn agent_task_controller_materializes_worktree(
        command: &agent_task::AgentTaskCommand,
    ) -> bool {
        matches!(
            command,
            agent_task::AgentTaskCommand::Controller(agent_task::AgentTaskControllerArgs {
                command: agent_task::AgentTaskControllerCommand::FromSpec(
                    agent_task::AgentTaskControllerFromSpecArgs { resume: true, .. },
                ) | agent_task::AgentTaskControllerCommand::RunFromSpec(_)
                    | agent_task::AgentTaskControllerCommand::Materialize(_),
            })
        )
    }

    pub(crate) fn agent_task_provider_requires_cwd_git_checkout(
        command: &agent_task::AgentTaskCommand,
    ) -> bool {
        agent_task_provider_requires_cwd_git_checkout_with(
            command,
            || default_backend().ok().flatten(),
            provider_requires_cwd_git_checkout,
        )
    }

    pub(crate) fn agent_task_provider_requires_cwd_git_checkout_with(
        command: &agent_task::AgentTaskCommand,
        default_backend: impl FnOnce() -> Option<String>,
        provider_requires_cwd_git_checkout: impl Fn(&str, Option<&str>) -> bool,
    ) -> bool {
        match command {
            agent_task::AgentTaskCommand::Cook(agent_task::AgentTaskCookArgs {
                dispatch: args,
                ..
            }) => {
                let backend = args.backend.clone().or_else(default_backend);
                backend.as_ref().is_some_and(|backend| {
                    provider_requires_cwd_git_checkout(backend, args.selector.as_deref())
                }) || args
                    .backend
                    .as_ref()
                    .is_some_and(|backend| !backend.trim().is_empty())
            }
            agent_task::AgentTaskCommand::Controller(agent_task::AgentTaskControllerArgs {
                command:
                    agent_task::AgentTaskControllerCommand::FromSpec(
                        agent_task::AgentTaskControllerFromSpecArgs {
                            resume: true,
                            dispatch:
                                agent_task::AgentTaskControllerDispatchArgs {
                                    dispatch_backend,
                                    dispatch_selector,
                                    ..
                                },
                            ..
                        },
                    )
                    | agent_task::AgentTaskControllerCommand::RunFromSpec(
                        agent_task::AgentTaskControllerRunFromSpecArgs {
                            dispatch:
                                agent_task::AgentTaskControllerDispatchArgs {
                                    dispatch_backend,
                                    dispatch_selector,
                                    ..
                                },
                            ..
                        },
                    ),
            }) => {
                let backend = dispatch_backend.clone().or_else(default_backend);
                backend.as_ref().is_some_and(|backend| {
                    provider_requires_cwd_git_checkout(backend, dispatch_selector.as_deref())
                }) || dispatch_backend
                    .as_ref()
                    .is_some_and(|backend| !backend.trim().is_empty())
            }
            _ => false,
        }
    }
}
pub(crate) use agent_task_checkout::*;

pub(super) fn apply_lab_contract_to_descriptor(
    descriptor: &mut CommandDescriptor,
    contract: Option<LabCommandContract>,
) {
    descriptor.supports_lab_runner = contract
        .is_some_and(|contract| matches!(contract.portability, LabCommandPortability::Portable));
    descriptor.lab_runner_unsupported_reason =
        contract.and_then(|contract| match contract.portability {
            LabCommandPortability::Portable => None,
            LabCommandPortability::LocalOnly(reason) => Some(reason),
        });
    descriptor.lab_offload_captures_mutation_patch =
        contract.is_some_and(|contract| contract.capture_mutation_patch);
    descriptor.lab_offload_mutation_flag = contract.and_then(|contract| contract.mutation_flag);
}

impl LabCommandContract {
    pub(crate) fn into_route_contract(
        self,
        required_extensions: Vec<String>,
    ) -> LabCommandRouteContract {
        let required_capabilities = self
            .extra_required_capabilities
            .iter()
            .map(|capability| RunnerWorkloadCapability {
                name: (*capability).to_string(),
                required: true,
            })
            .collect();
        LabCommandRouteContract {
            command: self,
            required_extensions,
            required_capabilities,
        }
    }

    pub(crate) fn portable(
        hot_label: &'static str,
        mutation_flag: Option<&'static str>,
        requires_extension_parity: bool,
        extra_required_capabilities: &'static [&'static str],
    ) -> Self {
        Self {
            hot_label,
            portability: LabCommandPortability::Portable,
            source_path_mode: LabSourcePathMode::CwdOrPathFlag,
            workspace_mode_policy: LabWorkspaceModePolicy::ChangedSinceGitElseSnapshot,
            capture_mutation_patch: mutation_flag.is_some(),
            mutation_flag,
            extra_required_capabilities,
            secret_env_sources: LAB_NO_SECRET_ENV_SOURCES,
            routing_policy: LabRoutingPolicy {
                default_lab_offload: true,
                infer_source_path_tools: true,
                release_gate: false,
                requires_extension_parity,
                read_only_polling: false,
            },
        }
    }

    pub(crate) fn portable_workload(
        hot_label: &'static str,
        mutation_flag: Option<&'static str>,
        requires_extension_parity: bool,
        extra_required_capabilities: &'static [&'static str],
    ) -> Self {
        let base = Self::portable(
            hot_label,
            mutation_flag,
            requires_extension_parity,
            extra_required_capabilities,
        );
        Self {
            routing_policy: LabRoutingPolicy {
                infer_source_path_tools: false,
                ..base.routing_policy
            },
            ..base
        }
    }

    pub(crate) fn explicit_runner(
        hot_label: &'static str,
        mutation_flag: Option<&'static str>,
        requires_extension_parity: bool,
        extra_required_capabilities: &'static [&'static str],
    ) -> Self {
        let base = Self::portable_workload(
            hot_label,
            mutation_flag,
            requires_extension_parity,
            extra_required_capabilities,
        );
        Self {
            routing_policy: LabRoutingPolicy {
                default_lab_offload: false,
                ..base.routing_policy
            },
            ..base
        }
    }

    /// Explicit-runner contract for the common case of a command that takes no
    /// mutation flag, requires no extension parity, and pulls in no extra tools.
    /// Collapses the repeated `explicit_runner(label, None, false,
    /// LAB_NO_EXTRA_CAPABILITIES)` call shape that several agent-task and tunnel arms
    /// share down to a single label argument.
    pub(crate) fn explicit_runner_simple(hot_label: &'static str) -> Self {
        Self::explicit_runner(hot_label, None, false, LAB_NO_EXTRA_CAPABILITIES)
    }

    /// Explicit-runner contract pinned to a runner-resident workspace: the
    /// command operates against state that already lives on the runner, so both
    /// the source-path and workspace-mode policies are runner-resident. Used by
    /// the agent-task status/resume and tunnel service-lifecycle commands, none
    /// of which take a mutation flag, extension parity, or extra tools.
    pub(crate) fn runner_resident(hot_label: &'static str) -> Self {
        Self {
            source_path_mode: LabSourcePathMode::RunnerResident,
            workspace_mode_policy: LabWorkspaceModePolicy::RunnerResident,
            ..Self::explicit_runner_simple(hot_label)
        }
    }

    pub(crate) fn runner_resident_read_polling(hot_label: &'static str) -> Self {
        let base = Self::runner_resident(hot_label);
        Self {
            routing_policy: LabRoutingPolicy {
                read_only_polling: true,
                ..base.routing_policy
            },
            ..base
        }
    }

    pub(crate) fn local_only(hot_label: &'static str, reason: &'static str) -> Self {
        Self {
            hot_label,
            portability: LabCommandPortability::LocalOnly(reason),
            source_path_mode: LabSourcePathMode::CwdOrPathFlag,
            workspace_mode_policy: LabWorkspaceModePolicy::ChangedSinceGitElseSnapshot,
            capture_mutation_patch: false,
            mutation_flag: None,
            extra_required_capabilities: LAB_NO_EXTRA_CAPABILITIES,
            secret_env_sources: LAB_NO_SECRET_ENV_SOURCES,
            routing_policy: LabRoutingPolicy {
                default_lab_offload: false,
                infer_source_path_tools: false,
                release_gate: false,
                requires_extension_parity: false,
                read_only_polling: false,
            },
        }
    }

    /// Mark this contract as a release gate (lint/test/audit) so that the
    /// `/release_gate/local_hot` policy applies to force-local bypass and
    /// stale-runner local fallback when a default Lab runner is configured.
    pub(crate) const fn release_gate(mut self) -> Self {
        self.routing_policy.release_gate = true;
        self
    }

    pub(crate) const fn with_secret_env_sources(
        mut self,
        sources: &'static [LabSecretEnvSource],
    ) -> Self {
        self.secret_env_sources = sources;
        self
    }
}

impl RunnerWorkloadCommandFamily {
    pub(crate) fn from_command_label(label: &str) -> Self {
        match label {
            label if label.starts_with("agent-task") => Self::AgentTask,
            LINT_LAB_LABEL | TEST_LAB_LABEL | AUDIT_LAB_LABEL | REVIEW_LAB_LABEL
            | BENCH_LAB_LABEL | FUZZ_LAB_LABEL | TRACE_LAB_LABEL | RIG_RUN_LAB_LABEL => {
                Self::Quality
            }
            REFACTOR_LAB_LABEL | RIG_CHECK_LAB_LABEL | RUNTIME_REFRESH_LAB_LABEL => Self::Workspace,
            label if label.starts_with("tunnel") => Self::Service,
            _ => Self::Unknown,
        }
    }
}

impl Commands {
    pub fn lab_route_contract(&self) -> crate::core::Result<Option<LabCommandRouteContract>> {
        let Some(contract) = self.lab_contract() else {
            return Ok(None);
        };
        let required_extensions = self.lab_required_extensions()?;
        Ok(Some(contract.into_route_contract(required_extensions)))
    }

    pub fn supports_lab_runner(&self) -> bool {
        self.lab_contract()
            .is_some_and(|contract| matches!(contract.portability, LabCommandPortability::Portable))
    }

    pub fn lab_runner_unsupported_reason(&self) -> Option<&'static str> {
        self.lab_contract()
            .and_then(|contract| match contract.portability {
                LabCommandPortability::Portable => None,
                LabCommandPortability::LocalOnly(reason) => Some(reason),
            })
    }

    pub fn lab_offload_mutation_flag(&self) -> Option<&'static str> {
        self.lab_contract()
            .and_then(|contract| contract.mutation_flag)
    }

    pub fn lab_offload_captures_mutation_patch(&self) -> bool {
        self.lab_contract()
            .is_some_and(|contract| contract.capture_mutation_patch)
    }

    pub fn lab_required_extensions(&self) -> crate::core::Result<Vec<String>> {
        let Some(contract) = self.lab_contract() else {
            return Ok(Vec::new());
        };
        if !contract.routing_policy.requires_extension_parity {
            return Ok(Vec::new());
        }

        let mut extension_ids = BTreeSet::new();
        match self {
            Commands::Audit(args) => {
                extension_ids.extend(args.extension_override.extensions.clone())
            }
            Commands::Bench(args) => extension_ids.extend(args.lab_required_extension_ids()?),
            Commands::Fuzz(args) => {
                extension_ids.extend(args.extension_override_ids().iter().cloned())
            }
            Commands::Lint(args) => {
                extension_ids.extend(args.extension_override.extensions.clone())
            }
            Commands::Review(args) => {
                extension_ids.extend(args.extension_override.extensions.clone());
                extension_ids.extend(review_lab_extension_ids(args)?);
            }
            Commands::Test(args) => {
                extension_ids.extend(args.extension_override.extensions.clone());
                extension_ids.extend(test_lab_extension_ids(args)?);
            }
            Commands::AgentTask(args) => extension_ids.extend(agent_task_lab_extension_ids(args)?),
            _ => {}
        }

        Ok(extension_ids.into_iter().collect())
    }
}

mod extension_ids {
    use super::*;

    pub(crate) fn agent_task_lab_extension_ids(
        args: &agent_task::AgentTaskArgs,
    ) -> crate::core::Result<Vec<String>> {
        let agent_task::AgentTaskCommand::RunPlan(run_plan) = &args.command else {
            return Ok(Vec::new());
        };
        if run_plan.plan.trim() == "-" {
            return Ok(Vec::new());
        }

        let plan = crate::core::agent_tasks::service::read_plan(&run_plan.plan)?;
        Ok(crate::core::agent_tasks::required_extension_ids_for_plan(
            &plan,
        ))
    }

    pub(crate) fn test_lab_extension_ids(
        args: &crate::commands::test::TestArgs,
    ) -> crate::core::Result<Vec<String>> {
        let resolve_for = |capability: Option<ExtensionCapability>| {
            execution_context::resolve(&ResolveOptions {
                component_id: args.comp.component.clone(),
                path_override: args.comp.path.clone(),
                capability,
                settings_profile_json_overrides: args
                    .setting_args
                    .settings_profile_json_overrides()?,
                settings_overrides: args.setting_args.settings_overrides()?,
                settings_json_overrides: args.setting_args.settings_json_overrides()?,
                extension_overrides: args.extension_override.extensions.clone(),
            })
        };

        let source_context = resolve_for(None)?;

        if !args.drift
            && args.ci_job.is_none()
            && source_context
                .component
                .has_script(ExtensionCapability::Test)
        {
            return Ok(Vec::new());
        }

        let context = resolve_for(Some(ExtensionCapability::Test))?;

        Ok(context.extension_id.into_iter().collect())
    }

    pub(crate) fn review_lab_extension_ids(
        args: &crate::commands::review::ReviewArgs,
    ) -> crate::core::Result<Vec<String>> {
        let resolve_for = |capability: Option<ExtensionCapability>| {
            execution_context::resolve(&ResolveOptions {
                component_id: args.comp.component.clone(),
                path_override: args.comp.path.clone(),
                capability,
                settings_overrides: Vec::new(),
                settings_profile_json_overrides: Vec::new(),
                settings_json_overrides: Vec::new(),
                extension_overrides: args.extension_override.extensions.clone(),
            })
        };

        let source_context = resolve_for(None)?;

        if source_context
            .component
            .has_script(ExtensionCapability::Test)
        {
            return Ok(Vec::new());
        }

        let context = resolve_for(Some(ExtensionCapability::Test))?;

        Ok(context.extension_id.into_iter().collect())
    }
}

#[cfg(test)]
mod low_noise_polling_tests {
    use super::*;
    use crate::cli_surface::Cli;
    use clap::Parser;

    fn parsed_command(args: &[&str]) -> Commands {
        Cli::try_parse_from(args)
            .expect("CLI args should parse")
            .command
    }

    #[test]
    fn runner_resident_agent_task_reads_are_low_noise_polling() {
        for args in [
            ["homeboy", "agent-task", "status", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "logs", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "artifacts", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "list"].as_slice(),
            ["homeboy", "agent-task", "active"].as_slice(),
            ["homeboy", "agent-task", "latest"].as_slice(),
        ] {
            let contract = parsed_command(args)
                .lab_contract()
                .expect("runner-resident read polling contract");
            assert_eq!(contract.source_path_mode, LabSourcePathMode::RunnerResident);
            assert_eq!(
                contract.workspace_mode_policy,
                LabWorkspaceModePolicy::RunnerResident
            );
            assert!(contract.routing_policy.read_only_polling);
        }
    }

    #[test]
    fn runner_resident_agent_task_execution_keeps_full_runner_evidence() {
        for args in [
            ["homeboy", "agent-task", "run", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "run-next"].as_slice(),
            ["homeboy", "agent-task", "controller", "resume", "loop-123"].as_slice(),
        ] {
            let contract = parsed_command(args)
                .lab_contract()
                .expect("runner-resident execution contract");
            assert_eq!(contract.source_path_mode, LabSourcePathMode::RunnerResident);
            assert!(!contract.routing_policy.read_only_polling);
        }
    }

    #[test]
    fn agent_task_cook_finalization_is_controller_owned() {
        let command = parsed_command(&[
            "homeboy",
            "agent-task",
            "cook",
            "--prompt",
            "make a change",
            "--to-worktree",
            "homeboy@cook-finalization",
            "--verify",
            "cargo test --lib",
        ]);

        let contract = command
            .lab_contract()
            .expect("cook contract should explain controller-owned finalization");

        assert_eq!(
            contract.portability,
            LabCommandPortability::LocalOnly(AGENT_TASK_COOK_FINALIZATION_CONTROLLER_REASON)
        );
    }

    #[test]
    fn agent_task_cook_no_finalize_remains_lab_portable() {
        let command = parsed_command(&[
            "homeboy",
            "agent-task",
            "cook",
            "--prompt",
            "make a change",
            "--to-worktree",
            "homeboy@cook-finalization",
            "--verify",
            "cargo test --lib",
            "--no-finalize",
        ]);

        let contract = command
            .lab_contract()
            .expect("no-finalize cook should remain portable");

        assert_eq!(contract.portability, LabCommandPortability::Portable);
    }
}
pub(crate) use extension_ids::*;
