import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { createConnection, type Socket } from "node:net";
import {
  type CoreEgressTcpCloseMessage,
  type CoreEgressTcpDataMessage,
  type CoreEgressTcpOpenedMessage,
  type CoreDispatchMessage,
  type CoreHttpTunnelRequestMessage,
  type CoreHostSshOpenMessage,
  type DockerContainerSummary,
  type DockerImageSummary,
  type DockerNetworkSummary,
  type DockerStatusSnapshot,
  type DockerVolumeSummary,
  type JsonValue,
  type ProcessResourceSummary,
  type ProviderLabels,
  type ProviderTaskStatusMessage,
  type SystemStatusSnapshot,
  parseJsonObject,
} from "../../shared/src/index";
import { createHourlyJsonlWriter, logRetentionBytesForService } from "../../shared/src/rotating-jsonl";
import { startProviderEgressProxy, type ProviderEgressProxyHandle } from "./egress-proxy";

interface RuntimeConfig {
  serverUrl: string;
  token: string;
  providerId: string;
  providerName: string;
  labels: ProviderLabels;
  heartbeatIntervalMs: number;
  reconnectBaseMs: number;
  reconnectMaxMs: number;
  dockerSocketPath: string;
  monitorDiskPath: string;
  upgradeHostProjectRoot: string;
  upgradeWorkspacePath: string;
  upgradeComposeFile: string;
  upgradeEnvFile: string;
  upgradeComposeProject: string;
  upgradeService: string;
  upgradeRunnerImage: string;
  hostSshHost: string | null;
  hostSshPort: number | null;
  hostSshUser: string | null;
  hostSshKey: string | null;
  hostRemoteCwd: string | null;
  hostLoginShell: string | null;
  providerDataHost: string;
  providerDataPort: number;
  providerDataPoolSize: number;
  providerDataConnectTimeoutMs: number;
  egressProxyEnabled: boolean;
  egressProxyListenHost: string;
  egressProxyPort: number;
  egressProxyOpenTimeoutMs: number;
  egressProxyIdleTimeoutMs: number;
  egressProxyMaxTunnelAgeMs: number;
  egressProxyStaleTunnelIdleMs: number;
  egressProxyMaxPendingBytes: number;
  logFile: string;
}

const startedAt = new Date();
const gatewayMetadata = readGatewayMetadata();
const defaultMasterServer = "http://152.53.229.148/";
const defaultProviderToken = "unidesk-dev-token-change-me";
const microserviceHttpMaxBodyTextLength = 8 * 1024 * 1024;
const sshDataProtocol = "unidesk-host-ssh-tcp-pool-v1";
const sshDataMaxHeaderBytes = 64 * 1024;
const sshDataMaxPayloadBytes = 16 * 1024 * 1024;
const config = readConfig();
const logger = createLogger("provider-gateway", config.logFile);
let socket: WebSocket | null = null;
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
let dockerStatusTimer: ReturnType<typeof setInterval> | null = null;
let systemStatusTimer: ReturnType<typeof setInterval> | null = null;
let dockerStatusRunning = false;
let systemStatusRunning = false;
let previousCpuSample: { idle: number; total: number } | null = null;
let previousProcessSamples = new Map<number, { totalTicks: number; sampledAtMs: number; readBytes: number; writeBytes: number }>();
let passwdCache: Map<number, string> | null = null;
let clockTicksCache: number | null = null;
let pageSizeCache: number | null = null;
let reconnectAttempt = 0;
let stopping = false;
let upgradeSleepUntil = 0;
let upgradeSleepTimer: ReturnType<typeof setTimeout> | null = null;
let selfContainerGuard: ProviderGatewayContainerGuardState | null = null;
let selfContainerGuardLastCheckMs = 0;
const reportedProviderUpgradeCompletionTasks = new Set<string>();

interface MicroserviceHttpCacheEntry {
  createdAt: number;
  expiresAt: number;
  result: JsonValue;
}

interface HostSshSession {
  proc: ReturnType<typeof Bun.spawn>;
  openedAt: number;
  tty: boolean;
  dataChannelId?: string;
  commandBytes: number;
  commandDigest: string | null;
  cwd: string | null;
}

interface PendingSshDataFrame {
  header: Record<string, unknown>;
  payload: Buffer;
  queuedAt: number;
}

type CompletedHostSshSessionReason = "exit" | "error" | "client-close";

interface CompletedHostSshSession {
  dataChannelId: string;
  closedAt: number;
  reason: CompletedHostSshSessionReason;
  exitCode: number | null;
}

interface SshDataChannel {
  id: string;
  socket: Socket;
  status: "connecting" | "ready" | "claimed" | "closed";
  buffer: Buffer;
  pendingFrames: PendingSshDataFrame[];
  openedAt: number;
  lastReadyAt: number | null;
  activeSessionId: string | null;
  bytesIn: number;
  bytesOut: number;
  lastError: string | null;
}

interface HostSshStdin {
  write(chunk: Uint8Array): unknown;
  end(): unknown;
}

interface ProviderGatewayContainerGuardState {
  checkedAt: string;
  containerId: string;
  containerName: string;
  restartPolicy: string;
  restartPolicyMaximumRetryCount: number;
  restartPolicyOk: boolean;
  pidMode: string;
  pidModeOk: boolean;
  remediatedRestartPolicy: boolean;
  ok: boolean;
  error: string;
}

interface DockerContainerInspectDetails {
  id: string;
  name: string;
  restartPolicy: string;
  restartPolicyMaximumRetryCount: number;
  pidMode: string;
  composeProject: string;
  composeService: string;
  labels: Record<string, string>;
}

const hostSshSessions = new Map<string, HostSshSession>();
const completedHostSshSessions = new Map<string, CompletedHostSshSession>();
const microserviceHttpCache = new Map<string, MicroserviceHttpCacheEntry>();
const microserviceHttpInFlight = new Map<string, Promise<JsonValue>>();
let providerEgressProxy: ProviderEgressProxyHandle | null = null;
const sshDataChannels = new Map<string, SshDataChannel>();
let sshDataPoolDesired = false;
let sshDataChannelSeq = 0;
let sshDataLastError: string | null = null;
const sshDataPendingSessionFrameMaxCount = 64;
const sshDataPendingSessionFrameMaxBytes = sshDataMaxPayloadBytes;
const completedHostSshSessionTtlMs = 60_000;
const completedHostSshSessionMaxCount = 512;
const microserviceForwardRequestHeaders = [
  "accept",
  "content-type",
  "cookie",
  "range",
  "x-auth",
  "x-requested-with",
  "destination",
  "overwrite",
  "tus-resumable",
  "upload-concat",
  "upload-defer-length",
  "upload-length",
  "upload-metadata",
  "upload-offset",
] as const;
const microserviceForwardResponseHeaders = [
  "cache-control",
  "content-disposition",
  "etag",
  "expires",
  "last-modified",
  "location",
  "set-cookie",
  "www-authenticate",
] as const;

function readGatewayMetadataFile(path: string): { name: string; version: string } | null {
  try {
    const raw = readFileSync(path, "utf8");
    const parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };
    return {
      name: typeof parsed.name === "string" && parsed.name.length > 0 ? parsed.name : "@unidesk/provider-gateway",
      version: typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "0.0.0-unknown",
    };
  } catch {
    return null;
  }
}

function readGatewayMetadata(): { name: string; version: string } {
  return readGatewayMetadataFile(new URL("../package.json", import.meta.url).pathname)
    ?? { name: "@unidesk/provider-gateway", version: "0.0.0-unknown" };
}

function readTargetGatewayMetadata(workspace: string): { name: string; version: string } {
  const root = workspace.replace(/\/+$/, "");
  return readGatewayMetadataFile(`${root}/src/components/provider-gateway/package.json`) ?? gatewayMetadata;
}

function readEnv(...names: string[]): string | null {
  for (const name of names) {
    const value = process.env[name];
    if (value !== undefined && value.length > 0) return value;
  }
  return null;
}

function requiredEnv(name: string, ...aliases: string[]): string {
  const value = readEnv(name, ...aliases);
  if (value === null) {
    throw new Error(`Missing required environment variable: ${[name, ...aliases].join(" or ")}`);
  }
  return value;
}

function readNumberEnv(name: string, fallback: number, ...aliases: string[]): number {
  const raw = readEnv(name, ...aliases) ?? String(fallback);
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new Error(`Environment variable ${name} must be a positive number, got ${raw}`);
  }
  return parsed;
}

function readOptionalStringEnv(name: string, ...aliases: string[]): string | null {
  return readEnv(name, ...aliases);
}

function readOptionalNumberEnv(name: string, ...aliases: string[]): number | null {
  const raw = readOptionalStringEnv(name, ...aliases);
  if (raw === null) return null;
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new Error(`Environment variable ${name} must be a positive number, got ${raw}`);
  }
  return parsed;
}

function readBooleanEnv(name: string, fallback: boolean, ...aliases: string[]): boolean {
  const raw = readEnv(name, ...aliases);
  if (raw === null || raw.trim().length === 0) return fallback;
  const value = raw.trim().toLowerCase();
  if (value === "1" || value === "true" || value === "yes" || value === "on") return true;
  if (value === "0" || value === "false" || value === "no" || value === "off") return false;
  return fallback;
}

function providerServerUrlFromMaster(rawMaster: string): string {
  const raw = rawMaster.trim() || defaultMasterServer;
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//iu.test(raw) ? raw : `http://${raw}`;
  const url = new URL(withScheme);
  if (url.protocol === "ws:" || url.protocol === "wss:") {
    if (url.pathname === "" || url.pathname === "/") url.pathname = "/ws/provider";
    return url.toString();
  }
  const providerIngressPort = readEnv("PROVIDER_INGRESS_PORT", "UNIDESK_PROVIDER_INGRESS_PORT")
    ?? (url.port && url.port !== "18081" ? url.port : "18082");
  const protocol = url.protocol === "https:" ? "wss:" : "ws:";
  const host = url.hostname.includes(":") ? `[${url.hostname}]` : url.hostname;
  const providerUrl = new URL(`${protocol}//${host}:${providerIngressPort}/ws/provider`);
  return providerUrl.toString();
}

function readProviderServerUrl(): string {
  const explicit = readEnv("PROVIDER_SERVER_URL", "UNIDESK_PROVIDER_SERVER_URL");
  if (explicit !== null) return explicit;
  return providerServerUrlFromMaster(readEnv("UNIDESK_MASTER_SERVER", "UNIDESK_MASTER_SERVER_URL", "MASTER_SERVER_URL", "MASTER_SERVER_IP") ?? defaultMasterServer);
}

function deriveProviderDataEndpoint(serverUrl: string): { host: string; port: number } {
  const explicitHost = readOptionalStringEnv("PROVIDER_DATA_HOST", "UNIDESK_PROVIDER_DATA_HOST");
  const explicitPort = readOptionalNumberEnv("PROVIDER_DATA_PORT", "UNIDESK_PROVIDER_DATA_PORT");
  const url = new URL(serverUrl);
  const host = explicitHost ?? url.hostname;
  if (explicitPort !== null) return { host, port: explicitPort };
  const controlPort = Number(url.port || (url.protocol === "wss:" ? 443 : 80));
  if (controlPort === 8081) return { host, port: 8082 };
  if (controlPort === 18082) return { host, port: 18084 };
  return { host, port: controlPort + 2 };
}

function readProviderId(): string {
  return requiredEnv("PROVIDER_ID", "UNIDESK_PROVIDER_ID");
}

function safeProviderSlug(value: string): string {
  return value.replace(/[^a-zA-Z0-9_.-]/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "provider";
}

function safeReadText(path: string): string {
  try {
    return readFileSync(path, "utf8");
  } catch {
    return "";
  }
}

function defaultProviderLabels(providerId: string): ProviderLabels {
  const labels: ProviderLabels = {
    host: providerId,
    role: "compute-provider",
    docker: true,
    attachMode: "simple",
  };
  const kernel = safeReadText("/proc/sys/kernel/osrelease").toLowerCase();
  if (kernel.includes("microsoft") || kernel.includes("wsl") || process.env.WSL_INTEROP !== undefined) {
    labels.role = "wsl-provider";
    labels.wsl = true;
  }
  const osRelease = safeReadText("/etc/os-release");
  const distro = osRelease.match(/^PRETTY_NAME="?([^"\n]+)"?/m)?.[1] ?? osRelease.match(/^ID="?([^"\n]+)"?/m)?.[1] ?? "";
  if (distro.length > 0) labels.distro = distro;
  return labels;
}

function readProviderLabels(providerId: string): ProviderLabels {
  const raw = readEnv("PROVIDER_LABELS_JSON", "UNIDESK_PROVIDER_LABELS_JSON");
  return raw === null ? defaultProviderLabels(providerId) : parseJsonObject(raw, "PROVIDER_LABELS_JSON");
}

function goTemplateString(value: string): string {
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
}

function currentContainerId(): string | null {
  const hostname = (process.env.HOSTNAME ?? safeReadText("/etc/hostname")).trim();
  return hostname.length > 0 ? hostname : null;
}

function unknownContainerGuardState(error: string): ProviderGatewayContainerGuardState {
  return {
    checkedAt: new Date().toISOString(),
    containerId: currentContainerId() ?? "",
    containerName: "",
    restartPolicy: "",
    restartPolicyMaximumRetryCount: 0,
    restartPolicyOk: false,
    pidMode: "",
    pidModeOk: false,
    remediatedRestartPolicy: false,
    ok: false,
    error,
  };
}

function stringRecordFromUnknown(value: unknown): Record<string, string> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
  const output: Record<string, string> = {};
  for (const [key, raw] of Object.entries(value)) {
    if (typeof raw === "string") output[key] = raw;
  }
  return output;
}

function dockerInspectDetails(value: unknown): DockerContainerInspectDetails | null {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
  const row = value as Record<string, unknown>;
  const hostConfig = typeof row.HostConfig === "object" && row.HostConfig !== null && !Array.isArray(row.HostConfig)
    ? row.HostConfig as Record<string, unknown>
    : {};
  const configRow = typeof row.Config === "object" && row.Config !== null && !Array.isArray(row.Config)
    ? row.Config as Record<string, unknown>
    : {};
  const restartPolicy = typeof hostConfig.RestartPolicy === "object" && hostConfig.RestartPolicy !== null && !Array.isArray(hostConfig.RestartPolicy)
    ? hostConfig.RestartPolicy as Record<string, unknown>
    : {};
  const labels = stringRecordFromUnknown(configRow.Labels);
  const rawName = typeof row.Name === "string" ? row.Name : "";
  const rawMaxRetry = restartPolicy.MaximumRetryCount;
  return {
    id: typeof row.Id === "string" ? row.Id : "",
    name: rawName.replace(/^\/+/, ""),
    restartPolicy: typeof restartPolicy.Name === "string" ? restartPolicy.Name : "",
    restartPolicyMaximumRetryCount: typeof rawMaxRetry === "number" && Number.isFinite(rawMaxRetry) ? rawMaxRetry : 0,
    pidMode: typeof hostConfig.PidMode === "string" ? hostConfig.PidMode : "",
    composeProject: labels["com.docker.compose.project"] ?? "",
    composeService: labels["com.docker.compose.service"] ?? "",
    labels,
  };
}

function inspectCurrentContainerDetails(containerId: string): DockerContainerInspectDetails | null {
  const result = spawnSync("docker", ["inspect", "--format", "{{json .}}", containerId], {
    encoding: "utf8",
    timeout: 2500,
    maxBuffer: 1024 * 1024,
  });
  if (result.status !== 0) return null;
  try {
    return dockerInspectDetails(JSON.parse(result.stdout.trim()) as unknown);
  } catch {
    return null;
  }
}

function containerGuardStateFromDetails(details: DockerContainerInspectDetails, remediatedRestartPolicy: boolean, error = ""): ProviderGatewayContainerGuardState {
  const restartPolicyOk = details.restartPolicy === "always";
  const pidModeOk = details.pidMode === "host";
  return {
    checkedAt: new Date().toISOString(),
    containerId: details.id.slice(0, 12),
    containerName: details.name,
    restartPolicy: details.restartPolicy,
    restartPolicyMaximumRetryCount: details.restartPolicyMaximumRetryCount,
    restartPolicyOk,
    pidMode: details.pidMode,
    pidModeOk,
    remediatedRestartPolicy,
    ok: restartPolicyOk && pidModeOk && error.length === 0,
    error,
  };
}

function refreshSelfContainerGuard(force = false): ProviderGatewayContainerGuardState {
  const now = Date.now();
  if (!force && selfContainerGuard !== null && now - selfContainerGuardLastCheckMs < 60_000) return selfContainerGuard;
  selfContainerGuardLastCheckMs = now;
  const containerId = currentContainerId();
  if (containerId === null) {
    selfContainerGuard = unknownContainerGuardState("unable to determine current container id from hostname");
    return selfContainerGuard;
  }
  if (!existsSync(config.dockerSocketPath)) {
    selfContainerGuard = unknownContainerGuardState(`docker socket is not mounted at ${config.dockerSocketPath}`);
    return selfContainerGuard;
  }

  let details = inspectCurrentContainerDetails(containerId);
  if (details === null) {
    selfContainerGuard = unknownContainerGuardState("docker inspect of current provider-gateway container failed");
    return selfContainerGuard;
  }

  let remediatedRestartPolicy = false;
  let error = "";
  if (details.restartPolicy !== "always") {
    const updateResult = spawnSync("docker", ["update", "--restart", "always", containerId], {
      encoding: "utf8",
      timeout: 2500,
      maxBuffer: 64 * 1024,
    });
    if (updateResult.status === 0) {
      remediatedRestartPolicy = true;
      details = inspectCurrentContainerDetails(containerId) ?? details;
    } else {
      error = `docker update --restart always failed: ${(updateResult.stderr || updateResult.stdout || "").slice(0, 500)}`;
    }
  }

  selfContainerGuard = containerGuardStateFromDetails(details, remediatedRestartPolicy, error);
  if (remediatedRestartPolicy) {
    logger("warn", "provider_gateway_restart_policy_remediated", selfContainerGuard as unknown as JsonValue);
  } else if (!selfContainerGuard.restartPolicyOk) {
    logger("error", "provider_gateway_restart_policy_guard_failed", selfContainerGuard as unknown as JsonValue);
  }
  if (!selfContainerGuard.pidModeOk) {
    logger("error", "provider_gateway_pid_namespace_guard_failed", selfContainerGuard as unknown as JsonValue);
  }
  return selfContainerGuard;
}

function discoverDockerMountSource(destination: string): string | null {
  const containerId = currentContainerId();
  if (containerId === null) return null;
  const template = `{{range .Mounts}}{{if eq .Destination ${goTemplateString(destination)}}{{println .Source}}{{end}}{{end}}`;
  try {
    const result = spawnSync("docker", ["inspect", "--format", template, containerId], {
      encoding: "utf8",
      timeout: 2000,
      maxBuffer: 8192,
    });
    if (result.status !== 0) return null;
    const source = (result.stdout ?? "").split("\n").map((line) => line.trim()).find((line) => line.length > 0);
    return source ?? null;
  } catch {
    return null;
  }
}

function inferHostSshUser(hostProjectRoot: string): string | null {
  const homeMatch = hostProjectRoot.match(/^\/home\/([^/]+)/u);
  if (homeMatch?.[1]) return homeMatch[1];
  if (hostProjectRoot === "/root" || hostProjectRoot.startsWith("/root/")) return "root";
  return null;
}

function defaultHostRemoteCwd(user: string | null): string | null {
  if (user === null) return null;
  return user === "root" ? "/root" : `/home/${user}`;
}

function readConfig(): RuntimeConfig {
  const providerId = readProviderId();
  const serverUrl = readProviderServerUrl();
  const providerDataEndpoint = deriveProviderDataEndpoint(serverUrl);
  const workspacePath = readOptionalStringEnv("PROVIDER_UPGRADE_WORKSPACE_PATH") ?? "/workspace";
  const hostProjectRoot = readOptionalStringEnv("PROVIDER_UPGRADE_HOST_PROJECT_ROOT")
    ?? discoverDockerMountSource(workspacePath)
    ?? workspacePath;
  const inferredSshUser = inferHostSshUser(hostProjectRoot);
  const defaultHostSshKey = "/run/host-ssh/id_ed25519";
  const mountedHostSshKey = existsSync(defaultHostSshKey);
  const hostSshUser = readOptionalStringEnv("HOST_SSH_USER") ?? (mountedHostSshKey ? inferredSshUser : null);
  return {
    serverUrl,
    token: readEnv("PROVIDER_TOKEN", "UNIDESK_PROVIDER_TOKEN") ?? defaultProviderToken,
    providerId,
    providerName: readEnv("PROVIDER_NAME", "UNIDESK_PROVIDER_NAME") ?? providerId,
    labels: readProviderLabels(providerId),
    heartbeatIntervalMs: readNumberEnv("HEARTBEAT_INTERVAL_MS", 15000, "UNIDESK_HEARTBEAT_INTERVAL_MS"),
    reconnectBaseMs: readNumberEnv("RECONNECT_BASE_MS", 1000, "UNIDESK_RECONNECT_BASE_MS"),
    reconnectMaxMs: readNumberEnv("RECONNECT_MAX_MS", 30000, "UNIDESK_RECONNECT_MAX_MS"),
    dockerSocketPath: readOptionalStringEnv("DOCKER_SOCKET_PATH") ?? "/var/run/docker.sock",
    monitorDiskPath: readOptionalStringEnv("MONITOR_DISK_PATH", "UNIDESK_MONITOR_DISK_PATH") ?? "/",
    upgradeHostProjectRoot: hostProjectRoot,
    upgradeWorkspacePath: workspacePath,
    upgradeComposeFile: readOptionalStringEnv("PROVIDER_UPGRADE_COMPOSE_FILE") ?? `provider-${providerId}.yml`,
    upgradeEnvFile: readOptionalStringEnv("PROVIDER_UPGRADE_ENV_FILE") ?? `.state/provider-${providerId}.env`,
    upgradeComposeProject: readOptionalStringEnv("PROVIDER_UPGRADE_COMPOSE_PROJECT") ?? `unidesk-${safeProviderSlug(providerId)}`,
    upgradeService: readOptionalStringEnv("PROVIDER_UPGRADE_SERVICE") ?? "provider-gateway",
    upgradeRunnerImage: readOptionalStringEnv("PROVIDER_UPGRADE_RUNNER_IMAGE") ?? `unidesk_provider-gateway:${safeProviderSlug(providerId)}`,
    hostSshHost: readOptionalStringEnv("HOST_SSH_HOST") ?? (mountedHostSshKey ? "host.docker.internal" : null),
    hostSshPort: readOptionalNumberEnv("HOST_SSH_PORT") ?? (mountedHostSshKey ? 22 : null),
    hostSshUser,
    hostSshKey: readOptionalStringEnv("HOST_SSH_KEY") ?? (mountedHostSshKey ? defaultHostSshKey : null),
    hostRemoteCwd: readOptionalStringEnv("HOST_REMOTE_CWD") ?? defaultHostRemoteCwd(hostSshUser),
    hostLoginShell: readOptionalStringEnv("HOST_LOGIN_SHELL") ?? (hostSshUser === null ? null : "/bin/bash"),
    providerDataHost: providerDataEndpoint.host,
    providerDataPort: providerDataEndpoint.port,
    providerDataPoolSize: Math.max(1, Math.min(64, Math.floor(readNumberEnv("PROVIDER_DATA_POOL_SIZE", 10, "UNIDESK_PROVIDER_DATA_POOL_SIZE")))),
    providerDataConnectTimeoutMs: Math.max(250, Math.min(30_000, Math.floor(readNumberEnv("PROVIDER_DATA_CONNECT_TIMEOUT_MS", 5000, "UNIDESK_PROVIDER_DATA_CONNECT_TIMEOUT_MS")))),
    egressProxyEnabled: readBooleanEnv("PROVIDER_EGRESS_PROXY_ENABLED", true, "UNIDESK_PROVIDER_EGRESS_PROXY_ENABLED"),
    egressProxyListenHost: readOptionalStringEnv("PROVIDER_EGRESS_PROXY_LISTEN_HOST", "UNIDESK_PROVIDER_EGRESS_PROXY_LISTEN_HOST") ?? "0.0.0.0",
    egressProxyPort: readNumberEnv("PROVIDER_EGRESS_PROXY_PORT", 18789, "UNIDESK_PROVIDER_EGRESS_PROXY_PORT"),
    egressProxyOpenTimeoutMs: Math.max(1_000, Math.min(120_000, Math.floor(readNumberEnv("PROVIDER_EGRESS_PROXY_OPEN_TIMEOUT_MS", 15_000, "UNIDESK_PROVIDER_EGRESS_PROXY_OPEN_TIMEOUT_MS")))),
    egressProxyIdleTimeoutMs: Math.max(30_000, Math.min(3_600_000, Math.floor(readNumberEnv("PROVIDER_EGRESS_PROXY_IDLE_TIMEOUT_MS", 600_000, "UNIDESK_PROVIDER_EGRESS_PROXY_IDLE_TIMEOUT_MS")))),
    egressProxyMaxTunnelAgeMs: Math.max(60_000, Math.min(14_400_000, Math.floor(readNumberEnv("PROVIDER_EGRESS_PROXY_MAX_TUNNEL_AGE_MS", 3_600_000, "UNIDESK_PROVIDER_EGRESS_PROXY_MAX_TUNNEL_AGE_MS")))),
    egressProxyStaleTunnelIdleMs: Math.max(10_000, Math.min(600_000, Math.floor(readNumberEnv("PROVIDER_EGRESS_PROXY_STALE_TUNNEL_IDLE_MS", 120_000, "UNIDESK_PROVIDER_EGRESS_PROXY_STALE_TUNNEL_IDLE_MS")))),
    egressProxyMaxPendingBytes: Math.max(64 * 1024, Math.min(64 * 1024 * 1024, Math.floor(readNumberEnv("PROVIDER_EGRESS_PROXY_MAX_PENDING_BYTES", 4 * 1024 * 1024, "UNIDESK_PROVIDER_EGRESS_PROXY_MAX_PENDING_BYTES")))),
    logFile: readOptionalStringEnv("LOG_FILE") ?? `/var/log/unidesk/provider-gateway-${providerId}.jsonl`,
  };
}

function createLogger(service: string, logFile: string) {
  const writer = createHourlyJsonlWriter({
    baseLogFile: logFile,
    service,
    maxBytes: logRetentionBytesForService(service),
  });
  writer.prune();
  return (level: "debug" | "info" | "warn" | "error", message: string, data?: JsonValue): void => {
    const entry = data === undefined
      ? { ts: new Date().toISOString(), service, level, message }
      : { ts: new Date().toISOString(), service, level, message, data };
    const line = `${JSON.stringify(entry)}\n`;
    try {
      writer.appendLine(line, new Date(entry.ts));
    } catch (error) {
      console.error(JSON.stringify({ ts: new Date().toISOString(), service, level: "error", message: "log_write_failed", data: String(error) }));
    }
    const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
    consoleMethod(line.trimEnd());
  };
}

function withToken(rawUrl: string, token: string): string {
  const url = new URL(rawUrl);
  url.searchParams.set("token", token);
  return url.toString();
}

function currentLabels(): ProviderLabels {
  const hostSshConfigured = isHostSshConfigured();
  const containerGuard = refreshSelfContainerGuard();
  const egressProxyStatus = providerEgressProxy?.status() ?? null;
  const dataPoolStatus = sshDataPoolStatus();
  return {
    ...config.labels,
    dockerSocketPresent: existsSync(config.dockerSocketPath),
    hostSshConfigured,
    hostSshKeyPresent: config.hostSshKey !== null && existsSync(config.hostSshKey),
    hostSshTarget: hostSshConfigured ? `${config.hostSshUser}@${config.hostSshHost}:${config.hostSshPort}` : "not-configured",
    runtime: "bun",
    providerGatewayName: gatewayMetadata.name,
    providerGatewayVersion: gatewayMetadata.version,
    providerGatewayStartedAt: startedAt.toISOString(),
    providerGatewayUpgradePolicy: "always-enabled",
    providerGatewayMicroserviceHttpCache: true,
    providerGatewayEgressProxy: config.egressProxyEnabled,
    providerGatewayEgressProxyPort: config.egressProxyPort,
    providerGatewayEgressProxyConnected: egressProxyStatus === null ? false : egressProxyStatus.connected === true,
    providerGatewayEgressProxyActiveTunnels: egressProxyStatus === null ? 0 : egressProxyStatus.activeTunnels ?? 0,
    providerGatewayEgressProxyPendingTunnels: egressProxyStatus === null ? 0 : egressProxyStatus.pendingTunnels ?? 0,
    providerGatewayEgressProxyOpenedTunnels: egressProxyStatus === null ? 0 : egressProxyStatus.openedTunnels ?? 0,
    providerGatewayEgressProxyStaleTunnels: egressProxyStatus === null ? 0 : egressProxyStatus.staleTunnels ?? 0,
    providerGatewayEgressProxyOldestTunnelAgeMs: egressProxyStatus === null ? 0 : egressProxyStatus.oldestTunnelAgeMs ?? 0,
    providerGatewayEgressProxyOpenTimeoutMs: egressProxyStatus === null ? config.egressProxyOpenTimeoutMs : egressProxyStatus.openTimeoutMs ?? config.egressProxyOpenTimeoutMs,
    providerGatewayEgressProxyIdleTimeoutMs: egressProxyStatus === null ? config.egressProxyIdleTimeoutMs : egressProxyStatus.idleTimeoutMs ?? config.egressProxyIdleTimeoutMs,
    providerGatewayEgressProxyMaxTunnelAgeMs: egressProxyStatus === null ? config.egressProxyMaxTunnelAgeMs : egressProxyStatus.maxTunnelAgeMs ?? config.egressProxyMaxTunnelAgeMs,
    providerGatewayEgressProxyStaleTunnelIdleMs: egressProxyStatus === null ? config.egressProxyStaleTunnelIdleMs : egressProxyStatus.staleTunnelIdleMs ?? config.egressProxyStaleTunnelIdleMs,
    providerGatewayEgressProxyMaxPendingBytes: egressProxyStatus === null ? config.egressProxyMaxPendingBytes : egressProxyStatus.maxPendingBytes ?? config.egressProxyMaxPendingBytes,
    providerGatewayEgressProxyActiveTunnelDetails: egressProxyStatus === null ? [] : egressProxyStatus.activeTunnelDetails ?? [],
    providerGatewayEgressProxyRecentClosedTunnels: egressProxyStatus === null ? [] : egressProxyStatus.recentClosedTunnels ?? [],
    providerGatewaySshDataTransport: "tcp-pool",
    providerGatewaySshDataHost: config.providerDataHost,
    providerGatewaySshDataPort: config.providerDataPort,
    providerGatewaySshDataPoolDesired: config.providerDataPoolSize,
    providerGatewaySshDataPoolTotal: dataPoolStatus.total,
    providerGatewaySshDataPoolReady: dataPoolStatus.ready,
    providerGatewaySshDataPoolClaimed: dataPoolStatus.claimed,
    providerGatewaySshDataPoolConnecting: dataPoolStatus.connecting,
    providerGatewaySshDataPoolLastError: dataPoolStatus.lastError,
    providerGatewayDockerRestartGuard: true,
    providerGatewayContainerId: containerGuard.containerId,
    providerGatewayContainerName: containerGuard.containerName,
    providerGatewayRestartPolicy: containerGuard.restartPolicy,
    providerGatewayRestartPolicyOk: containerGuard.restartPolicyOk,
    providerGatewayRestartPolicyRemediated: containerGuard.remediatedRestartPolicy,
    providerGatewayPidMode: containerGuard.pidMode,
    providerGatewayPidModeOk: containerGuard.pidModeOk,
    providerGatewayRuntimeGuardOk: containerGuard.ok,
    providerGatewayRuntimeGuardError: containerGuard.error,
    providerGatewayRuntimeGuardCheckedAt: containerGuard.checkedAt,
    gatewayUptimeSeconds: Math.floor((Date.now() - startedAt.getTime()) / 1000),
  };
}

function sendJson(value: unknown): void {
  if (!socket || socket.readyState !== WebSocket.OPEN) return;
  socket.send(JSON.stringify(value));
}

function sendJsonOk(value: unknown): boolean {
  if (!socket || socket.readyState !== WebSocket.OPEN) return false;
  socket.send(JSON.stringify(value));
  return true;
}

function sshDataPoolStatus(): { total: number; ready: number; claimed: number; connecting: number; lastError: string | null } {
  let ready = 0;
  let claimed = 0;
  let connecting = 0;
  for (const channel of sshDataChannels.values()) {
    if (channel.status === "ready") ready += 1;
    if (channel.status === "claimed") claimed += 1;
    if (channel.status === "connecting") connecting += 1;
  }
  return { total: sshDataChannels.size, ready, claimed, connecting, lastError: sshDataLastError };
}

function nextSshDataChannelId(): string {
  sshDataChannelSeq += 1;
  return `sshdata_${safeProviderSlug(config.providerId)}_${Date.now()}_${sshDataChannelSeq}`;
}

function writeSshDataFrame(channel: SshDataChannel, header: Record<string, unknown>, payload: Buffer = Buffer.alloc(0)): boolean {
  if (channel.status === "closed" || channel.socket.destroyed) return false;
  const fullHeader = { ...header, length: payload.length };
  const headerBytes = Buffer.from(JSON.stringify(fullHeader), "utf8");
  if (headerBytes.length > sshDataMaxHeaderBytes) {
    channel.lastError = `ssh data header too large: ${headerBytes.length}`;
    sshDataLastError = channel.lastError;
    channel.socket.destroy(new Error(channel.lastError));
    return false;
  }
  const lengthBytes = Buffer.allocUnsafe(4);
  lengthBytes.writeUInt32BE(headerBytes.length, 0);
  channel.socket.write(lengthBytes);
  channel.socket.write(headerBytes);
  if (payload.length > 0) channel.socket.write(payload);
  channel.bytesOut += payload.length;
  return true;
}

function sendSshDataSessionFrame(sessionId: string, header: Record<string, unknown>, payload: Buffer = Buffer.alloc(0)): boolean {
  const session = hostSshSessions.get(sessionId);
  if (session?.dataChannelId === undefined) return false;
  const channel = sshDataChannels.get(session.dataChannelId);
  if (channel === undefined) return false;
  return writeSshDataFrame(channel, { ...header, sessionId }, payload);
}

function pruneCompletedHostSshSessions(now = Date.now()): void {
  for (const [sessionId, completed] of completedHostSshSessions) {
    if (now - completed.closedAt > completedHostSshSessionTtlMs) completedHostSshSessions.delete(sessionId);
  }
  while (completedHostSshSessions.size > completedHostSshSessionMaxCount) {
    const oldestSessionId = completedHostSshSessions.keys().next().value;
    if (typeof oldestSessionId !== "string") break;
    completedHostSshSessions.delete(oldestSessionId);
  }
}

function rememberCompletedHostSshSession(
  sessionId: string,
  dataChannelId: string | undefined,
  reason: CompletedHostSshSessionReason,
  exitCode: number | null = null,
): void {
  if (dataChannelId === undefined) return;
  pruneCompletedHostSshSessions();
  completedHostSshSessions.delete(sessionId);
  completedHostSshSessions.set(sessionId, { dataChannelId, closedAt: Date.now(), reason, exitCode });
}

function getRecentCompletedHostSshSession(sessionId: string, dataChannelId: string): CompletedHostSshSession | null {
  const completed = completedHostSshSessions.get(sessionId);
  if (completed === undefined) return null;
  const ageMs = Date.now() - completed.closedAt;
  if (ageMs > completedHostSshSessionTtlMs) {
    completedHostSshSessions.delete(sessionId);
    return null;
  }
  if (completed.dataChannelId !== dataChannelId) return null;
  return completed;
}

function isSshDataFrameIgnorableAfterSessionClosed(type: string): boolean {
  return type === "input" || type === "eof" || type === "resize" || type === "close";
}

function pendingSshDataFrameBytes(channel: SshDataChannel): number {
  return channel.pendingFrames.reduce((total, frame) => total + frame.payload.length, 0);
}

function shouldBufferSshDataFrameBeforeSessionReady(channel: SshDataChannel, sessionId: string, type: string): boolean {
  return (
    channel.activeSessionId === sessionId
    && (type === "input" || type === "eof" || type === "resize" || type === "close")
  );
}

function bufferSshDataFrameBeforeSessionReady(channel: SshDataChannel, sessionId: string, type: string, header: Record<string, unknown>, payload: Buffer): void {
  const pendingBytes = pendingSshDataFrameBytes(channel);
  if (
    channel.pendingFrames.length >= sshDataPendingSessionFrameMaxCount
    || pendingBytes + payload.length > sshDataPendingSessionFrameMaxBytes
  ) {
    const message = `ssh data frames arrived before session was ready and exceeded pending buffer limits: frames=${channel.pendingFrames.length} bytes=${pendingBytes}`;
    channel.lastError = message;
    sshDataLastError = message;
    logger("warn", "ssh_data_pending_session_buffer_overflow", { channelId: channel.id, sessionId, type, pendingFrames: channel.pendingFrames.length, pendingBytes, payloadBytes: payload.length });
    writeSshDataFrame(channel, { type: "error", sessionId, message, at: new Date().toISOString() });
    closeSshDataChannel(channel, "pending session frame buffer overflow");
    return;
  }
  channel.pendingFrames.push({ header, payload, queuedAt: Date.now() });
  logger("debug", "ssh_data_frame_buffered_before_session_ready", { channelId: channel.id, sessionId, type, pendingFrames: channel.pendingFrames.length, payloadBytes: payload.length });
}

function replayBufferedSshDataFrames(sessionId: string, channel: SshDataChannel): void {
  if (channel.pendingFrames.length === 0) return;
  const frames = channel.pendingFrames.splice(0);
  logger("info", "ssh_data_pending_session_frames_replayed", { channelId: channel.id, sessionId, frames: frames.length, oldestAgeMs: Date.now() - frames[0]!.queuedAt });
  for (const frame of frames) {
    handleSshDataFrame(channel, frame.header, frame.payload);
    if (!hostSshSessions.has(sessionId)) break;
  }
}

function handleSshDataFrame(channel: SshDataChannel, header: Record<string, unknown>, payload: Buffer): void {
  const type = typeof header.type === "string" ? header.type : "";
  const sessionId = typeof header.sessionId === "string" ? header.sessionId : "";
  if (sessionId.length === 0) {
    logger("warn", "ssh_data_frame_missing_session", { channelId: channel.id, type });
    return;
  }
  const session = hostSshSessions.get(sessionId);
  if (session === undefined) {
    if (shouldBufferSshDataFrameBeforeSessionReady(channel, sessionId, type)) {
      bufferSshDataFrameBeforeSessionReady(channel, sessionId, type, header, payload);
      return;
    }
    const completed = getRecentCompletedHostSshSession(sessionId, channel.id);
    if (completed !== null && isSshDataFrameIgnorableAfterSessionClosed(type)) {
      logger("debug", "ssh_data_frame_ignored_after_session_closed", {
        channelId: channel.id,
        sessionId,
        type,
        reason: completed.reason,
        exitCode: completed.exitCode,
        ageMs: Date.now() - completed.closedAt,
        payloadBytes: payload.length,
      });
      return;
    }
    logger("warn", "ssh_data_session_missing", { channelId: channel.id, sessionId, type });
    return;
  }
  try {
    if (type === "input") {
      const stdin = session.proc.stdin as HostSshStdin | undefined;
      if (stdin === undefined) throw new Error("host SSH stdin is not available");
      if (!session.tty && payload.length === 1 && payload[0] === 4) return;
      stdin.write(payload);
      return;
    }
    if (type === "eof") {
      const stdin = session.proc.stdin as HostSshStdin | undefined;
      if (stdin !== undefined) stdin.end();
      return;
    }
    if (type === "resize") {
      logger("debug", "host_ssh_resize_requested", {
        sessionId,
        cols: typeof header.cols === "number" || typeof header.cols === "string" ? header.cols : null,
        rows: typeof header.rows === "number" || typeof header.rows === "string" ? header.rows : null,
        transport: "tcp-pool",
      });
      return;
    }
    if (type === "close") {
      closeHostSshSession(sessionId);
      return;
    }
    logger("warn", "ssh_data_frame_unsupported", { channelId: channel.id, sessionId, type });
  } catch (error) {
    sendSshDataSessionFrame(sessionId, { type: "error", message: error instanceof Error ? error.message : String(error) });
  }
}

function consumeSshDataBytes(channel: SshDataChannel, chunk: Buffer): void {
  channel.buffer = Buffer.concat([channel.buffer, chunk]);
  while (channel.buffer.length >= 4) {
    const headerLength = channel.buffer.readUInt32BE(0);
    if (headerLength <= 0 || headerLength > sshDataMaxHeaderBytes) {
      channel.lastError = `invalid ssh data header length: ${headerLength}`;
      sshDataLastError = channel.lastError;
      channel.socket.destroy(new Error(channel.lastError));
      return;
    }
    if (channel.buffer.length < 4 + headerLength) return;
    const headerBytes = channel.buffer.subarray(4, 4 + headerLength);
    let header: Record<string, unknown>;
    try {
      header = JSON.parse(headerBytes.toString("utf8")) as Record<string, unknown>;
    } catch (error) {
      channel.lastError = `invalid ssh data header json: ${error instanceof Error ? error.message : String(error)}`;
      sshDataLastError = channel.lastError;
      channel.socket.destroy(new Error(channel.lastError));
      return;
    }
    const payloadLength = typeof header.length === "number" && Number.isFinite(header.length)
      ? Math.max(0, Math.floor(header.length))
      : 0;
    if (payloadLength > sshDataMaxPayloadBytes) {
      channel.lastError = `ssh data payload too large: ${payloadLength}`;
      sshDataLastError = channel.lastError;
      channel.socket.destroy(new Error(channel.lastError));
      return;
    }
    if (channel.buffer.length < 4 + headerLength + payloadLength) return;
    const payload = channel.buffer.subarray(4 + headerLength, 4 + headerLength + payloadLength);
    channel.buffer = channel.buffer.subarray(4 + headerLength + payloadLength);
    channel.bytesIn += payload.length;
    handleSshDataFrame(channel, header, Buffer.from(payload));
  }
}

function closeSshDataChannel(channel: SshDataChannel, reason: string): void {
  channel.status = "closed";
  sshDataChannels.delete(channel.id);
  if (channel.activeSessionId !== null) {
    const sessionId = channel.activeSessionId;
    const session = hostSshSessions.get(sessionId);
    if (session !== undefined) {
      sendHostSshError(sessionId, `ssh tcp data channel closed: ${reason}`);
      try {
        session.proc.kill("SIGTERM");
      } catch {
        /* process may already be gone */
      }
    }
  }
  if (sshDataPoolDesired) {
    setTimeout(ensureSshDataPool, 250);
  }
}

function openSshDataChannel(): void {
  const channelId = nextSshDataChannelId();
  const socket = createConnection({ host: config.providerDataHost, port: config.providerDataPort });
  const channel: SshDataChannel = {
    id: channelId,
    socket,
    status: "connecting",
    buffer: Buffer.alloc(0),
    pendingFrames: [],
    openedAt: Date.now(),
    lastReadyAt: null,
    activeSessionId: null,
    bytesIn: 0,
    bytesOut: 0,
    lastError: null,
  };
  sshDataChannels.set(channelId, channel);
  const timeout = setTimeout(() => {
    if (channel.status === "connecting") {
      channel.lastError = `provider data tcp connect timeout after ${config.providerDataConnectTimeoutMs}ms`;
      sshDataLastError = channel.lastError;
      socket.destroy(new Error(channel.lastError));
    }
  }, config.providerDataConnectTimeoutMs);
  socket.on("connect", () => {
    clearTimeout(timeout);
    channel.status = "ready";
    channel.lastReadyAt = Date.now();
    sshDataLastError = null;
    writeSshDataFrame(channel, {
      type: "hello",
      protocol: sshDataProtocol,
      providerId: config.providerId,
      channelId,
      token: config.token,
    });
    logger("info", "ssh_data_channel_ready", { providerId: config.providerId, channelId, host: config.providerDataHost, port: config.providerDataPort });
    sendHeartbeat();
  });
  socket.on("data", (chunk) => consumeSshDataBytes(channel, Buffer.from(chunk)));
  socket.on("error", (error) => {
    channel.lastError = error.message;
    sshDataLastError = error.message;
    logger("warn", "ssh_data_channel_error", { providerId: config.providerId, channelId, error: error.message });
  });
  socket.on("close", () => {
    clearTimeout(timeout);
    logger("warn", "ssh_data_channel_close", { providerId: config.providerId, channelId, lastError: channel.lastError });
    closeSshDataChannel(channel, channel.lastError ?? "socket closed");
  });
}

function ensureSshDataPool(): void {
  if (!sshDataPoolDesired || !isHostSshConfigured()) return;
  const status = sshDataPoolStatus();
  const needed = config.providerDataPoolSize - status.total;
  for (let index = 0; index < needed; index += 1) {
    openSshDataChannel();
  }
}

function closeSshDataPool(reason: string): void {
  sshDataPoolDesired = false;
  for (const channel of sshDataChannels.values()) {
    channel.status = "closed";
    try {
      channel.socket.destroy(new Error(reason));
    } catch {
      /* socket may already be closed */
    }
  }
  sshDataChannels.clear();
}

function acquireSshDataChannel(sessionId: string, channelId: string): SshDataChannel | null {
  const channel = sshDataChannels.get(channelId);
  if (channel === undefined || channel.status !== "ready" || channel.activeSessionId !== null) {
    return null;
  }
  channel.status = "claimed";
  channel.activeSessionId = sessionId;
  return channel;
}

function releaseSshDataChannel(channelId: string, sessionId: string): void {
  const channel = sshDataChannels.get(channelId);
  if (channel === undefined) return;
  if (channel.activeSessionId !== sessionId) return;
  channel.activeSessionId = null;
  if (channel.status !== "closed" && !channel.socket.destroyed) channel.status = "ready";
  ensureSshDataPool();
}

function sendRegister(): void {
  const capabilities = ["heartbeat", "system.status", "docker.status", "docker.ps", "provider.upgrade", "microservice.http", "microservice.http.cache", "microservice.http.tunnel", "echo"];
  if (isHostSshConfigured()) capabilities.push("host.ssh", "host.ssh.tcp-pool");
  if (config.egressProxyEnabled) capabilities.push("network.egress-proxy");
  sendJson({
    type: "register",
    providerId: config.providerId,
    name: config.providerName,
    labels: currentLabels(),
    startedAt: startedAt.toISOString(),
    capabilities,
  });
}

function sendHeartbeat(): void {
  const labels = currentLabels();
  sendJson({
    type: "heartbeat",
    providerId: config.providerId,
    labels,
    at: new Date().toISOString(),
  });
  reportPendingProviderUpgradeCompletion(labels);
}

function providerUpgradeCompletionPath(): string {
  return `${config.upgradeWorkspacePath.replace(/\/+$/, "")}/.state/provider-upgrade-final-${safeProviderSlug(config.providerId)}.json`;
}

function readPendingProviderUpgradeCompletion(): Record<string, JsonValue> | null {
  const path = providerUpgradeCompletionPath();
  if (!existsSync(path)) return null;
  try {
    const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
    return parsed as Record<string, JsonValue>;
  } catch (error) {
    logger("warn", "provider_upgrade_completion_file_invalid", { path, error: error instanceof Error ? error.message : String(error) });
    return null;
  }
}

function reportPendingProviderUpgradeCompletion(labels: ProviderLabels): void {
  const completion = readPendingProviderUpgradeCompletion();
  if (completion === null) return;
  const taskId = typeof completion.taskId === "string" ? completion.taskId : "";
  if (!/^task_[A-Za-z0-9_.-]+$/u.test(taskId)) return;
  if (reportedProviderUpgradeCompletionTasks.has(taskId)) return;
  if (completion.providerId !== config.providerId) return;
  const finalContainerId = typeof completion.finalContainerId === "string" ? completion.finalContainerId : "";
  const currentContainerId = typeof labels.providerGatewayContainerId === "string" ? labels.providerGatewayContainerId : "";
  if (finalContainerId.length > 0 && currentContainerId.length > 0 && finalContainerId !== currentContainerId) {
    logger("warn", "provider_upgrade_completion_container_mismatch", { taskId, finalContainerId, currentContainerId });
    return;
  }
  const heartbeatTimestamp = new Date().toISOString();
  sendTaskStatus(taskId, "succeeded", "provider upgrade final promoted container verified by provider heartbeat", {
    ok: true,
    mode: "schedule",
    taskId,
    providerId: config.providerId,
    providerGatewayVersion: gatewayMetadata.version,
    targetProviderGatewayVersion: completion.version ?? gatewayMetadata.version,
    candidateValidationWasTerminal: false,
    finalPromotedContainer: {
      containerId: currentContainerId || finalContainerId || null,
      containerName: labels.providerGatewayContainerName ?? completion.finalContainerName ?? null,
      version: gatewayMetadata.version,
      restartPolicy: labels.providerGatewayRestartPolicy ?? completion.restartPolicy ?? null,
      pidMode: labels.providerGatewayPidMode ?? completion.pidMode ?? null,
      heartbeatTimestamp,
    },
    heartbeat: {
      providerGatewayRuntimeGuardOk: labels.providerGatewayRuntimeGuardOk ?? null,
      providerGatewayRuntimeGuardError: labels.providerGatewayRuntimeGuardError ?? null,
      providerGatewayRestartPolicyOk: labels.providerGatewayRestartPolicyOk ?? null,
      providerGatewayPidModeOk: labels.providerGatewayPidModeOk ?? null,
      providerGatewayStartedAt: labels.providerGatewayStartedAt ?? null,
    },
    completionFile: providerUpgradeCompletionPath(),
  });
  reportedProviderUpgradeCompletionTasks.add(taskId);
  logger("info", "provider_upgrade_completion_reported", { taskId, finalContainerId: currentContainerId || finalContainerId || null });
}

async function sendDockerStatus(): Promise<void> {
  if (!socket || socket.readyState !== WebSocket.OPEN || dockerStatusRunning) return;
  dockerStatusRunning = true;
  try {
    const status = await collectDockerStatus();
    sendJson({
      type: "docker_status",
      providerId: config.providerId,
      at: new Date().toISOString(),
      status,
    });
    logger("debug", "docker_status_sent", { providerId: config.providerId, counts: status.counts });
  } catch (error) {
    logger("error", "docker_status_failed", { error: error instanceof Error ? error.message : String(error) });
  } finally {
    dockerStatusRunning = false;
  }
}

async function sendSystemStatus(): Promise<void> {
  if (!socket || socket.readyState !== WebSocket.OPEN || systemStatusRunning) return;
  systemStatusRunning = true;
  try {
    const status = await collectSystemStatus();
    sendJson({
      type: "system_status",
      providerId: config.providerId,
      at: new Date().toISOString(),
      status,
    });
    logger("debug", "system_status_sent", {
      providerId: config.providerId,
      cpuPercent: status.cpu.percent,
      memoryPercent: status.memory.percent,
      diskPercent: status.disk.percent,
      processCount: status.processes?.length ?? 0,
    });
  } catch (error) {
    logger("error", "system_status_failed", { error: error instanceof Error ? error.message : String(error) });
  } finally {
    systemStatusRunning = false;
  }
}

async function sendTaskStatus(taskId: string, status: ProviderTaskStatusMessage["status"], message: string, result?: JsonValue): Promise<void> {
  sendJson({
    type: "task_status",
    providerId: config.providerId,
    taskId,
    status,
    message,
    at: new Date().toISOString(),
    result,
  });
}

async function runProcessCommand(command: string, args: string[], timeoutMs = 6000): Promise<{ ok: boolean; stdout: string; stderr: string; exitCode: number; timedOut: boolean }> {
  const proc = Bun.spawn([command, ...args], {
    stdout: "pipe",
    stderr: "pipe",
  });
  let timedOut = false;
  const timeout = setTimeout(() => {
    timedOut = true;
    proc.kill("SIGKILL");
  }, timeoutMs);
  const [stdout, stderr, exitCode] = await Promise.all([
    new Response(proc.stdout).text(),
    new Response(proc.stderr).text(),
    proc.exited,
  ]);
  clearTimeout(timeout);
  return { ok: exitCode === 0 && !timedOut, stdout, stderr, exitCode, timedOut };
}

async function runDockerCommand(args: string[], timeoutMs = 6000): Promise<{ ok: boolean; stdout: string; stderr: string; exitCode: number }> {
  return runProcessCommand("docker", args, timeoutMs);
}

function containerInspectMapFromJson(stdout: string): Map<string, DockerContainerInspectDetails> {
  const map = new Map<string, DockerContainerInspectDetails>();
  let parsed: unknown;
  try {
    parsed = JSON.parse(stdout) as unknown;
  } catch {
    return map;
  }
  const rows = Array.isArray(parsed) ? parsed : [parsed];
  for (const row of rows) {
    const details = dockerInspectDetails(row);
    if (details === null) continue;
    if (details.id.length > 0) {
      map.set(details.id, details);
      map.set(details.id.slice(0, 12), details);
    }
    if (details.name.length > 0) map.set(details.name, details);
  }
  return map;
}

async function inspectContainerDetails(containers: DockerContainerSummary[]): Promise<{ details: Map<string, DockerContainerInspectDetails>; errors: JsonValue[] }> {
  const details = new Map<string, DockerContainerInspectDetails>();
  const errors: JsonValue[] = [];
  const ids = containers.map((container) => container.id).filter((id) => id.length > 0);
  if (ids.length === 0) return { details, errors };

  const result = await runDockerCommand(["inspect", ...ids], 8000);
  if (result.ok) {
    return { details: containerInspectMapFromJson(result.stdout), errors };
  }

  errors.push({ source: "docker.inspect", exitCode: result.exitCode, stderr: result.stderr.slice(0, 500) });
  const gatewayContainers = containers.filter((container) => container.name.includes("provider-gateway"));
  for (const container of gatewayContainers) {
    const fallback = await runDockerCommand(["inspect", container.id], 3000);
    if (!fallback.ok) {
      errors.push({ source: "docker.inspect.provider-gateway", container: container.name, exitCode: fallback.exitCode, stderr: fallback.stderr.slice(0, 300) });
      continue;
    }
    for (const [key, value] of containerInspectMapFromJson(fallback.stdout)) details.set(key, value);
  }
  return { details, errors };
}

function stringField(row: Record<string, unknown>, key: string): string {
  const value = row[key];
  return typeof value === "string" ? value : value === undefined || value === null ? "" : String(value);
}

function parseJsonLines(stdout: string, limit: number): Array<Record<string, unknown>> {
  const rows: Array<Record<string, unknown>> = [];
  for (const line of stdout.split("\n")) {
    const text = line.trim();
    if (!text) continue;
    try {
      const parsed = JSON.parse(text) as unknown;
      if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) rows.push(parsed as Record<string, unknown>);
    } catch (error) {
      logger("warn", "docker_json_line_parse_failed", { line: text.slice(0, 200), error: String(error) });
    }
    if (rows.length >= limit) break;
  }
  return rows;
}

function toContainer(row: Record<string, unknown>, inspect: DockerContainerInspectDetails | null = null): DockerContainerSummary {
  return {
    id: stringField(row, "ID"),
    name: stringField(row, "Names"),
    image: stringField(row, "Image"),
    state: stringField(row, "State"),
    status: stringField(row, "Status"),
    ports: stringField(row, "Ports"),
    createdAt: stringField(row, "CreatedAt"),
    runningFor: stringField(row, "RunningFor"),
    size: stringField(row, "Size"),
    networks: stringField(row, "Networks"),
    restartPolicy: inspect?.restartPolicy ?? "",
    restartPolicyMaximumRetryCount: inspect?.restartPolicyMaximumRetryCount ?? 0,
    pidMode: inspect?.pidMode ?? "",
    composeProject: inspect?.composeProject ?? "",
    composeService: inspect?.composeService ?? "",
    labels: inspect?.labels ?? {},
  };
}

function toImage(row: Record<string, unknown>): DockerImageSummary {
  return {
    id: stringField(row, "ID"),
    repository: stringField(row, "Repository"),
    tag: stringField(row, "Tag"),
    size: stringField(row, "Size"),
    createdSince: stringField(row, "CreatedSince"),
    containers: stringField(row, "Containers"),
  };
}

function toVolume(row: Record<string, unknown>): DockerVolumeSummary {
  return {
    name: stringField(row, "Name"),
    driver: stringField(row, "Driver"),
    scope: stringField(row, "Scope"),
    mountpoint: stringField(row, "Mountpoint"),
    labels: stringField(row, "Labels"),
    size: stringField(row, "Size"),
    status: stringField(row, "Status"),
  };
}

function toNetwork(row: Record<string, unknown>): DockerNetworkSummary {
  return {
    id: stringField(row, "ID"),
    name: stringField(row, "Name"),
    driver: stringField(row, "Driver"),
    scope: stringField(row, "Scope"),
    internal: stringField(row, "Internal"),
    ipv4: stringField(row, "IPv4"),
    ipv6: stringField(row, "IPv6"),
  };
}

function jsonRecord(value: unknown): Record<string, JsonValue> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
  return value as Record<string, JsonValue>;
}

function clampPercent(value: number): number {
  return Math.max(0, Math.min(100, Number(value.toFixed(1))));
}

function roundMetric(value: number, digits = 1): number {
  if (!Number.isFinite(value)) return 0;
  return Math.max(0, Number(value.toFixed(digits)));
}

function readCpuSample(): { idle: number; total: number; cores: number } {
  const stat = readFileSync("/proc/stat", "utf8");
  const lines = stat.split("\n");
  const aggregate = lines[0]?.trim().split(/\s+/).slice(1).map(Number) ?? [];
  if (aggregate.length < 5 || aggregate.some((item) => !Number.isFinite(item))) {
    throw new Error("Unable to parse /proc/stat aggregate CPU row");
  }
  const idle = aggregate[3] + aggregate[4];
  const total = aggregate.reduce((sum, item) => sum + item, 0);
  const cores = Math.max(1, lines.filter((line) => /^cpu\d+\s/.test(line)).length);
  return { idle, total, cores };
}

function readLoadAverage(): { load1: number; load5: number; load15: number } {
  const [load1, load5, load15] = readFileSync("/proc/loadavg", "utf8").trim().split(/\s+/).map(Number);
  return {
    load1: Number.isFinite(load1) ? load1 : 0,
    load5: Number.isFinite(load5) ? load5 : 0,
    load15: Number.isFinite(load15) ? load15 : 0,
  };
}

function readMemInfo(): Record<string, number> {
  const result: Record<string, number> = {};
  for (const line of readFileSync("/proc/meminfo", "utf8").split("\n")) {
    const match = line.match(/^([A-Za-z_()]+):\s+(\d+)\s+kB/);
    if (match) result[match[1]] = Number(match[2]) * 1024;
  }
  return result;
}

async function readGetconfNumber(name: string, fallback: number): Promise<number> {
  try {
    const result = await runProcessCommand("getconf", [name], 1000);
    const parsed = Number(result.stdout.trim());
    return result.ok && Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
  } catch {
    return fallback;
  }
}

async function systemClockTicks(): Promise<number> {
  if (clockTicksCache !== null) return clockTicksCache;
  clockTicksCache = await readGetconfNumber("CLK_TCK", 100);
  return clockTicksCache;
}

async function systemPageSize(): Promise<number> {
  if (pageSizeCache !== null) return pageSizeCache;
  pageSizeCache = await readGetconfNumber("PAGESIZE", 4096);
  return pageSizeCache;
}

function usernameForUid(uid: number): string {
  if (passwdCache === null) {
    passwdCache = new Map<number, string>();
    try {
      for (const line of readFileSync("/etc/passwd", "utf8").split("\n")) {
        const parts = line.split(":");
        const parsedUid = Number(parts[2]);
        if (parts[0] && Number.isFinite(parsedUid)) passwdCache.set(parsedUid, parts[0]);
      }
    } catch {
      // Provider containers may not have the host passwd database; fall back to uid labels.
    }
  }
  return passwdCache.get(uid) ?? `uid:${uid}`;
}

function readProcessStatus(pid: number): { uid: number; threads: number; name: string } {
  const status = readFileSync(`/proc/${pid}/status`, "utf8");
  let uid = -1;
  let threads = 0;
  let name = "";
  for (const line of status.split("\n")) {
    if (line.startsWith("Uid:")) {
      const parsed = Number(line.trim().split(/\s+/)[1]);
      if (Number.isFinite(parsed)) uid = parsed;
    } else if (line.startsWith("Threads:")) {
      const parsed = Number(line.trim().split(/\s+/)[1]);
      if (Number.isFinite(parsed)) threads = parsed;
    } else if (line.startsWith("Name:")) {
      name = line.slice("Name:".length).trim();
    }
  }
  return { uid, threads, name };
}

function readProcessCommand(pid: number, fallback: string): string {
  try {
    const raw = readFileSync(`/proc/${pid}/cmdline`, "utf8");
    const command = raw.split("\0").filter(Boolean).join(" ").trim();
    return (command || fallback).slice(0, 500);
  } catch {
    return fallback;
  }
}

function readProcessIo(pid: number): { readBytes: number; writeBytes: number } {
  try {
    const result = { readBytes: 0, writeBytes: 0 };
    for (const line of readFileSync(`/proc/${pid}/io`, "utf8").split("\n")) {
      const match = line.match(/^(read_bytes|write_bytes):\s+(\d+)/);
      if (!match) continue;
      const parsed = Number(match[2]);
      if (!Number.isFinite(parsed)) continue;
      if (match[1] === "read_bytes") result.readBytes = parsed;
      if (match[1] === "write_bytes") result.writeBytes = parsed;
    }
    return result;
  } catch {
    return { readBytes: 0, writeBytes: 0 };
  }
}

function readProcessMemory(pid: number, rssBytes: number, pageSize: number): {
  memoryBytes: number;
  memoryMode: string;
  pssBytes?: number;
  privateBytes?: number;
  sharedBytes?: number;
  swapPssBytes?: number;
} {
  try {
    const values = new Map<string, number>();
    for (const line of readFileSync(`/proc/${pid}/smaps_rollup`, "utf8").split("\n")) {
      const match = line.match(/^([A-Za-z_]+):\s+(\d+)\s+kB/);
      if (!match) continue;
      values.set(match[1], Number(match[2]) * 1024);
    }
    const pssBytes = values.get("Pss");
    if (typeof pssBytes !== "number" || !Number.isFinite(pssBytes) || pssBytes < 0) {
      throw new Error("missing Pss");
    }
    const privateBytes = (values.get("Private_Clean") ?? 0) + (values.get("Private_Dirty") ?? 0);
    const sharedBytes = (values.get("Shared_Clean") ?? 0) + (values.get("Shared_Dirty") ?? 0);
    const swapPssBytes = values.get("SwapPss") ?? 0;
    return {
      memoryBytes: pssBytes,
      memoryMode: "pss_smaps_rollup",
      pssBytes,
      privateBytes,
      sharedBytes,
      swapPssBytes,
    };
  } catch {
    try {
      const fields = readFileSync(`/proc/${pid}/statm`, "utf8").trim().split(/\s+/);
      const sharedPages = Number(fields[2]);
      const sharedBytes = Number.isFinite(sharedPages) ? Math.max(0, sharedPages * pageSize) : 0;
      const privateBytes = Math.max(0, rssBytes - sharedBytes);
      return {
        memoryBytes: privateBytes,
        memoryMode: "rss_minus_shared_fallback",
        privateBytes,
        sharedBytes,
      };
    } catch {
      return { memoryBytes: rssBytes, memoryMode: "rss_fallback" };
    }
  }
}

function readUptimeSeconds(): number {
  const uptime = Number(readFileSync("/proc/uptime", "utf8").trim().split(/\s+/)[0]);
  return Number.isFinite(uptime) ? uptime : 0;
}

function parseProcessStat(raw: string): {
  pid: number;
  name: string;
  state: string;
  ppid: number;
  totalTicks: number;
  startTicks: number;
  vmsBytes: number;
  rssPages: number;
  threads: number;
} | null {
  const open = raw.indexOf("(");
  const close = raw.lastIndexOf(")");
  if (open < 0 || close <= open) return null;
  const pid = Number(raw.slice(0, open).trim());
  const name = raw.slice(open + 1, close);
  const fields = raw.slice(close + 1).trim().split(/\s+/);
  if (!Number.isFinite(pid) || fields.length < 22) return null;
  const value = (index: number): number => {
    const parsed = Number(fields[index]);
    return Number.isFinite(parsed) ? parsed : 0;
  };
  return {
    pid,
    name,
    state: fields[0] || "?",
    ppid: value(1),
    totalTicks: value(11) + value(12),
    threads: value(17),
    startTicks: value(19),
    vmsBytes: value(20),
    rssPages: value(21),
  };
}

async function collectProcessResources(totalMemoryBytes: number, cpuCores: number): Promise<{
  processes: ProcessResourceSummary[];
  summary: Record<string, JsonValue>;
}> {
  const [clockTicks, pageSize] = await Promise.all([systemClockTicks(), systemPageSize()]);
  const uptimeSeconds = readUptimeSeconds();
  const sampledAtMs = Date.now();
  const rows: ProcessResourceSummary[] = [];
  const seen = new Set<number>();
  let skipped = 0;
  const hasPreviousProcessSample = previousProcessSamples.size > 0;

  for (const entry of readdirSync("/proc")) {
    if (!/^\d+$/.test(entry)) continue;
    const pid = Number(entry);
    try {
      const stat = parseProcessStat(readFileSync(`/proc/${entry}/stat`, "utf8"));
      if (stat === null) {
        skipped += 1;
        continue;
      }
      const status = readProcessStatus(pid);
      const io = readProcessIo(pid);
      const previous = previousProcessSamples.get(pid);
      const elapsedSampleSeconds = previous ? Math.max(0.001, (sampledAtMs - previous.sampledAtMs) / 1000) : 0;
      const elapsedProcessSeconds = Math.max(0, uptimeSeconds - stat.startTicks / clockTicks);
      const cpuPercent = previous
        ? ((Math.max(0, stat.totalTicks - previous.totalTicks) / clockTicks) / elapsedSampleSeconds) * 100
        : elapsedProcessSeconds > 0
          ? ((stat.totalTicks / clockTicks) / elapsedProcessSeconds) * 100
          : 0;
      const ioSeconds = previous ? elapsedSampleSeconds : 0;
      const readBytesPerSecond = previous && ioSeconds > 0 ? Math.max(0, io.readBytes - previous.readBytes) / ioSeconds : 0;
      const writeBytesPerSecond = previous && ioSeconds > 0 ? Math.max(0, io.writeBytes - previous.writeBytes) / ioSeconds : 0;
      const rssBytes = Math.max(0, stat.rssPages * pageSize);
      const processMemory = readProcessMemory(pid, rssBytes, pageSize);
      const name = status.name || stat.name;
      const uid = status.uid >= 0 ? status.uid : 0;
      rows.push({
        pid: stat.pid,
        ppid: stat.ppid,
        uid,
        user: usernameForUid(uid),
        name,
        command: readProcessCommand(pid, name),
        state: stat.state,
        cpuPercent: roundMetric(Math.min(cpuPercent, Math.max(1, cpuCores) * 100)),
        memoryPercent: totalMemoryBytes > 0 ? clampPercent((processMemory.memoryBytes / totalMemoryBytes) * 100) : 0,
        memoryBytes: processMemory.memoryBytes,
        memoryMode: processMemory.memoryMode,
        rssBytes,
        pssBytes: processMemory.pssBytes,
        privateBytes: processMemory.privateBytes,
        sharedBytes: processMemory.sharedBytes,
        swapPssBytes: processMemory.swapPssBytes,
        vmsBytes: Math.max(0, stat.vmsBytes),
        threads: status.threads || stat.threads,
        readBytes: io.readBytes,
        writeBytes: io.writeBytes,
        readBytesPerSecond: roundMetric(readBytesPerSecond, 0),
        writeBytesPerSecond: roundMetric(writeBytesPerSecond, 0),
        elapsedSeconds: roundMetric(elapsedProcessSeconds, 0),
        startedAt: new Date(Date.now() - elapsedProcessSeconds * 1000).toISOString(),
      });
      previousProcessSamples.set(pid, {
        totalTicks: stat.totalTicks,
        sampledAtMs,
        readBytes: io.readBytes,
        writeBytes: io.writeBytes,
      });
      seen.add(pid);
    } catch {
      skipped += 1;
    }
  }

  previousProcessSamples = new Map([...previousProcessSamples].filter(([pid]) => seen.has(pid)));
  rows.sort((a, b) => b.memoryBytes - a.memoryBytes || b.cpuPercent - a.cpuPercent || a.pid - b.pid);
  const pssRows = rows.filter((row) => row.memoryMode === "pss_smaps_rollup").length;
  const rssMinusSharedRows = rows.filter((row) => row.memoryMode === "rss_minus_shared_fallback").length;
  return {
    processes: rows.slice(0, 120),
    summary: {
      total: rows.length,
      visible: Math.min(rows.length, 120),
      skipped,
      defaultSort: "memory_desc",
      scope: "provider_pid_namespace",
      memoryMode: pssRows > 0 ? "pss_smaps_rollup_with_fallback" : rssMinusSharedRows > 0 ? "rss_minus_shared_fallback" : "rss_fallback",
      pssRows,
      rssMinusSharedRows,
      rssFallbackRows: rows.length - pssRows - rssMinusSharedRows,
      cpuPercentMode: hasPreviousProcessSample ? "delta_ticks_per_sample" : "lifetime_average_first_sample",
      diskIoMode: "proc_pid_io_delta_bytes_per_second",
    },
  };
}

async function readDiskUsage(path: string): Promise<{ mount: string; totalBytes: number; usedBytes: number; availableBytes: number; percent: number }> {
  const result = await runProcessCommand("df", ["-PB1", path], 3000);
  if (!result.ok) throw new Error(`df failed with exit ${result.exitCode}: ${result.stderr}`);
  const lines = result.stdout.trim().split("\n").filter(Boolean);
  const values = lines[1]?.trim().split(/\s+/);
  if (!values || values.length < 6) throw new Error(`Unable to parse df output: ${result.stdout.slice(0, 300)}`);
  const totalBytes = Number(values[1]);
  const usedBytes = Number(values[2]);
  const availableBytes = Number(values[3]);
  if (![totalBytes, usedBytes, availableBytes].every(Number.isFinite) || totalBytes <= 0) {
    throw new Error(`Invalid df numeric output: ${lines[1]}`);
  }
  return {
    mount: values.slice(5).join(" "),
    totalBytes,
    usedBytes,
    availableBytes,
    percent: clampPercent((usedBytes / totalBytes) * 100),
  };
}

async function collectSystemStatus(): Promise<SystemStatusSnapshot> {
  const collectedAt = new Date().toISOString();
  const errors: JsonValue[] = [];

  let cpu: Record<string, JsonValue> = { percent: 0, cores: 0, load1: 0, load5: 0, load15: 0 };
  try {
    const sample = readCpuSample();
    const load = readLoadAverage();
    let percent = clampPercent((load.load1 / sample.cores) * 100);
    if (previousCpuSample !== null) {
      const totalDelta = sample.total - previousCpuSample.total;
      const idleDelta = sample.idle - previousCpuSample.idle;
      if (totalDelta > 0) percent = clampPercent((1 - idleDelta / totalDelta) * 100);
    }
    previousCpuSample = { idle: sample.idle, total: sample.total };
    cpu = { percent, cores: sample.cores, ...load };
  } catch (error) {
    errors.push({ source: "proc.stat", error: error instanceof Error ? error.message : String(error) });
  }

  let memory: Record<string, JsonValue> = { totalBytes: 0, usedBytes: 0, availableBytes: 0, cacheBytes: 0, percent: 0, mode: "actual_without_cache" };
  try {
    const mem = readMemInfo();
    const totalBytes = mem.MemTotal ?? 0;
    const freeBytes = mem.MemFree ?? 0;
    const buffersBytes = mem.Buffers ?? 0;
    const cachedBytes = mem.Cached ?? 0;
    const reclaimableBytes = mem.SReclaimable ?? 0;
    const shmemBytes = mem.Shmem ?? 0;
    const cacheBytes = Math.max(0, buffersBytes + cachedBytes + reclaimableBytes - shmemBytes);
    const usedBytes = Math.max(0, totalBytes - freeBytes - cacheBytes);
    const availableBytes = mem.MemAvailable ?? Math.max(0, freeBytes + cacheBytes);
    memory = {
      mode: "actual_without_cache",
      totalBytes,
      usedBytes,
      availableBytes,
      freeBytes,
      cacheBytes,
      buffersBytes,
      cachedBytes,
      reclaimableBytes,
      shmemBytes,
      percent: totalBytes > 0 ? clampPercent((usedBytes / totalBytes) * 100) : 0,
    };
  } catch (error) {
    errors.push({ source: "proc.meminfo", error: error instanceof Error ? error.message : String(error) });
  }

  let processes: ProcessResourceSummary[] = [];
  let processSummary: Record<string, JsonValue> = { total: 0, visible: 0, defaultSort: "memory_desc" };
  try {
    const totalMemoryBytes = typeof memory.totalBytes === "number" ? memory.totalBytes : 0;
    const cpuCores = typeof cpu.cores === "number" ? cpu.cores : 1;
    const collected = await collectProcessResources(totalMemoryBytes, cpuCores);
    processes = collected.processes;
    processSummary = collected.summary;
  } catch (error) {
    errors.push({ source: "proc.processes", error: error instanceof Error ? error.message : String(error) });
  }

  let disk: Record<string, JsonValue> = { path: config.monitorDiskPath, mount: "", totalBytes: 0, usedBytes: 0, availableBytes: 0, percent: 0 };
  try {
    disk = { path: config.monitorDiskPath, ...(await readDiskUsage(config.monitorDiskPath)) };
  } catch (error) {
    errors.push({ source: "df", path: config.monitorDiskPath, error: error instanceof Error ? error.message : String(error) });
  }

  return { ok: errors.length === 0, collectedAt, cpu, memory, disk, processes, processSummary, errors };
}

async function collectDockerStatus(): Promise<DockerStatusSnapshot> {
  const collectedAt = new Date().toISOString();
  const socketPresent = existsSync(config.dockerSocketPath);
  const errors: JsonValue[] = [];
  const [infoResult, containersResult, imagesResult, volumesResult, networksResult] = await Promise.all([
    runDockerCommand(["info", "--format", "{{json .}}"]),
    runDockerCommand(["ps", "-a", "--format", "{{json .}}"]),
    runDockerCommand(["images", "--format", "{{json .}}"]),
    runDockerCommand(["volume", "ls", "--format", "{{json .}}"]),
    runDockerCommand(["network", "ls", "--format", "{{json .}}"]),
  ]);

  let info: Record<string, JsonValue> = {};
  if (infoResult.ok) {
    try {
      info = jsonRecord(JSON.parse(infoResult.stdout) as unknown);
    } catch (error) {
      errors.push({ source: "docker.info", error: String(error) });
    }
  } else {
    errors.push({ source: "docker.info", exitCode: infoResult.exitCode, stderr: infoResult.stderr.slice(0, 500) });
  }
  for (const [source, result] of Object.entries({ containers: containersResult, images: imagesResult, volumes: volumesResult, networks: networksResult })) {
    if (!result.ok) errors.push({ source, exitCode: result.exitCode, stderr: result.stderr.slice(0, 500) });
  }

  const baseContainers = containersResult.ok ? parseJsonLines(containersResult.stdout, 80).map((row) => toContainer(row)) : [];
  const inspected = await inspectContainerDetails(baseContainers);
  for (const error of inspected.errors) errors.push(error);
  const containers = containersResult.ok
    ? parseJsonLines(containersResult.stdout, 80).map((row) => {
      const id = stringField(row, "ID");
      const name = stringField(row, "Names");
      return toContainer(row, inspected.details.get(id) ?? inspected.details.get(name) ?? null);
    })
    : [];
  const images = imagesResult.ok ? parseJsonLines(imagesResult.stdout, 80).map(toImage) : [];
  const volumes = volumesResult.ok ? parseJsonLines(volumesResult.stdout, 60).map(toVolume) : [];
  const networks = networksResult.ok ? parseJsonLines(networksResult.stdout, 60).map(toNetwork) : [];
  const running = containers.filter((item) => item.state === "running").length;
  const paused = containers.filter((item) => item.state === "paused").length;
  const stopped = containers.filter((item) => item.state !== "running" && item.state !== "paused").length;

  return {
    ok: errors.length === 0,
    socketPresent,
    collectedAt,
    daemon: {
      name: info.Name ?? "",
      serverVersion: info.ServerVersion ?? "",
      operatingSystem: info.OperatingSystem ?? "",
      osType: info.OSType ?? "",
      architecture: info.Architecture ?? "",
      cpus: info.NCPU ?? 0,
      memoryBytes: info.MemTotal ?? 0,
      dockerRootDir: info.DockerRootDir ?? "",
      driver: info.Driver ?? "",
    },
    counts: {
      containers: containers.length,
      running,
      paused,
      stopped,
      images: images.length,
      volumes: volumes.length,
      networks: networks.length,
      daemonContainers: info.Containers ?? containers.length,
      daemonImages: info.Images ?? images.length,
    },
    containers,
    images,
    volumes,
    networks,
    errors,
  };
}

async function runDockerPs(): Promise<JsonValue> {
  const result = await runDockerCommand(["ps", "--format", "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"], 5000);
  if (!result.ok) {
    throw new Error(`docker ps failed with exit ${result.exitCode}: ${result.stderr}`);
  }
  const containers = result.stdout
    .split("\n")
    .map((line) => line.trim())
    .filter((line) => line.length > 0)
    .slice(0, 50)
    .map((line) => {
      const [id, name, image, status, ports] = line.split("\t");
      return { id, name, image, status, ports };
    });
  return { containers, count: containers.length, stderr: result.stderr };
}

function shellQuote(value: string): string {
  return `'${value.replace(/'/g, `'\\''`)}'`;
}

function isHostSshConfigured(): boolean {
  return config.hostSshHost !== null && config.hostSshPort !== null && config.hostSshUser !== null && config.hostSshKey !== null;
}

function missingHostSshFields(): string[] {
  const missing: string[] = [];
  if (config.hostSshHost === null) missing.push("HOST_SSH_HOST");
  if (config.hostSshPort === null) missing.push("HOST_SSH_PORT");
  if (config.hostSshUser === null) missing.push("HOST_SSH_USER");
  if (config.hostSshKey === null) missing.push("HOST_SSH_KEY");
  return missing;
}

function payloadString(payload: Record<string, JsonValue>, key: string): string | null {
  const value = payload[key];
  return typeof value === "string" && value.length > 0 ? value : null;
}

function payloadTimeoutMs(payload: Record<string, JsonValue>): number {
  const raw = payload.timeoutMs;
  const value = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : 8000;
  if (!Number.isFinite(value) || value <= 0) return 8000;
  return Math.max(1000, Math.min(15_000, Math.floor(value)));
}

function truncateText(value: string, maxLength = 4000): string {
  return value.length > maxLength ? `${value.slice(0, maxLength)}...<truncated:${value.length}>` : value;
}

function safeKnownHostsName(providerId: string): string {
  return providerId.replace(/[^a-zA-Z0-9_.-]/g, "-").slice(0, 80) || "provider";
}

function defaultHostSshProbeCommand(): string {
  return "printf 'UNIDESK_SSH_TEST user=%s host=%s bridge=%s cwd=%s\\n' \"$(whoami)\" \"$(hostname)\" \"${UNIDESK_BRIDGE:-}\" \"$(pwd)\"";
}

function normalizeShellCommand(command: string): string {
  return command.replace(/\\\r?\n/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
}

function hostSshSelfMutationReason(command: string, cwd: string | null): string | null {
  const normalized = normalizeShellCommand(command);
  const currentContainerName = `unidesk-provider-gateway-${safeDockerName(config.providerId)}`.toLowerCase();
  const composeProject = config.upgradeComposeProject.toLowerCase();
  const composeService = config.upgradeService.toLowerCase();
  const composeFile = config.upgradeComposeFile.toLowerCase();
  const composeEnvFile = config.upgradeEnvFile.toLowerCase();
  const hostProjectRoot = config.upgradeHostProjectRoot.toLowerCase();
  const currentCwd = (cwd ?? "").toLowerCase();
  const mutatesCompose = /\bdocker\s+compose\b|\bdocker-compose\b/.test(normalized)
    && /\b(up|build|restart|stop|rm|down|kill)\b/.test(normalized);
  const mentionsCurrentProvider = normalized.includes(currentContainerName)
    || normalized.includes(composeProject)
    || normalized.includes(composeService)
    || normalized.includes(composeFile)
    || normalized.includes(composeEnvFile)
    || normalized.includes(hostProjectRoot)
    || (currentCwd.length > 0 && currentCwd.startsWith(hostProjectRoot));
  if (mutatesCompose && mentionsCurrentProvider) {
    return "docker compose mutation targets the current provider-gateway deployment";
  }
  const mutatesContainer = /\bdocker\s+(container\s+)?(rm|stop|restart|kill)\b/.test(normalized);
  if (mutatesContainer && normalized.includes(currentContainerName)) {
    return "docker container mutation targets the current provider-gateway container";
  }
  return null;
}

function assertHostSshCommandAllowed(command: string, cwd: string | null): void {
  const reason = hostSshSelfMutationReason(command, cwd);
  if (reason !== null) {
    throw new Error(`blocked unsafe host.ssh self-mutation: ${reason}; use provider.upgrade mode=schedule or a detached local node shell instead`);
  }
}

async function runHostSsh(payload: Record<string, JsonValue>): Promise<JsonValue> {
  if (!isHostSshConfigured()) {
    throw new Error(`host SSH bridge is not configured; missing ${missingHostSshFields().join(", ")}`);
  }
  const host = config.hostSshHost ?? "";
  const port = config.hostSshPort ?? 22;
  const user = config.hostSshUser ?? "";
  const key = config.hostSshKey ?? "";
  if (!existsSync(key)) {
    throw new Error(`host SSH key is not mounted at ${key}`);
  }

  const mode = payload.mode === "exec" ? "exec" : "probe";
  const timeoutMs = payloadTimeoutMs(payload);
  const requestedCommand = payloadString(payload, "command");
  const command = mode === "exec" && requestedCommand !== null ? requestedCommand : defaultHostSshProbeCommand();
  if (command.length > 4000) {
    throw new Error(`host SSH command is too long: ${command.length} bytes`);
  }
  const cwd = payloadString(payload, "cwd") ?? config.hostRemoteCwd;
  if (mode === "exec") assertHostSshCommandAllowed(command, cwd);
  const scriptParts = [
    "set -e",
    cwd === null ? null : `cd ${shellQuote(cwd)}`,
    "export UNIDESK_BRIDGE=host.ssh",
    `export UNIDESK_PROVIDER_ID=${shellQuote(config.providerId)}`,
    config.hostLoginShell === null
      ? command
      : `${shellQuote(config.hostLoginShell)} -lc ${shellQuote(command)}`,
  ].filter((part): part is string => part !== null);
  const remoteScript = scriptParts.join("; ");
  const result = await runProcessCommand("ssh", [
    "-T",
    "-i",
    key,
    "-p",
    String(port),
    "-o",
    "BatchMode=yes",
    "-o",
    "StrictHostKeyChecking=accept-new",
    "-o",
    `UserKnownHostsFile=/tmp/unidesk-host-known-hosts-${safeKnownHostsName(config.providerId)}`,
    "-o",
    "ServerAliveInterval=20",
    "-o",
    "ServerAliveCountMax=3",
    `${user}@${host}`,
    remoteScript,
  ], timeoutMs);
  const stdout = truncateText(result.stdout);
  const stderr = truncateText(result.stderr);
  const probeLine = stdout.split("\n").find((line) => line.includes("UNIDESK_SSH_TEST")) ?? "";
  return {
    ok: result.ok,
    mode,
    host,
    port,
    user,
    cwd: cwd ?? "",
    timeoutMs,
    timedOut: result.timedOut,
    exitCode: result.exitCode,
    command: truncateText(command, 1000),
    stdout,
    stderr,
    probeLine,
    sshKeyPresent: true,
  };
}

function hostSshCwdFailureLine(cwd: string): string {
  return `UNIDESK_SSH_CWD_FAILED ${JSON.stringify({
    code: "ssh-cwd-failed",
    failureKind: "cwd-failed",
    cwd,
    message: "requested host ssh cwd does not exist or is not accessible",
  })}`;
}

function hostSshEnterCwdScript(requestedCwd: string, fallbackCwd: string, explicitCwd: boolean): string {
  if (!explicitCwd) return `cd ${shellQuote(requestedCwd)} 2>/dev/null || cd ${shellQuote(fallbackCwd)} 2>/dev/null || cd`;
  return `cd ${shellQuote(requestedCwd)} 2>/dev/null || { printf '%s\\n' ${shellQuote(hostSshCwdFailureLine(requestedCwd))} >&2; exit 1; }`;
}

function hostSshRemoteScript(command: string | null, cwd: string | null, allocateTty: boolean, cols?: number, rows?: number): string {
  const fallbackCwd = config.hostRemoteCwd ?? `/home/${config.hostSshUser ?? "root"}`;
  const explicitCwd = cwd !== null;
  const requestedCwd = cwd ?? fallbackCwd;
  if (command !== null && command.length > 0) assertHostSshCommandAllowed(command, requestedCwd);
  const loginShell = config.hostLoginShell ?? "/bin/bash";
  const resize = allocateTty && Number.isFinite(cols) && Number.isFinite(rows)
    ? `stty rows ${Math.max(8, Math.min(120, Math.floor(rows ?? 30)))} cols ${Math.max(20, Math.min(300, Math.floor(cols ?? 100)))} 2>/dev/null || true`
    : "true";
  const enterCwd = hostSshEnterCwdScript(requestedCwd, fallbackCwd, explicitCwd);
  const exports = `export UNIDESK_BRIDGE=host.ssh; export UNIDESK_PROVIDER_ID=${shellQuote(config.providerId)}`;
  const execPart = command === null || command.length === 0
    ? `exec ${shellQuote(loginShell)} -l`
    : `${shellQuote(loginShell)} -lc ${shellQuote(command)}`;
  return `${enterCwd}; ${exports}; ${resize}; ${execPart}`;
}

function hostSshArgs(remoteScript: string, allocateTty: boolean): string[] {
  if (!isHostSshConfigured()) {
    throw new Error(`host SSH bridge is not configured; missing ${missingHostSshFields().join(", ")}`);
  }
  const key = config.hostSshKey ?? "";
  if (!existsSync(key)) {
    throw new Error(`host SSH key is not mounted at ${key}`);
  }
  return [
    allocateTty ? "-tt" : "-T",
    "-i",
    key,
    "-p",
    String(config.hostSshPort ?? 22),
    "-o",
    "BatchMode=yes",
    "-o",
    "StrictHostKeyChecking=accept-new",
    "-o",
    `UserKnownHostsFile=/tmp/unidesk-host-known-hosts-${safeKnownHostsName(config.providerId)}`,
    "-o",
    "ServerAliveInterval=20",
    "-o",
    "ServerAliveCountMax=3",
    `${config.hostSshUser}@${config.hostSshHost}`,
    remoteScript,
  ];
}

async function pumpHostSshOutput(sessionId: string, streamName: "stdout" | "stderr", stream: ReadableStream<Uint8Array> | null): Promise<void> {
  if (stream === null) return;
  const reader = stream.getReader();
  while (true) {
    const chunk = await reader.read();
    if (chunk.done) return;
    const sent = sendSshDataSessionFrame(sessionId, { type: "data", stream: streamName, at: new Date().toISOString() }, Buffer.from(chunk.value));
    if (!sent) throw new Error(`ssh tcp data channel is not available for ${sessionId}`);
  }
}

function sendHostSshError(sessionId: string, error: unknown): void {
  const message = error instanceof Error ? error.message : String(error);
  logger("error", "host_ssh_session_error", { sessionId, error: message });
  if (sendSshDataSessionFrame(sessionId, { type: "error", message, at: new Date().toISOString() })) return;
  sendJson({
    type: "host_ssh_error",
    providerId: config.providerId,
    sessionId,
    message,
    at: new Date().toISOString(),
  });
}

function startHostSshSession(message: CoreHostSshOpenMessage): void {
  if (hostSshSessions.has(message.sessionId)) {
    sendHostSshError(message.sessionId, `host SSH session already exists: ${message.sessionId}`);
    return;
  }
  if (message.transport !== "tcp-pool" || typeof message.dataChannelId !== "string" || message.dataChannelId.length === 0) {
    sendHostSshError(message.sessionId, "host SSH requires tcp-pool transport; upgrade backend-core/provider-gateway together");
    return;
  }
  const dataChannel = acquireSshDataChannel(message.sessionId, message.dataChannelId);
  if (dataChannel === null) {
    sendHostSshError(message.sessionId, `requested ssh tcp data channel is not ready: ${message.dataChannelId}`);
    return;
  }
  try {
    const command = typeof message.command === "string" && message.command.length > 0 ? message.command : null;
    const commandDigest = command === null ? null : createHash("sha256").update(command).digest("hex").slice(0, 16);
    const allocateTty = typeof message.tty === "boolean" ? message.tty : command === null;
    const remoteScript = hostSshRemoteScript(command, message.cwd ?? null, allocateTty, message.cols, message.rows);
    const proc = Bun.spawn(["ssh", ...hostSshArgs(remoteScript, allocateTty)], {
      stdin: "pipe",
      stdout: "pipe",
      stderr: "pipe",
    });
    hostSshSessions.set(message.sessionId, {
      proc,
      openedAt: Date.now(),
      tty: allocateTty,
      dataChannelId: message.dataChannelId,
      commandBytes: command === null ? 0 : Buffer.byteLength(command),
      commandDigest,
      cwd: message.cwd ?? null,
    });
    writeSshDataFrame(dataChannel, {
      type: "opened",
      sessionId: message.sessionId,
      providerId: config.providerId,
      at: new Date().toISOString(),
    });
    replayBufferedSshDataFrames(message.sessionId, dataChannel);
    const stdoutDone = pumpHostSshOutput(message.sessionId, "stdout", proc.stdout);
    const stderrDone = pumpHostSshOutput(message.sessionId, "stderr", proc.stderr);
    Promise.all([stdoutDone, stderrDone, proc.exited])
      .then(([, , exitCode]) => {
        sendSshDataSessionFrame(message.sessionId, {
          type: "exit",
          exitCode,
          signal: null,
          at: new Date().toISOString(),
        });
        rememberCompletedHostSshSession(message.sessionId, message.dataChannelId, "exit", typeof exitCode === "number" ? exitCode : null);
        logger("info", "host_ssh_session_completed", {
          sessionId: message.sessionId,
          exitCode,
          commandBytes: command === null ? 0 : Buffer.byteLength(command),
          commandDigest,
          tty: allocateTty,
          cwd: message.cwd ?? null,
          transport: "tcp-pool",
          dataChannelId: message.dataChannelId,
        });
        hostSshSessions.delete(message.sessionId);
        releaseSshDataChannel(message.dataChannelId, message.sessionId);
      })
      .catch((error) => {
        logger("error", "host_ssh_session_failed", {
          sessionId: message.sessionId,
          error: error instanceof Error ? error.message : String(error),
          commandBytes: command === null ? 0 : Buffer.byteLength(command),
          commandDigest,
          tty: allocateTty,
          cwd: message.cwd ?? null,
          transport: "tcp-pool",
          dataChannelId: message.dataChannelId,
        });
        sendHostSshError(message.sessionId, error);
        rememberCompletedHostSshSession(message.sessionId, message.dataChannelId, "error");
        hostSshSessions.delete(message.sessionId);
        releaseSshDataChannel(message.dataChannelId, message.sessionId);
      });
    logger("info", "host_ssh_session_started", {
      sessionId: message.sessionId,
      hasCommand: command !== null,
      commandBytes: command === null ? 0 : Buffer.byteLength(command),
      commandDigest,
      tty: allocateTty,
      cwd: message.cwd ?? null,
      transport: "tcp-pool",
      dataChannelId: message.dataChannelId,
    });
  } catch (error) {
    sendHostSshError(message.sessionId, error);
    releaseSshDataChannel(message.dataChannelId, message.sessionId);
  }
}

function closeHostSshSession(sessionId: string): void {
  const session = hostSshSessions.get(sessionId);
  if (session === undefined) return;
  rememberCompletedHostSshSession(sessionId, session.dataChannelId, "client-close");
  hostSshSessions.delete(sessionId);
  if (session.dataChannelId !== undefined) releaseSshDataChannel(session.dataChannelId, sessionId);
  session.proc.kill("SIGTERM");
  setTimeout(() => {
    try {
      session.proc.kill("SIGKILL");
    } catch {
      /* process may already be gone */
    }
  }, 1000);
}

function safeDockerName(value: string): string {
  return value.replace(/[^a-zA-Z0-9_.-]/g, "-").slice(0, 80);
}

function candidateGatewayName(taskId: string): string {
  return `unidesk-provider-gateway-${safeDockerName(config.providerId)}-candidate-${safeDockerName(taskId)}`.slice(0, 120);
}

function upgradePlan(taskId: string): Record<string, JsonValue> {
  const workspace = config.upgradeWorkspacePath;
  const workspaceRoot = workspace.replace(/\/+$/, "");
  const sleepMs = 300_000;
  const validationTimeoutMs = 180_000;
  const compactBuildContextCandidates = Array.from(new Set([
    `${workspaceRoot}/.state/provider-${config.providerId}-build-context`,
    `${workspaceRoot}/.state/provider-${safeProviderSlug(config.providerId)}-build-context`,
  ]));
  const composeBaseCommand = [
    "docker",
    "compose",
    "--env-file",
    `${workspace}/${config.upgradeEnvFile}`,
    "-f",
    `${workspace}/${config.upgradeComposeFile}`,
    "-p",
    config.upgradeComposeProject,
  ];
  const composeBuildCommand = [
    ...composeBaseCommand,
    "build",
    config.upgradeService,
  ];
  const listServiceContainersCommand = [
    "docker",
    "ps",
    "-aq",
    "--filter",
    `label=com.docker.compose.project=${config.upgradeComposeProject}`,
    "--filter",
    `label=com.docker.compose.service=${config.upgradeService}`,
    "--filter",
    "label=com.docker.compose.oneoff=False",
  ];
  const composeUpCommand = [
    ...composeBaseCommand,
    "up",
    "-d",
    "--no-deps",
    "--force-recreate",
    config.upgradeService,
  ];
  const updaterName = `unidesk-provider-upgrader-${safeDockerName(taskId)}`;
  const candidateName = candidateGatewayName(taskId);
  const upgradeCompletionPath = `${workspaceRoot}/.state/provider-upgrade-final-${safeProviderSlug(config.providerId)}.json`;
  const validationNeedleOpen = `"message":"connect_open"`;
  const validationNeedleAck = `"requestId":"register"`;
  const validationNeedleOk = `"ok":true`;
  const validationAttempts = Math.max(1, Math.ceil(validationTimeoutMs / 2000));
  const finalPromotedReconnectTimeoutMs = 180_000;
  const finalPromotedHeartbeatTimeoutMs = 240_000;
  const finalValidationAttempts = Math.max(1, Math.ceil(finalPromotedReconnectTimeoutMs / 2000));
  const targetGatewayMetadata = readTargetGatewayMetadata(workspace);
  const unavailableFinalPromotedContainer = {
    containerId: null,
    containerIdUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
    containerName: null,
    containerNameUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
    version: null,
    versionUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
    restartPolicy: null,
    restartPolicyUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
    pidMode: null,
    pidModeUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
    heartbeatTimestamp: null,
    heartbeatTimestampUnavailableReason: "mode=plan or schedule has not yet observed backend-core final promoted heartbeat",
  };
  const compactBuildContextRefreshScript = [
    `provider_src=${shellQuote(`${workspaceRoot}/src/components/provider-gateway`)}`,
    `shared_src=${shellQuote(`${workspaceRoot}/src/components/shared`)}`,
    `for compact_context in ${compactBuildContextCandidates.map(shellQuote).join(" ")}; do`,
    `  if [ -d "$compact_context" ]; then`,
    `    echo "refreshing provider-gateway build context: $compact_context"`,
    `    mkdir -p "$compact_context/src/components/provider-gateway" "$compact_context/src/components/shared"`,
    `    rm -rf "$compact_context/src/components/provider-gateway/src" "$compact_context/src/components/provider-gateway/scripts" "$compact_context/src/components/shared/src"`,
    `    cp -a "$provider_src/package.json" "$compact_context/src/components/provider-gateway/package.json"`,
    `    [ ! -f "$provider_src/tsconfig.json" ] || cp -a "$provider_src/tsconfig.json" "$compact_context/src/components/provider-gateway/tsconfig.json"`,
    `    [ ! -f "$provider_src/Dockerfile" ] || cp -a "$provider_src/Dockerfile" "$compact_context/src/components/provider-gateway/Dockerfile"`,
    `    [ -f "$compact_context/Dockerfile" ] || cp -a "$provider_src/Dockerfile" "$compact_context/Dockerfile"`,
    `    cp -a "$provider_src/src" "$compact_context/src/components/provider-gateway/src"`,
    `    cp -a "$provider_src/scripts" "$compact_context/src/components/provider-gateway/scripts"`,
    `    cp -a "$shared_src/package.json" "$compact_context/src/components/shared/package.json"`,
    `    [ ! -f "$shared_src/tsconfig.json" ] || cp -a "$shared_src/tsconfig.json" "$compact_context/src/components/shared/tsconfig.json"`,
    `    cp -a "$shared_src/src" "$compact_context/src/components/shared/src"`,
    `    date -u +%Y-%m-%dT%H:%M:%SZ > "$compact_context/.unidesk-build-context-refreshed-at"`,
    `  fi`,
    `done`,
  ].join("\n");
  const rollbackToLastKnownGoodScript = [
    `rollback_to_last_known_good() {`,
    `  reason="$1"`,
    `  echo "rollback_to_last_known_good: $reason" >&2`,
    `  if [ -n "$final_container" ]; then docker rm -f "$final_container" >/dev/null 2>&1 || true; fi`,
    `  docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true`,
    `  if [ -z "$old_name" ] || [ -z "$old_image" ]; then echo "rollback_to_last_known_good unavailable: old_name=$old_name old_image=$old_image" >&2; return 1; fi`,
    `  docker rm -f "$old_name" >/dev/null 2>&1 || true`,
    [
      `  docker run -d`,
      `--name "$old_name"`,
      `--restart always`,
      `--pid "\${old_pid_mode:-host}"`,
      `$network_arg`,
      `--env-file "$candidate_env_file"`,
      `--label ${shellQuote(`com.docker.compose.project=${config.upgradeComposeProject}`)}`,
      `--label ${shellQuote(`com.docker.compose.service=${config.upgradeService}`)}`,
      `--label ${shellQuote("com.docker.compose.oneoff=False")}`,
      `--label ${shellQuote(`unidesk.upgrade.rollback=${taskId}`)}`,
      `$mount_args`,
      `$extra_host_args`,
      `"$old_image" >/dev/null || return 1`,
    ].join(" "),
    `  if [ -n "$network_names" ]; then for net in $network_names; do if [ "$net" != "$first_network" ]; then docker network connect "$net" "$old_name" >/dev/null 2>&1 || true; fi; done; fi`,
    `  echo "rollback_to_last_known_good restored old gateway container name=$old_name image=$old_image reason=$reason" >&2`,
    `}`,
  ].join("\n");
  const runtimeDependencyGuardScript = [
    `validate_gateway_runtime_dependencies() {`,
    `  container="$1"`,
    `  phase="$2"`,
    `  docker exec "$container" /bin/sh -lc 'set -eu`,
    `test -x /bin/sh`,
    `test -x /usr/bin/ssh`,
    `/usr/bin/ssh -V >/tmp/unidesk-provider-ssh-version 2>&1 || { cat /tmp/unidesk-provider-ssh-version >&2; exit 41; }`,
    `command -v docker >/dev/null`,
    `docker version --format "{{.Client.Version}}" >/dev/null`,
    `test -e /lib/libz.so.1 -o -e /usr/lib/libz.so.1`,
    `command -v bun >/dev/null' || { echo "$phase provider-gateway runtime dependency guard failed" >&2; return 1; }`,
    `  echo "$phase provider-gateway runtime dependency guard passed: container=$container"`,
    `}`,
  ].join("\n");
  const script = [
    "set -eu",
    `cd ${shellQuote(workspace)}`,
    compactBuildContextRefreshScript,
    `old_ids=$(${listServiceContainersCommand.map(shellQuote).join(" ")})`,
    `first_old=""`,
    `for old_id in $old_ids; do first_old="$old_id"; break; done`,
    `old_name=""`,
    `old_image=""`,
    `old_restart=""`,
    `old_pid_mode=""`,
    `final_container=""`,
    `mount_args=""`,
    `extra_host_args=""`,
    `candidate_env_file=${shellQuote(`/tmp/${candidateName}.env`)}`,
    `upgrade_completion_file=${shellQuote(upgradeCompletionPath)}`,
    `network_names=""`,
    `first_network=""`,
    `network_arg=""`,
    rollbackToLastKnownGoodScript,
    runtimeDependencyGuardScript,
    `if [ -n "$old_ids" ]; then docker update --restart always $old_ids >/dev/null 2>&1 || true; fi`,
    `if [ -z "$first_old" ]; then echo "no existing provider-gateway compose container found; cannot perform safe in-place upgrade" >&2; exit 1; fi`,
    `old_name=$(docker inspect --format '{{.Name}}' "$first_old")`,
    `old_name="\${old_name#/}"`,
    `old_image=$(docker inspect --format '{{.Image}}' "$first_old")`,
    `old_restart=$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$first_old")`,
    `old_pid_mode=$(docker inspect --format '{{.HostConfig.PidMode}}' "$first_old")`,
    `docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$first_old" > "$candidate_env_file"`,
    `mount_args=$(docker inspect --format '{{range .Mounts}}{{printf "-v %s:%s" .Source .Destination}}{{if not .RW}}{{printf ":ro"}}{{end}}{{printf "\\n"}}{{end}}' "$first_old")`,
    `extra_host_args=$(docker inspect --format '{{range .HostConfig.ExtraHosts}}{{printf "--add-host %s\\n" .}}{{end}}' "$first_old")`,
    `network_names=$(docker inspect --format '{{range $name, $_ := .NetworkSettings.Networks}}{{printf "%s\\n" $name}}{{end}}' "$first_old")`,
    `for net in $network_names; do first_network="$net"; break; done`,
    `if [ -n "$first_network" ]; then network_arg="--network $first_network"; fi`,
    composeBuildCommand.map(shellQuote).join(" "),
    `docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true`,
    [
      "docker run -d",
      `--name ${shellQuote(candidateName)}`,
      "--restart no",
      "--pid host",
      "$network_arg",
      `--env-file "$candidate_env_file"`,
      `--label ${shellQuote(`com.docker.compose.project=${config.upgradeComposeProject}`)}`,
      `--label ${shellQuote(`com.docker.compose.service=${config.upgradeService}`)}`,
      `--label ${shellQuote("com.docker.compose.oneoff=False")}`,
      "$mount_args",
      "$extra_host_args",
      shellQuote(config.upgradeRunnerImage),
    ].join(" "),
    `if [ -n "$network_names" ]; then for net in $network_names; do if [ "$net" != "$first_network" ]; then docker network connect "$net" ${shellQuote(candidateName)} >/dev/null 2>&1 || true; fi; done; fi`,
    `attempt=0`,
    `validated=0`,
    `while [ "$attempt" -lt "${validationAttempts}" ]; do logs=$(docker logs ${shellQuote(candidateName)} 2>&1 || true); has_open=0; has_ack=0; has_ok=0; case "$logs" in *${shellQuote(validationNeedleOpen)}*) has_open=1;; esac; case "$logs" in *${shellQuote(validationNeedleAck)}*) has_ack=1;; esac; case "$logs" in *${shellQuote(validationNeedleOk)}*) has_ok=1;; esac; if [ "$has_open" = "1" ] && [ "$has_ack" = "1" ] && [ "$has_ok" = "1" ]; then validated=1; break; fi; candidate_running=$(docker inspect --format '{{.State.Running}}' ${shellQuote(candidateName)} 2>/dev/null || true); if [ "$candidate_running" != "true" ]; then break; fi; attempt=$((attempt + 1)); sleep 2; done`,
	    `if [ "$validated" != "1" ]; then echo "candidate validation failed; old gateway will leave upgrade sleep automatically" >&2; docker logs ${shellQuote(candidateName)} >&2 || true; docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true; rm -f "$candidate_env_file"; exit 1; fi`,
	    `candidate_restart=$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' ${shellQuote(candidateName)})`,
	    `candidate_pid_mode=$(docker inspect --format '{{.HostConfig.PidMode}}' ${shellQuote(candidateName)})`,
	    `if [ "$candidate_pid_mode" != "host" ]; then echo "candidate runtime guard failed: restart=$candidate_restart pid=$candidate_pid_mode" >&2; docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true; rm -f "$candidate_env_file"; exit 1; fi`,
	    `validate_gateway_runtime_dependencies ${shellQuote(candidateName)} candidate || { docker logs ${shellQuote(candidateName)} >&2 || true; docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true; rm -f "$candidate_env_file"; exit 1; }`,
	    `if [ -n "$old_ids" ]; then docker rm -f $old_ids; fi`,
    `docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true`,
    composeUpCommand.map(shellQuote).join(" "),
    `final_container="$old_name"`,
    `if [ -z "$final_container" ] || ! docker inspect "$final_container" >/dev/null 2>&1; then final_container=$(${listServiceContainersCommand.map(shellQuote).join(" ")} | head -n 1); fi`,
    `if [ -z "$final_container" ]; then echo "final provider-gateway compose container not found after promotion" >&2; rollback_to_last_known_good "final-compose-container-missing" || true; rm -f "$candidate_env_file"; exit 1; fi`,
    `final_container_id=$(docker inspect --format '{{.Id}}' "$final_container")`,
    `final_restart=$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$final_container")`,
    `final_pid_mode=$(docker inspect --format '{{.HostConfig.PidMode}}' "$final_container")`,
    `if [ "$final_restart" != "always" ] || [ "$final_pid_mode" != "host" ]; then echo "final provider-gateway runtime guard failed: restart=$final_restart pid=$final_pid_mode" >&2; rollback_to_last_known_good "final-runtime-guard-failed" || true; rm -f "$candidate_env_file"; exit 1; fi`,
    `if ! validate_gateway_runtime_dependencies "$final_container" final; then rollback_to_last_known_good "final-runtime-dependency-guard-failed" || true; rm -f "$candidate_env_file"; exit 1; fi`,
    `final_attempt=0`,
    `final_validated=0`,
    `while [ "$final_attempt" -lt "${finalValidationAttempts}" ]; do final_logs=$(docker logs "$final_container" 2>&1 || true); final_has_open=0; final_has_ack=0; final_has_ok=0; case "$final_logs" in *${shellQuote(validationNeedleOpen)}*) final_has_open=1;; esac; case "$final_logs" in *${shellQuote(validationNeedleAck)}*) final_has_ack=1;; esac; case "$final_logs" in *${shellQuote(validationNeedleOk)}*) final_has_ok=1;; esac; if [ "$final_has_open" = "1" ] && [ "$final_has_ack" = "1" ] && [ "$final_has_ok" = "1" ]; then final_validated=1; break; fi; final_running=$(docker inspect --format '{{.State.Running}}' "$final_container" 2>/dev/null || true); if [ "$final_running" != "true" ]; then break; fi; final_attempt=$((final_attempt + 1)); sleep 2; done`,
    `if [ "$final_validated" != "1" ]; then echo "final provider-gateway compose container did not reconnect to backend-core before local guard timeout; backend-core must not treat candidate validation as success" >&2; docker logs "$final_container" >&2 || true; rollback_to_last_known_good "final-promoted-container-reconnect-timeout" || true; rm -f "$candidate_env_file"; exit 1; fi`,
    `mkdir -p "$(dirname "$upgrade_completion_file")"`,
    `upgrade_completion_tmp="$upgrade_completion_file.tmp.$$"`,
    [
      `{`,
      `  printf '{"taskId":"%s",' ${shellQuote(taskId)};`,
      `  printf '"providerId":"%s",' ${shellQuote(config.providerId)};`,
      `  printf '"finalContainerId":"%s",' "$final_container_id";`,
      `  printf '"finalContainerName":"%s",' "$final_container";`,
      `  printf '"version":"%s",' ${shellQuote(targetGatewayMetadata.version)};`,
      `  printf '"restartPolicy":"%s",' "$final_restart";`,
      `  printf '"pidMode":"%s",' "$final_pid_mode";`,
      `  printf '"writtenAt":"%s"}\\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)";`,
      `} > "$upgrade_completion_tmp"`,
    ].join(" "),
    `mv "$upgrade_completion_tmp" "$upgrade_completion_file"`,
    `rm -f "$candidate_env_file"`,
    `echo "candidate provider-gateway validated; final promoted compose container reconnected locally: container=$final_container id=$final_container_id restart=$final_restart pid=$final_pid_mode"`,
  ].join("\n");
  const dockerRunCommand = [
    "docker",
    "run",
    "-d",
    "--name",
    updaterName,
    "--restart",
    "no",
    "--label",
    "com.docker.compose.oneoff=True",
    "--label",
    `unidesk.upgrade.updater=${taskId}`,
    "-v",
    "/var/run/docker.sock:/var/run/docker.sock",
    "-v",
    `${config.upgradeHostProjectRoot}:${workspace}:ro`,
    "-v",
    `${config.upgradeHostProjectRoot.replace(/\/+$/, "")}/.state:${workspaceRoot}/.state`,
    "-w",
    workspace,
    config.upgradeRunnerImage,
    "sh",
    "-lc",
    script,
  ];
  return {
    policy: "always-enabled",
    taskId,
    providerId: config.providerId,
    providerName: config.providerName,
    providerGatewayName: gatewayMetadata.name,
    providerGatewayVersion: gatewayMetadata.version,
    targetProviderGatewayName: targetGatewayMetadata.name,
    targetProviderGatewayVersion: targetGatewayMetadata.version,
    updaterName,
    candidateName,
    runnerImage: config.upgradeRunnerImage,
    hostProjectRoot: config.upgradeHostProjectRoot,
    workspace,
    compose: {
      project: config.upgradeComposeProject,
      service: config.upgradeService,
      composeFile: config.upgradeComposeFile,
      envFile: config.upgradeEnvFile,
    },
    composeCommand: composeUpCommand,
    composeBuildCommand,
    listServiceContainersCommand,
    composeUpCommand,
    replacementStrategy: {
      buildBeforeCandidate: true,
      oldGatewaySleepMs: sleepMs,
      validationTimeoutMs,
      finalPromotedReconnectTimeoutMs,
      finalPromotedHeartbeatTimeoutMs,
      oldGatewayRestartPolicyBeforeSleep: "always",
      promoteOnlyAfterCandidateValidation: true,
      candidateValidationIsNotTerminalSuccess: true,
      finalPromotedComposeHeartbeatRequired: true,
      finalPromotedContainerMustNotBeCandidate: true,
      localRollbackToLastKnownGoodOnFinalReconnectFailure: true,
      backendCoreOwnsFinalTerminalStatus: true,
      candidateRestartPolicyAfterPromotion: "always",
      candidateFinalRestartPolicyValidation: true,
      candidateUsesOldContainerMounts: true,
      candidateUsesOldContainerNetworks: true,
      candidateUsesOldContainerExtraHosts: true,
      candidateUsesOldContainerEnvironment: true,
      candidateUsesHostPidNamespace: true,
      startupSelfHealsRestartPolicy: true,
      dockerStatusReportsRestartPolicyAndPidMode: true,
      refreshesCompactBuildContextBeforeBuild: true,
      removeScope: {
        projectLabel: config.upgradeComposeProject,
        serviceLabel: config.upgradeService,
      },
      noDeps: true,
      namedVolumesPreserved: true,
    },
    dockerRunCommand,
    finalPromotedHeartbeatConfirmation: {
      required: true,
      providerId: config.providerId,
      targetProviderGatewayVersion: targetGatewayMetadata.version,
      backendCoreNodeRegistry: "/api/nodes",
      heartbeatTimeoutMs: finalPromotedHeartbeatTimeoutMs,
      finalContainerMustNotEqualCandidate: candidateName,
      requiredResultFields: ["containerId", "version", "restartPolicy", "pidMode", "heartbeatTimestamp"],
      unavailableResult: unavailableFinalPromotedContainer,
    },
    finalPromotedContainer: unavailableFinalPromotedContainer,
    compactBuildContextCandidates,
    workspaceStateMount: `${config.upgradeHostProjectRoot.replace(/\/+$/, "")}/.state:${workspaceRoot}/.state`,
    upgradeCompletionPath,
  };
}

async function runProviderUpgrade(taskId: string, payload: Record<string, JsonValue>): Promise<JsonValue> {
  const mode = payload.mode === "schedule" ? "schedule" : "plan";
  const plan = upgradePlan(taskId);
  if (mode === "plan") return { mode, message: "provider gateway upgrade plan generated; no container changed", plan };
  const dockerRunCommand = (plan.dockerRunCommand as JsonValue[]).map((item) => String(item));
  const result = await runDockerCommand(dockerRunCommand.slice(1), 12_000);
  if (!result.ok) {
    throw new Error(`provider upgrade scheduler failed with exit ${result.exitCode}: ${result.stderr}`);
  }
  const sleepMs = Number((plan.replacementStrategy as Record<string, JsonValue>).oldGatewaySleepMs ?? 300_000);
  setTimeout(() => enterUpgradeSleep(sleepMs), 250).unref?.();
  return {
    mode,
    message: "provider gateway upgrade scheduled; backend-core must wait for final promoted compose heartbeat before terminal success",
    providerId: config.providerId,
    providerName: config.providerName,
    providerGatewayVersion: gatewayMetadata.version,
    targetProviderGatewayVersion: (plan.targetProviderGatewayVersion as string | undefined) ?? gatewayMetadata.version,
    updaterContainerId: result.stdout.trim(),
    terminalStatusManagedByBackendCore: true,
    candidateValidationIsNotTerminalSuccess: true,
    finalPromotedHeartbeatConfirmation: plan.finalPromotedHeartbeatConfirmation,
    finalPromotedContainer: plan.finalPromotedContainer,
    plan,
    stderr: result.stderr.slice(0, 500),
  };
}

function payloadNumber(payload: Record<string, JsonValue>, key: string, fallback: number): number {
  const raw = payload[key];
  const value = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : fallback;
  if (!Number.isFinite(value) || value <= 0) return fallback;
  return Math.floor(value);
}

function payloadBoundedNumber(payload: Record<string, JsonValue>, key: string, fallback: number, min: number, max: number): number {
  const raw = payload[key];
  const value = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : fallback;
  if (!Number.isFinite(value)) return fallback;
  return Math.max(min, Math.min(max, Math.floor(value)));
}

function payloadJsonArrayLimits(payload: Record<string, JsonValue>): Record<string, number> {
  const raw = payload.jsonArrayLimits;
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
  const limits: Record<string, number> = {};
  for (const [path, value] of Object.entries(raw)) {
    if (!/^[A-Za-z0-9_.-]+$/.test(path)) continue;
    const limit = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
    if (Number.isInteger(limit) && limit > 0 && limit <= 500) limits[path] = limit;
  }
  return limits;
}

function assertAllowedMicroserviceBase(rawBaseUrl: string): URL {
  const baseUrl = new URL(rawBaseUrl);
  if (baseUrl.protocol !== "http:") throw new Error(`microservice backend only supports http URLs, got ${baseUrl.protocol}`);
  const host = baseUrl.hostname.toLowerCase();
  const allowedHosts = new Set(["127.0.0.1", "localhost", "host.docker.internal", "todo-note", "code-queue"]);
  if (!allowedHosts.has(host)) throw new Error(`microservice backend host is not allowed: ${baseUrl.hostname}`);
  return baseUrl;
}

function arrayAtPath(value: unknown, path: string): JsonValue[] | null {
  let current: unknown = value;
  for (const part of path.split(".")) {
    if (typeof current !== "object" || current === null || Array.isArray(current)) return null;
    current = (current as Record<string, unknown>)[part];
  }
  return Array.isArray(current) ? current as JsonValue[] : null;
}

function applyJsonArrayLimits(bodyText: string, contentType: string, limits: Record<string, number>): { bodyText: string; transform: JsonValue } {
  const entries = Object.entries(limits);
  if (entries.length === 0) return { bodyText, transform: { applied: false } };
  if (!contentType.toLowerCase().includes("json")) {
    return { bodyText, transform: { applied: false, reason: "content-type is not json" } };
  }
  try {
    const parsed = JSON.parse(bodyText) as unknown;
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
      return { bodyText, transform: { applied: false, reason: "json root is not an object" } };
    }
    const root = parsed as Record<string, unknown>;
    const applied: Record<string, JsonValue> = {};
    for (const [path, limit] of entries) {
      const array = arrayAtPath(root, path);
      if (array === null) continue;
      const originalLength = array.length;
      if (array.length > limit) array.splice(limit);
      applied[path] = { limit, originalLength, returnedLength: array.length };
    }
    root._unidesk = { arrayLimits: applied };
    return { bodyText: JSON.stringify(parsed), transform: { applied: Object.keys(applied).length > 0, arrayLimits: applied } };
  } catch (error) {
    return { bodyText, transform: { applied: false, error: error instanceof Error ? error.message : String(error) } };
  }
}

function contentTypeIsJson(contentType: string): boolean {
  return contentType.toLowerCase().includes("json");
}

function boundedMicroserviceBodyText(
  bodyText: string,
  contentType: string,
  metadata: { serviceId: string; path: string; status: number; upstreamBodyBytes: number },
): { bodyText: string; truncated: boolean } {
  if (bodyText.length <= microserviceHttpMaxBodyTextLength) {
    return { bodyText, truncated: false };
  }
  if (contentTypeIsJson(contentType)) {
    return {
      bodyText: JSON.stringify({
        ok: false,
        error: "microservice proxy response body is too large",
        serviceId: metadata.serviceId,
        path: metadata.path,
        upstreamStatus: metadata.status,
        upstreamBodyBytes: metadata.upstreamBodyBytes,
        transformedBodyBytes: bodyText.length,
        responseBodyLimitBytes: microserviceHttpMaxBodyTextLength,
        hint: "Use a paged endpoint or tighten __unideskArrayLimit so the response stays below the proxy safety limit.",
      }),
      truncated: true,
    };
  }
  return { bodyText: truncateText(bodyText, microserviceHttpMaxBodyTextLength), truncated: true };
}

function microserviceHttpCacheKey(serviceId: string, targetBaseUrl: string, method: string, path: string, query: string, jsonArrayLimits: Record<string, number>): string {
  return JSON.stringify([serviceId, targetBaseUrl, method, path, query, jsonArrayLimits]);
}

function microserviceHttpCachePrefix(serviceId: string, targetBaseUrl: string): string {
  return JSON.stringify([serviceId, targetBaseUrl]).slice(0, -1);
}

function cloneJsonObject(value: JsonValue): Record<string, JsonValue> {
  return value && typeof value === "object" && !Array.isArray(value) ? { ...value as Record<string, JsonValue> } : { value };
}

function resultWithCacheMetadata(result: JsonValue, metadata: Record<string, JsonValue>): JsonValue {
  const record = cloneJsonObject(result);
  record.cache = metadata;
  return record;
}

function readMicroserviceHttpCache(key: string): JsonValue | null {
  const entry = microserviceHttpCache.get(key);
  if (entry === undefined) return null;
  const now = Date.now();
  if (entry.expiresAt <= now) {
    microserviceHttpCache.delete(key);
    return null;
  }
  return resultWithCacheMetadata(entry.result, {
    hit: true,
    ageMs: now - entry.createdAt,
    ttlRemainingMs: entry.expiresAt - now,
    layer: "provider-gateway",
  });
}

function rememberMicroserviceHttpCache(key: string, ttlMs: number, result: JsonValue): void {
  if (ttlMs <= 0 || !result || typeof result !== "object" || Array.isArray(result)) return;
  const record = result as Record<string, JsonValue>;
  if (record.ok !== true || record.upstreamOk !== true) return;
  if (record.truncated === true) return;
  const now = Date.now();
  microserviceHttpCache.set(key, { createdAt: now, expiresAt: now + ttlMs, result: { ...record } });
  if (microserviceHttpCache.size > 300) {
    for (const [cacheKey, entry] of microserviceHttpCache) {
      if (entry.expiresAt <= now || microserviceHttpCache.size > 240) microserviceHttpCache.delete(cacheKey);
    }
  }
}

function invalidateMicroserviceHttpCache(serviceId: string, targetBaseUrl: string): void {
  const prefix = microserviceHttpCachePrefix(serviceId, targetBaseUrl);
  for (const key of microserviceHttpCache.keys()) {
    if (key.startsWith(prefix)) microserviceHttpCache.delete(key);
  }
}

function headersFromMicroserviceRequest(requestHeaders: Record<string, JsonValue>): Headers {
  const headers = new Headers();
  for (const name of microserviceForwardRequestHeaders) {
    const value = requestHeaders[name];
    if (typeof value === "string" && value.length > 0) headers.set(name, value);
  }
  const cookie = requestHeaders.cookie;
  const xAuth = requestHeaders["x-auth"];
  if ((typeof cookie !== "string" || cookie.length === 0) && typeof xAuth === "string" && xAuth.length > 0) {
    headers.set("cookie", xAuth);
  }
  return headers;
}

function forwardedMicroserviceResponseHeaders(response: Response): Record<string, JsonValue> {
  const headers: Record<string, JsonValue> = {};
  for (const name of microserviceForwardResponseHeaders) {
    const value = response.headers.get(name);
    if (value !== null && value.length > 0) headers[name] = value.slice(0, 8192);
  }
  return headers;
}

async function runMicroserviceHttp(payload: Record<string, JsonValue>): Promise<JsonValue> {
  const rawMethod = String(payload.method || "GET").toUpperCase();
  const allowedMethods = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]);
  const method = allowedMethods.has(rawMethod) ? rawMethod : "GET";
  const targetBaseUrl = payloadString(payload, "targetBaseUrl");
  if (targetBaseUrl === null) throw new Error("microservice.http requires targetBaseUrl");
  const path = payloadString(payload, "path") ?? "/";
  const query = payloadString(payload, "query") ?? "";
  if (!path.startsWith("/")) throw new Error("microservice.http path must start with /");
  if (query.length > 0 && !query.startsWith("?")) throw new Error("microservice.http query must start with ?");
  const baseUrl = assertAllowedMicroserviceBase(targetBaseUrl);
  const url = new URL(path, baseUrl);
  url.search = query;
  const timeoutMs = Math.max(1000, Math.min(30_000, payloadNumber(payload, "timeoutMs", 10_000)));
  const jsonArrayLimits = payloadJsonArrayLimits(payload);
  const bodyText = payloadString(payload, "bodyText") ?? "";
  const serviceId = payloadString(payload, "serviceId") ?? "unknown";
  const cacheTtlMs = payloadBoundedNumber(payload, "cacheTtlMs", 0, 0, 60_000);
  const cacheable = cacheTtlMs > 0 && (method === "GET" || method === "HEAD") && bodyText.length === 0;
  const cacheKey = cacheable ? microserviceHttpCacheKey(serviceId, targetBaseUrl, method, path, query, jsonArrayLimits) : "";
  if (cacheable) {
    const cached = readMicroserviceHttpCache(cacheKey);
    if (cached !== null) return cached;
    const inFlight = microserviceHttpInFlight.get(cacheKey);
    if (inFlight !== undefined) {
      const result = await inFlight;
      return resultWithCacheMetadata(result, {
        hit: true,
        deduped: true,
        layer: "provider-gateway",
      });
    }
  } else if (method !== "GET" && method !== "HEAD") {
    invalidateMicroserviceHttpCache(serviceId, targetBaseUrl);
  }
  const requestHeaders = typeof payload.requestHeaders === "object" && payload.requestHeaders !== null && !Array.isArray(payload.requestHeaders)
    ? payload.requestHeaders as Record<string, JsonValue>
    : {};
  const headers = headersFromMicroserviceRequest(requestHeaders);
  const requestStartedAt = Date.now();
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  const requestPromise = (async (): Promise<JsonValue> => {
    const response = await fetch(url, {
      method,
      headers,
      body: method === "GET" || method === "HEAD" ? undefined : bodyText,
      signal: controller.signal,
    });
    const rawBodyText = await response.text();
    const contentType = response.headers.get("content-type") ?? "text/plain; charset=utf-8";
    const transformed = applyJsonArrayLimits(rawBodyText, contentType, jsonArrayLimits);
    const bounded = boundedMicroserviceBodyText(transformed.bodyText, contentType, {
      serviceId,
      path,
      status: response.status,
      upstreamBodyBytes: rawBodyText.length,
    });
    return {
      ok: true,
      serviceId,
      method,
      url: url.toString(),
      status: response.status,
      upstreamOk: response.ok,
      contentType,
      bodyText: bounded.bodyText,
      upstreamBodyBytes: rawBodyText.length,
      transformedBodyBytes: transformed.bodyText.length,
      returnedBodyBytes: bounded.bodyText.length,
      responseBodyLimitBytes: microserviceHttpMaxBodyTextLength,
      truncated: bounded.truncated,
      responseHeaders: forwardedMicroserviceResponseHeaders(response),
      transform: transformed.transform,
      upstreamDurationMs: Date.now() - requestStartedAt,
      proxyMode: "provider-gateway-http-fetch",
    };
  })();
  if (cacheable) microserviceHttpInFlight.set(cacheKey, requestPromise);
  try {
    const result = await requestPromise;
    if (cacheable) rememberMicroserviceHttpCache(cacheKey, cacheTtlMs, result);
    return result;
  } catch (error) {
    return {
      ok: false,
      serviceId,
      method,
      url: url.toString(),
      timeoutMs,
      error: error instanceof Error ? error.message : String(error),
    };
  } finally {
    if (cacheable && microserviceHttpInFlight.get(cacheKey) === requestPromise) microserviceHttpInFlight.delete(cacheKey);
    clearTimeout(timer);
  }
}

async function handleDispatch(message: CoreDispatchMessage): Promise<void> {
  logger("info", "dispatch_received", { taskId: message.taskId, command: message.command, payload: message.payload });
  const verboseLifecycle = message.command !== "microservice.http";
  if (verboseLifecycle) await sendTaskStatus(message.taskId, "accepted", "provider accepted task");
  try {
    if (verboseLifecycle) await sendTaskStatus(message.taskId, "running", "provider running task");
    if (message.command === "docker.ps") {
      const result = await runDockerPs();
      await sendTaskStatus(message.taskId, "succeeded", "docker ps completed", result);
      return;
    }
    if (message.command === "provider.upgrade") {
      const result = await runProviderUpgrade(message.taskId, message.payload);
      const scheduleMode = message.payload.mode === "schedule";
      await sendTaskStatus(
        message.taskId,
        scheduleMode ? "running" : "succeeded",
        scheduleMode
          ? "provider upgrade scheduled; awaiting backend-core final promoted heartbeat"
          : "provider upgrade command completed",
        result,
      );
      return;
    }
    if (message.command === "host.ssh") {
      const result = await runHostSsh(message.payload);
      if ((result as { ok?: unknown }).ok !== true) {
        await sendTaskStatus(message.taskId, "failed", "host SSH command failed", result);
        return;
      }
      await sendTaskStatus(message.taskId, "succeeded", "host SSH command completed", result);
      return;
    }
    if (message.command === "microservice.http") {
      const result = await runMicroserviceHttp(message.payload);
      if ((result as { ok?: unknown }).ok !== true) {
        await sendTaskStatus(message.taskId, "failed", "microservice HTTP proxy failed", result);
        return;
      }
      await sendTaskStatus(message.taskId, "succeeded", "microservice HTTP proxy completed", result);
      return;
    }
    await sendTaskStatus(message.taskId, "succeeded", "echo completed", { echo: message.payload });
  } catch (error) {
    const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
    logger("error", "dispatch_failed", { taskId: message.taskId, error: text });
    await sendTaskStatus(message.taskId, "failed", text, { error: text });
  }
}

async function handleHttpTunnelRequest(message: CoreHttpTunnelRequestMessage): Promise<void> {
  const startedAt = Date.now();
  try {
    const result = await runMicroserviceHttp(message.payload);
    sendJson({
      type: "http_tunnel_response",
      providerId: config.providerId,
      requestId: message.requestId,
      ok: (result as { ok?: unknown }).ok === true,
      result,
      at: new Date().toISOString(),
    });
    logger("debug", "http_tunnel_completed", {
      requestId: message.requestId,
      serviceId: typeof message.payload.serviceId === "string" ? message.payload.serviceId : "",
      durationMs: Date.now() - startedAt,
      ok: (result as { ok?: unknown }).ok === true,
    });
  } catch (error) {
    const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
    logger("error", "http_tunnel_failed", { requestId: message.requestId, error: text });
    sendJson({
      type: "http_tunnel_response",
      providerId: config.providerId,
      requestId: message.requestId,
      ok: false,
      result: { ok: false, error: text },
      at: new Date().toISOString(),
    });
  }
}

function handleMessage(raw: MessageEvent<string>): void {
  try {
    const parsed = JSON.parse(raw.data) as { type?: unknown };
    if (parsed.type === "dispatch") {
      handleDispatch(parsed as CoreDispatchMessage).catch((error) => logger("error", "dispatch_handler_failed", { error: String(error) }));
      return;
    }
    if (parsed.type === "http_tunnel_request") {
      handleHttpTunnelRequest(parsed as CoreHttpTunnelRequestMessage).catch((error) => logger("error", "http_tunnel_handler_failed", { error: String(error) }));
      return;
    }
    if (parsed.type === "host_ssh_open") {
      startHostSshSession(parsed as CoreHostSshOpenMessage);
      return;
    }
    if (
      parsed.type === "egress_tcp_opened" ||
      parsed.type === "egress_tcp_data" ||
      parsed.type === "egress_tcp_close"
    ) {
      providerEgressProxy?.handleCoreMessage(parsed as CoreEgressTcpOpenedMessage | CoreEgressTcpDataMessage | CoreEgressTcpCloseMessage);
      return;
    }
    logger("debug", "core_message", parsed as JsonValue);
  } catch (error) {
    logger("error", "core_message_parse_failed", { error: String(error) });
  }
}

function scheduleReconnect(): void {
  if (stopping) return;
  const sleepRemainingMs = upgradeSleepUntil - Date.now();
  if (sleepRemainingMs > 0) {
    logger("warn", "reconnect_deferred_for_upgrade_sleep", { sleepRemainingMs });
    if (upgradeSleepTimer === null) {
      upgradeSleepTimer = setTimeout(() => {
        upgradeSleepTimer = null;
        logger("warn", "upgrade_sleep_expired_reconnecting", { providerId: config.providerId });
        connect();
      }, sleepRemainingMs);
    }
    return;
  }
  reconnectAttempt += 1;
  const delayMs = Math.min(config.reconnectMaxMs, config.reconnectBaseMs * 2 ** Math.min(reconnectAttempt, 8));
  logger("warn", "reconnect_scheduled", { reconnectAttempt, delayMs });
  setTimeout(connect, delayMs);
}

function clearConnectionTimers(): void {
  if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
  if (systemStatusTimer !== null) clearInterval(systemStatusTimer);
  if (dockerStatusTimer !== null) clearInterval(dockerStatusTimer);
  heartbeatTimer = null;
  systemStatusTimer = null;
  dockerStatusTimer = null;
}

function enterUpgradeSleep(durationMs: number): void {
  const sleepMs = Math.max(30_000, Math.min(300_000, Math.floor(durationMs)));
  upgradeSleepUntil = Date.now() + sleepMs;
  logger("warn", "upgrade_sleep_enter", { providerId: config.providerId, sleepMs, wakeAt: new Date(upgradeSleepUntil).toISOString() });
  clearConnectionTimers();
  closeSshDataPool("provider upgrade sleep");
  try {
    socket?.close(4000, "provider upgrade sleep");
  } catch (error) {
    logger("warn", "upgrade_sleep_socket_close_failed", { error: String(error) });
  }
  socket = null;
  if (upgradeSleepTimer !== null) clearTimeout(upgradeSleepTimer);
  upgradeSleepTimer = setTimeout(() => {
    upgradeSleepTimer = null;
    logger("warn", "upgrade_sleep_expired_reconnecting", { providerId: config.providerId });
    connect();
  }, sleepMs);
}

function connect(): void {
  if (stopping) return;
  const sleepRemainingMs = upgradeSleepUntil - Date.now();
  if (sleepRemainingMs > 0) {
    logger("warn", "connect_deferred_for_upgrade_sleep", { sleepRemainingMs });
    if (upgradeSleepTimer === null) {
      upgradeSleepTimer = setTimeout(() => {
        upgradeSleepTimer = null;
        connect();
      }, sleepRemainingMs);
    }
    return;
  }
  const url = withToken(config.serverUrl, config.token);
  logger("info", "connect_start", { serverUrl: config.serverUrl, providerId: config.providerId });
  socket = new WebSocket(url);
  socket.addEventListener("open", () => {
    reconnectAttempt = 0;
    logger("info", "connect_open", { providerId: config.providerId });
    sendRegister();
    if (isHostSshConfigured()) {
      sshDataPoolDesired = true;
      ensureSshDataPool();
    }
    sendHeartbeat();
    sendSystemStatus().catch((error) => logger("error", "system_status_initial_failed", { error: String(error) }));
    sendDockerStatus().catch((error) => logger("error", "docker_status_initial_failed", { error: String(error) }));
    if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
    if (systemStatusTimer !== null) clearInterval(systemStatusTimer);
    if (dockerStatusTimer !== null) clearInterval(dockerStatusTimer);
    heartbeatTimer = setInterval(sendHeartbeat, config.heartbeatIntervalMs);
    systemStatusTimer = setInterval(() => {
      sendSystemStatus().catch((error) => logger("error", "system_status_interval_failed", { error: String(error) }));
    }, config.heartbeatIntervalMs);
    dockerStatusTimer = setInterval(() => {
      sendDockerStatus().catch((error) => logger("error", "docker_status_interval_failed", { error: String(error) }));
    }, config.heartbeatIntervalMs);
  });
  socket.addEventListener("message", (event) => handleMessage(event as MessageEvent<string>));
  socket.addEventListener("close", (event) => {
    logger("warn", "connect_close", { code: event.code, reason: event.reason });
    clearConnectionTimers();
    closeSshDataPool("provider control socket closed");
    providerEgressProxy?.closeAll("provider-gateway core socket closed");
    scheduleReconnect();
  });
  socket.addEventListener("error", () => {
    logger("error", "connect_error", { providerId: config.providerId });
  });
}

process.on("SIGTERM", () => {
  stopping = true;
  logger("warn", "sigterm_received");
  if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
  if (systemStatusTimer !== null) clearInterval(systemStatusTimer);
  if (dockerStatusTimer !== null) clearInterval(dockerStatusTimer);
  for (const [sessionId, session] of hostSshSessions) {
    logger("warn", "host_ssh_session_terminated_by_shutdown", { sessionId });
    session.proc.kill("SIGTERM");
  }
  hostSshSessions.clear();
  providerEgressProxy?.close();
  closeSshDataPool("provider shutdown");
  socket?.close(1000, "provider shutdown");
  process.exit(0);
});

if (config.egressProxyEnabled) {
  providerEgressProxy = startProviderEgressProxy({
    providerId: config.providerId,
    listenHost: config.egressProxyListenHost,
    listenPort: config.egressProxyPort,
    openTimeoutMs: config.egressProxyOpenTimeoutMs,
    idleTimeoutMs: config.egressProxyIdleTimeoutMs,
    maxTunnelAgeMs: config.egressProxyMaxTunnelAgeMs,
    staleTunnelIdleMs: config.egressProxyStaleTunnelIdleMs,
    maxPendingBytes: config.egressProxyMaxPendingBytes,
    isCoreConnected: () => socket?.readyState === WebSocket.OPEN,
    sendToCore: (message) => sendJsonOk(message),
    logger,
    onStatusChanged: () => sendHeartbeat(),
  });
}

connect();
