// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. config module for scripts/src/platform-infra-sub2api-codex.ts.

// Moved mechanically from scripts/src/platform-infra-sub2api-codex.ts:1317-2034 for #903.

import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "../platform-infra-public-service";
import { shortSha256Fingerprint } from "../platform-infra-ops-library";
import {
  codexPoolSentinelSummary,
  codexPoolSentinelRuntimeImage,
  readCodexPoolSentinelConfig,
  renderCodexPoolSentinelManifest,
  type CodexPoolSentinelConfig,
  type CodexPoolSentinelProfileSecret,
} from "../platform-infra-sub2api-codex-sentinel";
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";

import type { CodexPoolCaddyEdgeRetryConfig, CodexPoolConfig, CodexPoolCreditRankTier, CodexPoolCreditsConfig, CodexPoolLocalCodexConfig, CodexPoolManualAccountGroupBinding, CodexPoolManualAccountProtection, CodexPoolManualAccountProxyBinding, CodexPoolManualAccountsConfig, CodexPoolManualBindingKind, CodexPoolManualBindingProvider, CodexPoolManualBindingSource, CodexPoolManualBindingSourcesConfig, CodexPoolProfileConfig, CodexPoolProfitConfig, CodexPoolPublicExposureConfig, CodexProfile, CodexRuntimeConfig, CodexRuntimeTemplate, CodexSentinelProtectPolicy, CodexTempUnschedulablePolicy, CodexTempUnschedulableRule, OpenAIResponsesWebSocketsV2Mode } from "./types";
import { booleanValue, integerArrayConfigField, integerConfigField, isRecord, normalizeBaseUrl, numberValue, readRequiredBaseUrl, readRequiredDescription, readRequiredEmail, readRequiredPort, readRequiredPositiveNumber, requiredRecordConfigField, requiredStringConfigField, stringValue, validateKubernetesName } from "./config-utils";
import { readAuthAPIKey, uniqueAccountName } from "./redaction";
import { codexPoolConfigPath } from "./types";

export function collectCodexProfiles(): CodexProfile[] {
  const codexDir = join(homedir(), ".codex");
  const pool = readCodexPoolConfig();
  if (!existsSync(codexDir)) return [];
  const seenAccountNames = new Set<string>();
  return pool.profiles.map((entry) => {
    const resolved = resolveProfileFiles(codexDir, entry);
    const profile = entry.profile;
    const accountName = entry.accountName ?? uniqueAccountName(profile, seenAccountNames);
    seenAccountNames.add(accountName);
    const configFile = resolved.configFile;
    const authFile = resolved.authFile;
    const configPath = join(codexDir, configFile);
    const authPath = join(codexDir, authFile);
    const base: CodexProfile = {
      profile,
      accountName,
      configFile,
      authFile,
      provider: "",
      baseUrl: "",
      wireApi: null,
      model: null,
      envKey: null,
      apiKey: null,
      apiKeySource: null,
      openaiResponsesWebSocketsV2Mode: entry.openaiResponsesWebSocketsV2Mode,
      upstreamUserAgent: entry.upstreamUserAgent,
      trustUpstream: entry.trustUpstream,
      sentinelProtect: entry.sentinelProtect,
      priority: entry.priority,
      capacity: entry.capacity ?? pool.defaultAccountCapacity,
      loadFactor: entry.loadFactor ?? pool.defaultAccountLoadFactor,
      tempUnschedulableTemplate: entry.tempUnschedulableTemplate,
      tempUnschedulable: entry.tempUnschedulable,
      authOpenAIKeyShape: existsSync(authPath) ? "unknown" : "missing",
      ok: false,
      error: null,
    };

    try {
      if (!existsSync(configPath)) throw new Error(`config file ${configFile} is missing`);
      const parsed = Bun.TOML.parse(readFileSync(configPath, "utf8")) as unknown;
      if (!isRecord(parsed)) throw new Error("config is not a TOML object");
      const providers = parsed.model_providers;
      if (!isRecord(providers)) throw new Error("model_providers is missing");
      const provider = stringValue(parsed.model_provider) ?? Object.keys(providers)[0] ?? "";
      if (provider === "") throw new Error("model_provider is missing");
      const providerConfig = providers[provider];
      if (!isRecord(providerConfig)) throw new Error(`model provider ${provider} is missing`);
      const baseUrl = normalizeBaseUrl(stringValue(providerConfig.base_url));
      if (baseUrl === null) throw new Error(`model provider ${provider} base_url is missing or invalid`);
      base.provider = provider;
      base.baseUrl = baseUrl;
      base.envKey = stringValue(providerConfig.env_key);
      base.wireApi = stringValue(providerConfig.wire_api);
      base.model = stringValue(parsed.model);

      const auth = readAuthAPIKey(authPath);
      base.authOpenAIKeyShape = auth.shape;
      if (auth.apiKey !== null) {
        base.apiKey = auth.apiKey;
        base.apiKeySource = "auth-json";
      } else if (base.envKey !== null && typeof process.env[base.envKey] === "string" && process.env[base.envKey]!.length > 0) {
        base.apiKey = process.env[base.envKey]!;
        base.apiKeySource = "env";
      }
      if (base.apiKey === null || base.apiKey.length === 0) {
        throw new Error(base.envKey === null ? "auth OPENAI_API_KEY is missing or empty" : `auth OPENAI_API_KEY is missing and env ${base.envKey} is not present`);
      }
      base.ok = true;
      return base;
    } catch (error) {
      base.error = error instanceof Error ? error.message : String(error);
      return base;
    }
  });
}

export function resolveProfileFiles(codexDir: string, profile: CodexPoolProfileConfig): { configFile: string; authFile: string } {
  const configFile = existsSync(join(codexDir, profile.configFile)) || profile.fallbackConfigFile === null
    ? profile.configFile
    : profile.fallbackConfigFile;
  const authFile = existsSync(join(codexDir, profile.authFile)) || profile.fallbackAuthFile === null
    ? profile.authFile
    : profile.fallbackAuthFile;
  return { configFile, authFile };
}

export function readCodexPoolConfig(): CodexPoolConfig {
  if (!existsSync(codexPoolConfigPath)) throw new Error(`${codexPoolConfigPath} is required`);
  const parsed = Bun.YAML.parse(readFileSync(codexPoolConfigPath, "utf8")) as unknown;
  if (!isRecord(parsed)) throw new Error(`${codexPoolConfigPath} must contain a YAML object`);
  const version = integerConfigField(parsed, "version", "");
  if (version !== 1) throw new Error(`${codexPoolConfigPath}.version must be 1`);
  const kind = requiredStringConfigField(parsed, "kind", "");
  if (kind !== "platform-infra-sub2api-codex-pool") throw new Error(`${codexPoolConfigPath}.kind must be platform-infra-sub2api-codex-pool`);
  const metadata = requiredRecordConfigField(parsed, "metadata", "");
  const pool = parsed.pool;
  if (!isRecord(pool)) throw new Error(`${codexPoolConfigPath}.pool must be a YAML object`);
  if (!isRecord(parsed.profiles)) throw new Error(`${codexPoolConfigPath}.profiles must be a YAML object`);
  rejectSchedulableYamlField(pool, "pool");
  const runtime = readRuntimeConfig(parsed.runtime);
  const defaultTempUnschedulableTemplate = readRuntimeTemplateRef(
    pool.defaultTempUnschedulableTemplate,
    "pool.defaultTempUnschedulableTemplate",
    runtime,
  );
  const defaultTempUnschedulable = runtime.templatesById[defaultTempUnschedulableTemplate]!.tempUnschedulable;
  const defaultAccountPriorityValue = readAccountPriority(pool.defaultAccountPriority, "pool.defaultAccountPriority");
  const defaultAccountCapacityValue = readAccountCapacity(pool.defaultAccountCapacity, "pool.defaultAccountCapacity");
  const defaultAccountLoadFactorValue = readAccountLoadFactor(pool.defaultAccountLoadFactor, "pool.defaultAccountLoadFactor");
  const defaultSentinelProtect = readSentinelProtectPolicy(pool.defaultSentinelProtect, "pool.defaultSentinelProtect");
  const profiles = readProfileConfig(
    parsed.profiles,
    runtime,
    defaultTempUnschedulableTemplate,
    defaultTempUnschedulable,
    defaultSentinelProtect,
    defaultAccountPriorityValue,
  );
  const manualAccounts = readManualAccountsConfig(parsed.manualAccounts);
  assertProtectedManualAccountsNotManaged(profiles, manualAccounts);
  const declaredAccountCapacity = desiredProfileCapacityTotal(profiles, defaultAccountCapacityValue);
  const minOwnerConcurrencySource = pool.minOwnerConcurrency === undefined || pool.minOwnerConcurrency === null ? "auto" : "yaml";
  const minOwnerConcurrency = minOwnerConcurrencySource === "auto"
    ? Math.max(1, declaredAccountCapacity)
    : readOwnerConcurrency(pool.minOwnerConcurrency, "pool.minOwnerConcurrency");
  const config: CodexPoolConfig = {
    version,
    kind,
    metadata: {
      id: requiredStringConfigField(metadata, "id", "metadata"),
      owner: requiredStringConfigField(metadata, "owner", "metadata"),
      relatedIssues: integerArrayConfigField(metadata, "relatedIssues", "metadata"),
    },
    groupName: requiredStringConfigField(pool, "groupName", "pool"),
    groupDescription: readRequiredDescription(pool.groupDescription, "pool.groupDescription", 300),
    apiKeyName: requiredStringConfigField(pool, "apiKeyName", "pool"),
    apiKeySecretName: requiredStringConfigField(pool, "apiKeySecretName", "pool"),
    apiKeySecretKey: requiredStringConfigField(pool, "apiKeySecretKey", "pool"),
    adminEmailDefault: readRequiredEmail(pool.adminEmailDefault, "pool.adminEmailDefault"),
    minOwnerBalanceUsd: readRequiredPositiveNumber(pool.minOwnerBalanceUsd, "pool.minOwnerBalanceUsd"),
    minOwnerConcurrency,
    minOwnerConcurrencySource,
    defaultAccountPriority: defaultAccountPriorityValue,
    defaultAccountCapacity: defaultAccountCapacityValue,
    defaultAccountLoadFactor: defaultAccountLoadFactorValue,
    defaultTempUnschedulableTemplate,
    defaultTempUnschedulable,
    defaultSentinelProtect,
    profiles,
    runtime,
    profit: readProfitConfig(parsed.profit),
    credits: readCreditsConfig(parsed.credits),
    manualAccounts,
    publicExposure: readPublicExposureConfig(parsed.publicExposure),
    localCodex: readLocalCodexConfig(parsed.localCodex),
    sentinel: {
      ...readCodexPoolSentinelConfig(parsed.sentinel, codexPoolConfigPath),
      protectedManualAccounts: manualAccounts.protected.map((account) => account.accountName),
    },
  };
  validateKubernetesName(config.groupName, "pool.groupName", false);
  validateKubernetesName(config.apiKeySecretName, "pool.apiKeySecretName", true);
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(config.apiKeySecretKey)) {
    throw new Error(`${codexPoolConfigPath}.pool.apiKeySecretKey must be a valid secret key name`);
  }
  if (config.minOwnerBalanceUsd <= 0) throw new Error(`${codexPoolConfigPath}.pool.minOwnerBalanceUsd must be > 0`);
  if (declaredAccountCapacity > 0 && config.minOwnerConcurrency < declaredAccountCapacity) {
    throw new Error(`${codexPoolConfigPath}.pool.minOwnerConcurrency must be >= the sum of declared account capacities (${declaredAccountCapacity})`);
  }
  return config;
}

function readCreditsConfig(value: unknown): CodexPoolCreditsConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.credits must be a YAML object`);
  const timezone = requiredStringConfigField(value, "timezone", "credits");
  const defaultRankTiers = readDefaultCreditRankTiers(value.defaultRankTiers);
  const excludedUsers = readUniqueStringArray(value.excludedUsers, "credits.excludedUsers", true);
  if (excludedUsers.some((selector) => selector.length > 256 || /[\r\n\0]/u.test(selector))) {
    throw new Error(`${codexPoolConfigPath}.credits.excludedUsers contains an unsupported selector`);
  }
  if (new Set(excludedUsers.map((selector) => selector.toLowerCase())).size !== excludedUsers.length) {
    throw new Error(`${codexPoolConfigPath}.credits.excludedUsers must not contain case-insensitive duplicates`);
  }
  try {
    new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(new Date(0));
  } catch {
    throw new Error(`${codexPoolConfigPath}.credits.timezone must be a valid IANA timezone`);
  }
  return { timezone, defaultRankTiers, excludedUsers };
}

function readDefaultCreditRankTiers(value: unknown): CodexPoolCreditRankTier[] {
  const path = `${codexPoolConfigPath}.credits.defaultRankTiers`;
  if (!Array.isArray(value) || value.length === 0) throw new Error(`${path} must be a non-empty YAML array`);
  return value.map((item, index) => {
    const itemPath = `${path}[${index}]`;
    if (!isRecord(item)) throw new Error(`${itemPath} must be a YAML object`);
    const amountUsd = readRequiredPositiveNumber(item.amountUsd, `credits.defaultRankTiers[${index}].amountUsd`);
    const isLast = index === value.length - 1;
    if (isLast) {
      if (item.remaining !== true) throw new Error(`${itemPath}.remaining must be true for the final tier`);
      if (item.count !== undefined) throw new Error(`${itemPath}.count is not allowed on the remaining tier`);
      return { count: null, amountUsd };
    }
    if (item.remaining !== undefined) throw new Error(`${itemPath}.remaining is only allowed on the final tier`);
    const count = numberValue(item.count);
    if (count === null || !Number.isSafeInteger(count) || count <= 0) {
      throw new Error(`${itemPath}.count must be a positive integer`);
    }
    return { count, amountUsd };
  });
}

function readProfitConfig(value: unknown): CodexPoolProfitConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.profit must be a YAML object`);
  const groups = requiredRecordConfigField(value, "groups", "profit");
  const profitable = readUniqueStringArray(groups.profitable, "profit.groups.profitable", false);
  const selfUse = readUniqueStringArray(groups.selfUse, "profit.groups.selfUse", false);
  const excluded = readUniqueStringArray(groups.excluded, "profit.groups.excluded", true);
  const allGroups = [...profitable, ...selfUse, ...excluded];
  if (new Set(allGroups).size !== allGroups.length) {
    throw new Error(`${codexPoolConfigPath}.profit.groups entries must be disjoint`);
  }
  const saleRateCnyPerApiUsd = numberValue(value.saleRateCnyPerApiUsd);
  if (saleRateCnyPerApiUsd === null || saleRateCnyPerApiUsd < 0) {
    throw new Error(`${codexPoolConfigPath}.profit.saleRateCnyPerApiUsd must be >= 0`);
  }
  const defaultWindow = requiredStringConfigField(value, "defaultWindow", "profit");
  if (!/^[1-9][0-9]*(?:s|m|h|d)$/u.test(defaultWindow)) {
    throw new Error(`${codexPoolConfigPath}.profit.defaultWindow must be a duration such as 24h`);
  }
  const usageCostField = requiredStringConfigField(value, "usageCostField", "profit");
  if (usageCostField !== "actual_cost") {
    throw new Error(`${codexPoolConfigPath}.profit.usageCostField must be actual_cost`);
  }
  const accountCostSource = requiredStringConfigField(value, "accountCostSource", "profit");
  if (accountCostSource !== "account-name-trailing-cny-per-api-usd") {
    throw new Error(`${codexPoolConfigPath}.profit.accountCostSource must be account-name-trailing-cny-per-api-usd`);
  }
  if (!Array.isArray(value.periodCosts)) {
    throw new Error(`${codexPoolConfigPath}.profit.periodCosts must be an array`);
  }
  const periodCostIds = new Set<string>();
  const periodCosts = value.periodCosts.map((item, index) => {
    const path = `profit.periodCosts[${index}]`;
    if (!isRecord(item)) throw new Error(`${codexPoolConfigPath}.${path} must be an object`);
    const id = requiredStringConfigField(item, "id", path);
    if (periodCostIds.has(id)) throw new Error(`${codexPoolConfigPath}.profit.periodCosts contains duplicate id ${id}`);
    periodCostIds.add(id);
    const period = requiredStringConfigField(item, "period", path);
    if (!/^\d{4}-(?:0[1-9]|1[0-2])$/u.test(period)) {
      throw new Error(`${codexPoolConfigPath}.${path}.period must use YYYY-MM`);
    }
    const accountName = requiredStringConfigField(item, "accountName", path);
    const amountCny = numberValue(item.amountCny);
    if (amountCny === null || amountCny < 0) throw new Error(`${codexPoolConfigPath}.${path}.amountCny must be >= 0`);
    const description = requiredStringConfigField(item, "description", path);
    return { id, period, accountName, amountCny, description };
  });
  return {
    defaultWindow,
    usageCostField,
    accountCostSource,
    saleRateCnyPerApiUsd,
    periodCosts,
    groups: { profitable, selfUse, excluded },
  };
}

function readUniqueStringArray(value: unknown, path: string, allowEmpty: boolean): string[] {
  if (!Array.isArray(value) || !value.every((item) => typeof item === "string" && item.trim().length > 0)) {
    throw new Error(`${codexPoolConfigPath}.${path} must be an array of non-empty strings`);
  }
  const normalized = value.map((item) => item.trim());
  if (!allowEmpty && normalized.length === 0) throw new Error(`${codexPoolConfigPath}.${path} must not be empty`);
  if (new Set(normalized).size !== normalized.length) throw new Error(`${codexPoolConfigPath}.${path} must not contain duplicates`);
  return normalized;
}

export function readRuntimeConfig(value: unknown): CodexRuntimeConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.runtime must be a YAML object`);
  if (!Array.isArray(value.templates)) throw new Error(`${codexPoolConfigPath}.runtime.templates must be a YAML array`);
  const templates: CodexRuntimeTemplate[] = value.templates.map((item, index) => {
    if (!isRecord(item)) throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}] must be a YAML object`);
    const id = requiredStringConfigField(item, "id", `runtime.templates[${index}]`);
    if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {
      throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}].id has an unsupported format`);
    }
    const kind = requiredStringConfigField(item, "kind", `runtime.templates[${index}]`);
    if (kind !== "temp-unschedulable") {
      throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}].kind must be temp-unschedulable`);
    }
    return {
      id,
      kind,
      description: readRequiredDescription(item.description, `runtime.templates[${index}].description`, 300),
      tempUnschedulable: readTempUnschedulablePolicy(item.spec, `runtime.templates[${index}].spec`),
    };
  });
  if (templates.length === 0) throw new Error(`${codexPoolConfigPath}.runtime.templates must not be empty`);
  const templatesById: Record<string, CodexRuntimeTemplate> = {};
  for (const template of templates) {
    if (templatesById[template.id] !== undefined) throw new Error(`${codexPoolConfigPath}.runtime.templates duplicates id ${template.id}`);
    templatesById[template.id] = template;
  }
  if (!Array.isArray(value.errorPassthroughRules)) {
    throw new Error(`${codexPoolConfigPath}.runtime.errorPassthroughRules must be a YAML array`);
  }
  const errorPassthroughRules = value.errorPassthroughRules.map((item, index) => {
    const path = `runtime.errorPassthroughRules[${index}]`;
    if (!isRecord(item)) throw new Error(`${codexPoolConfigPath}.${path} must be a YAML object`);
    const id = requiredStringConfigField(item, "id", path);
    if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) throw new Error(`${codexPoolConfigPath}.${path}.id has an unsupported format`);
    const matchMode = requiredStringConfigField(item, "matchMode", path);
    if (matchMode !== "any" && matchMode !== "all") throw new Error(`${codexPoolConfigPath}.${path}.matchMode must be any or all`);
    const errorCodes = integerArrayConfigField(item, "errorCodes", path);
    const keywords = readUniqueStringArray(item.keywords, `${path}.keywords`, true);
    if (errorCodes.length === 0 && keywords.length === 0) throw new Error(`${codexPoolConfigPath}.${path} must declare errorCodes or keywords`);
    const passthroughCode = item.passthroughCode;
    const passthroughBody = item.passthroughBody;
    const enabled = item.enabled;
    const skipMonitoring = item.skipMonitoring;
    if (typeof enabled !== "boolean") throw new Error(`${codexPoolConfigPath}.${path}.enabled must be boolean`);
    if (typeof passthroughCode !== "boolean") throw new Error(`${codexPoolConfigPath}.${path}.passthroughCode must be boolean`);
    if (typeof passthroughBody !== "boolean") throw new Error(`${codexPoolConfigPath}.${path}.passthroughBody must be boolean`);
    if (typeof skipMonitoring !== "boolean") throw new Error(`${codexPoolConfigPath}.${path}.skipMonitoring must be boolean`);
    const responseCode = item.responseCode === null ? null : integerConfigField(item, "responseCode", path);
    if (!passthroughCode && (responseCode === null || responseCode < 100 || responseCode > 599)) {
      throw new Error(`${codexPoolConfigPath}.${path}.responseCode must be an HTTP status when passthroughCode=false`);
    }
    const customMessage = item.customMessage === null || item.customMessage === undefined ? null : requiredStringConfigField(item, "customMessage", path);
    if (!passthroughBody && customMessage === null) throw new Error(`${codexPoolConfigPath}.${path}.customMessage is required when passthroughBody=false`);
    return {
      id,
      name: requiredStringConfigField(item, "name", path),
      enabled,
      priority: integerConfigField(item, "priority", path),
      errorCodes,
      keywords,
      matchMode,
      platforms: readUniqueStringArray(item.platforms, `${path}.platforms`, false),
      passthroughCode,
      responseCode,
      passthroughBody,
      customMessage,
      skipMonitoring,
      description: readRequiredDescription(item.description, `${path}.description`, 300),
    };
  });
  const errorPassthroughRulesById: Record<string, (typeof errorPassthroughRules)[number]> = {};
  const errorPassthroughRuleNames = new Set<string>();
  for (const rule of errorPassthroughRules) {
    if (errorPassthroughRulesById[rule.id] !== undefined) throw new Error(`${codexPoolConfigPath}.runtime.errorPassthroughRules duplicates id ${rule.id}`);
    if (errorPassthroughRuleNames.has(rule.name)) throw new Error(`${codexPoolConfigPath}.runtime.errorPassthroughRules duplicates name ${rule.name}`);
    errorPassthroughRulesById[rule.id] = rule;
    errorPassthroughRuleNames.add(rule.name);
  }
  return { templates, templatesById, errorPassthroughRules, errorPassthroughRulesById };
}

export function readRuntimeTemplateRef(value: unknown, key: string, runtime: CodexRuntimeConfig): string {
  const id = stringValue(value);
  if (id === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty template id`);
  if (runtime.templatesById[id] === undefined) throw new Error(`${codexPoolConfigPath}.${key} references missing runtime template ${id}`);
  return id;
}

export function readProfileConfig(
  value: unknown,
  runtime: CodexRuntimeConfig,
  defaultTempUnschedulableTemplate: string,
  defaultTempUnschedulable: CodexTempUnschedulablePolicy,
  defaultSentinelProtect: CodexSentinelProtectPolicy,
  defaultPriority: number,
): CodexPoolProfileConfig[] {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.profiles must be a YAML object`);
  const entries = value.entries;
  if (!Array.isArray(entries)) throw new Error(`${codexPoolConfigPath}.profiles.entries must be a YAML array`);
  return entries.map((entry, index) => {
    if (!isRecord(entry)) throw new Error(`${codexPoolConfigPath}.profiles.entries[${index}] must be an object`);
    rejectSchedulableYamlField(entry, `profiles.entries[${index}]`);
    const profile = stringValue(entry.profile);
    const configFile = stringValue(entry.configFile);
    const authFile = stringValue(entry.authFile);
    if (profile === null || configFile === null || authFile === null) {
      throw new Error(`${codexPoolConfigPath}.profiles.entries[${index}] requires profile, configFile, and authFile`);
    }
    const accountName = stringValue(entry.accountName);
    if (accountName !== null) validateKubernetesName(accountName, `profiles.entries[${index}].accountName`, false);
    validateCodexFileName(configFile, `profiles.entries[${index}].configFile`);
    validateCodexFileName(authFile, `profiles.entries[${index}].authFile`);
    const fallbackConfigFile = stringValue(entry.fallbackConfigFile);
    const fallbackAuthFile = stringValue(entry.fallbackAuthFile);
    if (fallbackConfigFile !== null) validateCodexFileName(fallbackConfigFile, `profiles.entries[${index}].fallbackConfigFile`);
    if (fallbackAuthFile !== null) validateCodexFileName(fallbackAuthFile, `profiles.entries[${index}].fallbackAuthFile`);
    const openaiResponsesWebSocketsV2Mode = readOpenAIResponsesWebSocketsV2Mode(entry.openaiResponsesWebSocketsV2Mode, `profiles.entries[${index}].openaiResponsesWebSocketsV2Mode`);
    const upstreamUserAgent = readUpstreamUserAgent(entry.upstreamUserAgent, `profiles.entries[${index}].upstreamUserAgent`);
    const trustUpstream = readTrustUpstream(entry.trustUpstream, `profiles.entries[${index}].trustUpstream`);
    const sentinelProtect = readSentinelProtectPolicy(entry.sentinelProtect, `profiles.entries[${index}].sentinelProtect`, defaultSentinelProtect);
    const priority = readAccountPriority(entry.priority, `profiles.entries[${index}].priority`, defaultPriority);
    const capacity = entry.capacity === undefined || entry.capacity === null ? null : readAccountCapacity(entry.capacity, `profiles.entries[${index}].capacity`);
    const loadFactor = entry.loadFactor === undefined || entry.loadFactor === null ? null : readAccountLoadFactor(entry.loadFactor, `profiles.entries[${index}].loadFactor`);
    if (entry.tempUnschedulable !== undefined) {
      throw new Error(`${codexPoolConfigPath}.profiles.entries[${index}].tempUnschedulable was replaced by tempUnschedulableTemplate`);
    }
    const tempUnschedulableTemplate = entry.tempUnschedulableTemplate === undefined || entry.tempUnschedulableTemplate === null
      ? defaultTempUnschedulableTemplate
      : readRuntimeTemplateRef(entry.tempUnschedulableTemplate, `profiles.entries[${index}].tempUnschedulableTemplate`, runtime);
    const tempUnschedulable = runtime.templatesById[tempUnschedulableTemplate]?.tempUnschedulable ?? defaultTempUnschedulable;
    return {
      profile,
      accountName,
      configFile,
      authFile,
      fallbackConfigFile,
      fallbackAuthFile,
      openaiResponsesWebSocketsV2Mode,
      upstreamUserAgent,
      trustUpstream,
      sentinelProtect,
      priority,
      capacity,
      loadFactor,
      tempUnschedulableTemplate,
      tempUnschedulable,
    };
  });
}

export function desiredProfileCapacityTotal(profiles: CodexPoolProfileConfig[], defaultCapacity: number): number {
  return profiles.reduce((total, profile) => total + (profile.capacity ?? defaultCapacity), 0);
}

export function readManualAccountsConfig(value: unknown): CodexPoolManualAccountsConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.manualAccounts must be a YAML object`);
  const bindingSources = readManualBindingSources(value.bindingSources, "manualAccounts.bindingSources");
  const protectedRaw = value.protected;
  if (!Array.isArray(protectedRaw)) throw new Error(`${codexPoolConfigPath}.manualAccounts.protected must be a YAML array`);
  const seen = new Set<string>();
  const protectedAccounts = protectedRaw.map((entry, index): CodexPoolManualAccountProtection => {
    const key = `manualAccounts.protected[${index}]`;
    const accountName = typeof entry === "string"
      ? readManualAccountName(entry, key)
      : isRecord(entry)
        ? readManualAccountName(entry.accountName, `${key}.accountName`)
        : null;
    if (accountName === null) throw new Error(`${codexPoolConfigPath}.${key} must be an account name string or object with accountName`);
    const normalized = accountName.toLowerCase();
    if (seen.has(normalized)) throw new Error(`${codexPoolConfigPath}.${key}.accountName is duplicated in manualAccounts.protected`);
    seen.add(normalized);
    const reason = isRecord(entry) ? readManualAccountReason(entry.reason, `${key}.reason`) : null;
    const targetIds = isRecord(entry) ? readManualAccountTargetIds(entry.targetIds, `${key}.targetIds`) : null;
    const proxyBinding = isRecord(entry) ? readManualAccountProxyBinding(entry.proxyBinding, `${key}.proxyBinding`, bindingSources) : null;
    const groupBinding = isRecord(entry) ? readManualAccountGroupBinding(entry.groupBinding, `${key}.groupBinding`, bindingSources) : null;
    return { accountName, reason, targetIds, proxyBinding, groupBinding };
  });
  return { bindingSources, protected: protectedAccounts };
}

export function readManualBindingSources(value: unknown, key: string): CodexPoolManualBindingSourcesConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const items: CodexPoolManualBindingSource[] = [];
  const byId: Record<string, CodexPoolManualBindingSource> = {};
  for (const [id, rawSource] of Object.entries(value)) {
    const path = `${key}.${id}`;
    validateManualBindingSourceId(id, path);
    if (!isRecord(rawSource)) throw new Error(`${codexPoolConfigPath}.${path} must be a YAML object`);
    const source: CodexPoolManualBindingSource = {
      id,
      enabled: readBooleanConfig(rawSource.enabled, `${path}.enabled`),
      kind: readManualBindingKind(rawSource.kind, `${path}.kind`),
      provider: readManualBindingProvider(rawSource.provider, `${path}.provider`),
      description: readManualAccountReason(rawSource.description, `${path}.description`),
      hostProxyRef: readManualBindingHostProxyRef(rawSource.hostProxyRef, `${path}.hostProxyRef`),
      fixedProxy: null,
    };
    if (source.kind === "proxy" && source.provider === "fixed-http-proxy") {
      const fixedProxy = requiredRecordConfigField(rawSource, "fixedProxy", path);
      const protocol = requiredStringConfigField(fixedProxy, "protocol", `${path}.fixedProxy`);
      if (protocol !== "http") throw new Error(`${codexPoolConfigPath}.${path}.fixedProxy.protocol must be http`);
      const host = requiredStringConfigField(fixedProxy, "host", `${path}.fixedProxy`);
      if (!/^[A-Za-z0-9._:-]+$/u.test(host)) throw new Error(`${codexPoolConfigPath}.${path}.fixedProxy.host has an unsupported format`);
      const port = integerConfigField(fixedProxy, "port", `${path}.fixedProxy`);
      validatePort(port, `${path}.fixedProxy.port`);
      source.fixedProxy = { protocol, host, port };
    }
    if (source.kind === "proxy" && source.provider !== "target-egress-proxy" && source.provider !== "fixed-http-proxy") {
      throw new Error(`${codexPoolConfigPath}.${path}.provider must be target-egress-proxy or fixed-http-proxy for kind=proxy`);
    }
    if (source.kind === "group" && source.provider !== "pool-group") {
      throw new Error(`${codexPoolConfigPath}.${path}.provider must be pool-group for kind=group`);
    }
    byId[id] = source;
    items.push(source);
  }
  if (items.length === 0) throw new Error(`${codexPoolConfigPath}.${key} must declare at least one source`);
  return { items, byId };
}

export function readManualAccountProxyBinding(value: unknown, key: string, bindingSources: CodexPoolManualBindingSourcesConfig): CodexPoolManualAccountProxyBinding | null {
  if (value === undefined || value === null) return null;
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const enabled = readBooleanConfig(value.enabled, `${key}.enabled`);
  const source = readManualBindingSourceRef(value.source, `${key}.source`, bindingSources, "proxy", enabled);
  const proxyName = stringValue(value.proxyName);
  if (proxyName === null || proxyName.trim().length === 0) throw new Error(`${codexPoolConfigPath}.${key}.proxyName is required`);
  validateProxyName(proxyName, `${key}.proxyName`);
  return {
    enabled,
    source,
    proxyName,
  };
}

export function readManualAccountGroupBinding(value: unknown, key: string, bindingSources: CodexPoolManualBindingSourcesConfig): CodexPoolManualAccountGroupBinding | null {
  if (value === undefined || value === null) return null;
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const enabled = readBooleanConfig(value.enabled, `${key}.enabled`);
  const source = readManualBindingSourceRef(value.source, `${key}.source`, bindingSources, "group", enabled);
  return {
    enabled,
    source,
  };
}

export function readManualBindingSourceRef(value: unknown, key: string, bindingSources: CodexPoolManualBindingSourcesConfig, expectedKind: CodexPoolManualBindingKind, bindingEnabled: boolean): string {
  const sourceId = stringValue(value);
  if (sourceId === null) throw new Error(`${codexPoolConfigPath}.${key} is required`);
  validateManualBindingSourceId(sourceId, key);
  const source = bindingSources.byId[sourceId];
  if (source === undefined) throw new Error(`${codexPoolConfigPath}.${key} references unknown manualAccounts.bindingSources id ${sourceId}`);
  if (source.kind !== expectedKind) throw new Error(`${codexPoolConfigPath}.${key} references ${sourceId} with kind=${source.kind}; expected ${expectedKind}`);
  if (bindingEnabled && !source.enabled) throw new Error(`${codexPoolConfigPath}.${key} references disabled manualAccounts.bindingSources.${sourceId}`);
  return sourceId;
}

export function readManualBindingKind(value: unknown, key: string): CodexPoolManualBindingKind {
  const text = stringValue(value);
  if (text !== "proxy" && text !== "group") throw new Error(`${codexPoolConfigPath}.${key} must be proxy or group`);
  return text;
}

export function readManualBindingProvider(value: unknown, key: string): CodexPoolManualBindingProvider {
  const text = stringValue(value);
  if (text !== "target-egress-proxy" && text !== "fixed-http-proxy" && text !== "pool-group") throw new Error(`${codexPoolConfigPath}.${key} must be target-egress-proxy, fixed-http-proxy, or pool-group`);
  return text;
}

export function readManualBindingHostProxyRef(value: unknown, key: string): string | null {
  if (value === undefined || value === null) return null;
  const text = stringValue(value)?.trim() ?? "";
  if (text.length === 0) return null;
  if (!/^config\/platform-infra\/host-proxy\.yaml#targets\.[A-Za-z0-9_-]+$/u.test(text)) {
    throw new Error(`${codexPoolConfigPath}.${key} must point at config/platform-infra/host-proxy.yaml#targets.<id>`);
  }
  return text;
}

export function validateManualBindingSourceId(value: string, key: string): void {
  if (!/^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$/u.test(value)) throw new Error(`${codexPoolConfigPath}.${key} has an unsupported source id format`);
}

export function readManualAccountName(value: unknown, key: string): string | null {
  const text = stringValue(value)?.trim() ?? null;
  if (text === null || text.length === 0) return null;
  if (text.length > 256) throw new Error(`${codexPoolConfigPath}.${key} must be at most 256 characters`);
  if (/[\r\n]/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must not contain newlines`);
  if (!/^[^<>"'`\\]+$/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} contains unsupported characters`);
  return text;
}

export function readManualAccountReason(value: unknown, key: string): string | null {
  if (value === undefined || value === null) return null;
  const text = stringValue(value)?.trim() ?? null;
  if (text === null || text.length === 0) return null;
  if (text.length > 240) throw new Error(`${codexPoolConfigPath}.${key} must be at most 240 characters`);
  if (/[\r\n]/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must not contain newlines`);
  return text;
}

export function readManualAccountTargetIds(value: unknown, key: string): string[] | null {
  if (value === undefined || value === null) return null;
  if (!Array.isArray(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML array of target ids`);
  if (value.length === 0) throw new Error(`${codexPoolConfigPath}.${key} must not be empty`);
  const seen = new Set<string>();
  return value.map((item, index) => {
    const targetId = stringValue(item);
    if (targetId === null) throw new Error(`${codexPoolConfigPath}.${key}[${index}] must be a non-empty string`);
    if (!/^[A-Za-z0-9._-]+$/u.test(targetId)) throw new Error(`${codexPoolConfigPath}.${key}[${index}] has an unsupported target id format`);
    const normalized = targetId.toLowerCase();
    if (seen.has(normalized)) throw new Error(`${codexPoolConfigPath}.${key}[${index}] duplicates target ${targetId}`);
    seen.add(normalized);
    return targetId;
  });
}

export function assertProtectedManualAccountsNotManaged(profiles: CodexPoolProfileConfig[], manualAccounts: CodexPoolManualAccountsConfig): void {
  const protectedNames = new Set(manualAccounts.protected.map((account) => account.accountName.toLowerCase()));
  for (const [index, profile] of profiles.entries()) {
    const accountName = profile.accountName;
    if (accountName !== null && protectedNames.has(accountName.toLowerCase())) {
      throw new Error(`${codexPoolConfigPath}.profiles.entries[${index}].accountName is listed in manualAccounts.protected; protected manual accounts must not be YAML-managed`);
    }
  }
}

export function rejectSchedulableYamlField(value: Record<string, unknown>, key: string): void {
  if (Object.prototype.hasOwnProperty.call(value, "schedulable")) {
    throw new Error(`${codexPoolConfigPath}.${key}.schedulable is process control, not durable YAML config; use codex-pool sync actions instead`);
  }
}

export function readOpenAIResponsesWebSocketsV2Mode(value: unknown, key: string): OpenAIResponsesWebSocketsV2Mode | null {
  if (value === undefined || value === null) return null;
  const text = stringValue(value);
  if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a string`);
  if (text === "off" || text === "ctx_pool" || text === "passthrough") return text;
  throw new Error(`${codexPoolConfigPath}.${key} must be one of off, ctx_pool, passthrough`);
}

export function readUpstreamUserAgent(value: unknown, key: string): string | null {
  if (value === undefined || value === null) return null;
  const text = stringValue(value);
  if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a string`);
  if (text.length > 512) throw new Error(`${codexPoolConfigPath}.${key} must be at most 512 characters`);
  if (/[\r\n]/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must not contain newlines`);
  return text;
}

export function readTrustUpstream(value: unknown, key: string): boolean {
  if (value === undefined || value === null) return false;
  const parsed = booleanValue(value);
  if (parsed === null) throw new Error(`${codexPoolConfigPath}.${key} must be a boolean`);
  return parsed;
}

export function readSentinelProtectPolicy(value: unknown, key: string, fallback?: CodexSentinelProtectPolicy): CodexSentinelProtectPolicy {
  if (value === undefined || value === null) {
    if (fallback !== undefined) return fallback;
    throw new Error(`${codexPoolConfigPath}.${key} is required`);
  }
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback?.enabled);
  const required = ["consecutiveFailures", "initialRetryDelaySeconds", "maxRetryDelaySeconds", "backoffMultiplier"];
  for (const field of required) {
    if (fallback === undefined && (value[field] === undefined || value[field] === null)) {
      throw new Error(`${codexPoolConfigPath}.${key}.${field} is required`);
    }
    if (enabled && value[field] === undefined && fallback === undefined) {
      throw new Error(`${codexPoolConfigPath}.${key}.${field} is required when enabled=true`);
    }
  }
  const consecutiveFailures = value.consecutiveFailures === undefined || value.consecutiveFailures === null
    ? fallback?.consecutiveFailures
    : readBoundedInteger(value.consecutiveFailures, `${key}.consecutiveFailures`, 1, 20);
  const initialRetryDelaySeconds = value.initialRetryDelaySeconds === undefined || value.initialRetryDelaySeconds === null
    ? fallback?.initialRetryDelaySeconds
    : readBoundedInteger(value.initialRetryDelaySeconds, `${key}.initialRetryDelaySeconds`, 1, 3600);
  const maxRetryDelaySeconds = value.maxRetryDelaySeconds === undefined || value.maxRetryDelaySeconds === null
    ? fallback?.maxRetryDelaySeconds
    : readBoundedInteger(value.maxRetryDelaySeconds, `${key}.maxRetryDelaySeconds`, 1, 3600);
  const backoffMultiplier = value.backoffMultiplier === undefined || value.backoffMultiplier === null
    ? fallback?.backoffMultiplier
    : readBoundedInteger(value.backoffMultiplier, `${key}.backoffMultiplier`, 1, 10);
  if (consecutiveFailures === undefined || initialRetryDelaySeconds === undefined || maxRetryDelaySeconds === undefined || backoffMultiplier === undefined) {
    throw new Error(`${codexPoolConfigPath}.${key} is missing required sentinel protection fields`);
  }
  if (maxRetryDelaySeconds < initialRetryDelaySeconds) {
    throw new Error(`${codexPoolConfigPath}.${key}.maxRetryDelaySeconds must be >= initialRetryDelaySeconds`);
  }
  return {
    enabled,
    consecutiveFailures,
    initialRetryDelaySeconds,
    maxRetryDelaySeconds,
    backoffMultiplier,
  };
}

export function readBoundedInteger(value: unknown, key: string, min: number, max: number): number {
  const parsed = numberValue(value);
  if (parsed === null || !Number.isInteger(parsed) || parsed < min || parsed > max) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from ${min} to ${max}`);
  }
  return parsed;
}

export function readAccountPriority(value: unknown, key: string, fallback?: number): number {
  if (value === undefined || value === null) {
    if (fallback !== undefined) return fallback;
    throw new Error(`${codexPoolConfigPath}.${key} is required`);
  }
  const priority = numberValue(value);
  if (priority === null || !Number.isInteger(priority) || priority < 0 || priority > 1000) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 0 to 1000`);
  }
  return priority;
}

export function readAccountCapacity(value: unknown, key: string): number {
  const capacity = numberValue(value);
  if (capacity === null || !Number.isInteger(capacity) || capacity < 1 || capacity > 1000) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 1 to 1000`);
  }
  return capacity;
}

export function readOwnerConcurrency(value: unknown, key: string): number {
  const concurrency = numberValue(value);
  if (concurrency === null || !Number.isInteger(concurrency) || concurrency < 1 || concurrency > 100000) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 1 to 100000`);
  }
  return concurrency;
}

export function readAccountLoadFactor(value: unknown, key: string): number {
  const loadFactor = numberValue(value);
  if (loadFactor === null || !Number.isInteger(loadFactor) || loadFactor < 1 || loadFactor > 1000) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 1 to 1000`);
  }
  return loadFactor;
}

export function readCaddyTimeoutSeconds(value: unknown, key: string): number {
  if (value === undefined || value === null) throw new Error(`${codexPoolConfigPath}.${key} is required`);
  const seconds = numberValue(value);
  if (seconds === null || !Number.isInteger(seconds) || seconds < 30 || seconds > 900) {
    throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 30 to 900`);
  }
  return seconds;
}

export function readCaddyEdgeRetryConfig(value: unknown, key: string): CodexPoolCaddyEdgeRetryConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const enabled = readBooleanConfig(value.enabled, `${key}.enabled`);

  const tryDurationSeconds = readPositiveInteger(value.tryDurationSeconds, `${key}.tryDurationSeconds`);
  const tryIntervalMilliseconds = readPositiveInteger(value.tryIntervalMilliseconds, `${key}.tryIntervalMilliseconds`);
  const retryMatchValue = isRecord(value.retryMatch) ? value.retryMatch : null;
  if (retryMatchValue === null) throw new Error(`${codexPoolConfigPath}.${key}.retryMatch must be a YAML object`);
  const retryMatch = {
    methods: readCaddyRetryMethods(retryMatchValue.methods, `${key}.retryMatch.methods`),
    paths: readCaddyRetryPaths(retryMatchValue.paths, `${key}.retryMatch.paths`),
  };
  if (retryMatch.methods.length === 0 && retryMatch.paths.length === 0) {
    throw new Error(`${codexPoolConfigPath}.${key}.retryMatch must include at least one method or path matcher when enabled=true`);
  }
  return { enabled, tryDurationSeconds, tryIntervalMilliseconds, retryMatch };
}

export function readPositiveInteger(value: unknown, key: string): number {
  const parsed = numberValue(value);
  if (parsed === null || !Number.isInteger(parsed) || parsed < 1) {
    throw new Error(`${codexPoolConfigPath}.${key} must be a positive integer`);
  }
  return parsed;
}

export function readPositiveIntegerConfig(value: unknown, key: string): number {
  if (value === undefined || value === null) throw new Error(`${codexPoolConfigPath}.${key} is required`);
  return readPositiveInteger(value, key);
}

export function readCaddyRetryMethods(value: unknown, key: string): string[] {
  if (value === undefined || value === null) return [];
  if (!Array.isArray(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML array`);
  const seen = new Set<string>();
  const methods: string[] = [];
  for (const item of value) {
    const method = stringValue(item)?.toUpperCase() ?? null;
    if (method === null || !/^[A-Z][A-Z0-9_-]*$/u.test(method)) throw new Error(`${codexPoolConfigPath}.${key} entries must be HTTP method tokens`);
    if (seen.has(method)) continue;
    seen.add(method);
    methods.push(method);
  }
  return methods;
}

export function readCaddyRetryPaths(value: unknown, key: string): string[] {
  if (value === undefined || value === null) return [];
  if (!Array.isArray(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML array`);
  const seen = new Set<string>();
  const paths: string[] = [];
  for (const item of value) {
    const path = stringValue(item);
    if (path === null || !path.startsWith("/") || /[\r\n]/u.test(path)) {
      throw new Error(`${codexPoolConfigPath}.${key} entries must be Caddy path matchers starting with / and without newlines`);
    }
    if (seen.has(path)) continue;
    seen.add(path);
    paths.push(path);
  }
  return paths;
}

export function readTempUnschedulablePolicy(value: unknown, key: string, fallback?: CodexTempUnschedulablePolicy): CodexTempUnschedulablePolicy {
  if (value === undefined || value === null) {
    if (fallback !== undefined) return cloneTempUnschedulablePolicy(fallback);
    throw new Error(`${codexPoolConfigPath}.${key} is required`);
  }
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
  const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback?.enabled);
  let rules: CodexTempUnschedulableRule[];
  if (value.rules === undefined || value.rules === null) {
    if (fallback === undefined) throw new Error(`${codexPoolConfigPath}.${key}.rules is required`);
    rules = cloneTempUnschedulablePolicy(fallback).rules;
  } else {
    rules = readTempUnschedulableRules(value.rules, `${key}.rules`);
  }
  if (enabled && rules.length === 0) throw new Error(`${codexPoolConfigPath}.${key}.rules must not be empty when enabled=true`);
  return { enabled, rules };
}

export function readTempUnschedulableRules(value: unknown, key: string): CodexTempUnschedulableRule[] {
  if (!Array.isArray(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML array`);
  return value.map((entry, index) => {
    if (!isRecord(entry)) throw new Error(`${codexPoolConfigPath}.${key}[${index}] must be an object`);
    const statusCode = numberValue(entry.statusCode ?? entry.errorCode);
    if (statusCode === null || !Number.isInteger(statusCode) || statusCode < 100 || statusCode > 599) {
      throw new Error(`${codexPoolConfigPath}.${key}[${index}].statusCode must be an HTTP status code from 100 to 599`);
    }
    const durationMinutes = numberValue(entry.durationMinutes);
    if (durationMinutes === null || !Number.isInteger(durationMinutes) || durationMinutes < 1 || durationMinutes > 1440) {
      throw new Error(`${codexPoolConfigPath}.${key}[${index}].durationMinutes must be an integer from 1 to 1440`);
    }
    const keywords = readTempUnschedulableKeywords(entry.keywords, `${key}[${index}].keywords`);
    const description = stringValue(entry.description);
    if (description !== null && description.length > 240) throw new Error(`${codexPoolConfigPath}.${key}[${index}].description must be at most 240 characters`);
    return { statusCode, keywords, durationMinutes, description };
  });
}

export function readTempUnschedulableKeywords(value: unknown, key: string): string[] {
  if (!Array.isArray(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML array`);
  const seen = new Set<string>();
  const keywords: string[] = [];
  for (const item of value) {
    const keyword = stringValue(item);
    if (keyword === null) throw new Error(`${codexPoolConfigPath}.${key} entries must be non-empty strings`);
    if (keyword.length > 120) throw new Error(`${codexPoolConfigPath}.${key} entries must be at most 120 characters`);
    if (/[\r\n]/u.test(keyword)) throw new Error(`${codexPoolConfigPath}.${key} entries must not contain newlines`);
    const normalized = keyword.toLowerCase();
    if (seen.has(normalized)) continue;
    seen.add(normalized);
    keywords.push(keyword);
  }
  if (keywords.length === 0) throw new Error(`${codexPoolConfigPath}.${key} must not be empty`);
  return keywords;
}

export function cloneTempUnschedulablePolicy(policy: CodexTempUnschedulablePolicy): CodexTempUnschedulablePolicy {
  return {
    enabled: policy.enabled,
    rules: policy.rules.map((rule) => ({
      statusCode: rule.statusCode,
      keywords: [...rule.keywords],
      durationMinutes: rule.durationMinutes,
      description: rule.description,
    })),
  };
}

export function readBooleanConfig(value: unknown, key: string, fallback?: boolean): boolean {
  if (value === undefined || value === null) {
    if (fallback !== undefined) return fallback;
    throw new Error(`${codexPoolConfigPath}.${key} is required`);
  }
  const parsed = booleanValue(value);
  if (parsed === null) throw new Error(`${codexPoolConfigPath}.${key} must be a boolean`);
  return parsed;
}

export function readPublicExposureConfig(value: unknown): CodexPoolPublicExposureConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.publicExposure must be a YAML object`);
  const masterFrpsValue = requiredRecordConfigField(value, "masterFrps", "publicExposure");
  const masterCaddyValue = requiredRecordConfigField(value, "masterCaddy", "publicExposure");
  const config: CodexPoolPublicExposureConfig = {
    enabled: readBooleanConfig(value.enabled, "publicExposure.enabled"),
    proxyName: requiredStringConfigField(value, "proxyName", "publicExposure"),
    configMapName: requiredStringConfigField(value, "configMapName", "publicExposure"),
    deploymentName: requiredStringConfigField(value, "deploymentName", "publicExposure"),
    frpcImage: requiredStringConfigField(value, "frpcImage", "publicExposure"),
    serverAddr: requiredStringConfigField(value, "serverAddr", "publicExposure"),
    serverPort: readRequiredPort(value.serverPort, "publicExposure.serverPort"),
    remotePort: readRequiredPort(value.remotePort, "publicExposure.remotePort"),
    localIP: requiredStringConfigField(value, "localIP", "publicExposure"),
    localPort: readRequiredPort(value.localPort, "publicExposure.localPort"),
    publicBaseUrl: readRequiredBaseUrl(value.publicBaseUrl, "publicExposure.publicBaseUrl"),
    masterBaseUrl: readRequiredBaseUrl(value.masterBaseUrl, "publicExposure.masterBaseUrl"),
    masterFrps: {
      configPath: requiredStringConfigField(masterFrpsValue, "configPath", "publicExposure.masterFrps"),
      containerName: requiredStringConfigField(masterFrpsValue, "containerName", "publicExposure.masterFrps"),
    },
    masterCaddy: {
      enabled: readBooleanConfig(masterCaddyValue.enabled, "publicExposure.masterCaddy.enabled"),
      domain: requiredStringConfigField(masterCaddyValue, "domain", "publicExposure.masterCaddy"),
      configPath: requiredStringConfigField(masterCaddyValue, "configPath", "publicExposure.masterCaddy"),
      serviceName: requiredStringConfigField(masterCaddyValue, "serviceName", "publicExposure.masterCaddy"),
      upstreamBaseUrl: readRequiredBaseUrl(masterCaddyValue.upstreamBaseUrl, "publicExposure.masterCaddy.upstreamBaseUrl"),
      responseHeaderTimeoutSeconds: readCaddyTimeoutSeconds(
        masterCaddyValue.responseHeaderTimeoutSeconds,
        "publicExposure.masterCaddy.responseHeaderTimeoutSeconds",
      ),
      edgeRetry: readCaddyEdgeRetryConfig(
        masterCaddyValue.edgeRetry,
        "publicExposure.masterCaddy.edgeRetry",
      ),
    },
  };
  validateKubernetesName(config.configMapName, "publicExposure.configMapName", true);
  validateKubernetesName(config.deploymentName, "publicExposure.deploymentName", true);
  validateProxyName(config.proxyName, "publicExposure.proxyName");
  validatePort(config.serverPort, "publicExposure.serverPort");
  validatePort(config.remotePort, "publicExposure.remotePort");
  validatePort(config.localPort, "publicExposure.localPort");
  if (!/^[A-Za-z0-9._:/-]+$/u.test(config.frpcImage)) throw new Error(`${codexPoolConfigPath}.publicExposure.frpcImage has an unsupported format`);
  if (!/^[A-Za-z0-9._:-]+$/u.test(config.serverAddr)) throw new Error(`${codexPoolConfigPath}.publicExposure.serverAddr has an unsupported format`);
  if (!/^[A-Za-z0-9._:-]+$/u.test(config.localIP)) throw new Error(`${codexPoolConfigPath}.publicExposure.localIP has an unsupported format`);
  if (!config.masterFrps.configPath.startsWith("/")) throw new Error(`${codexPoolConfigPath}.publicExposure.masterFrps.configPath must be absolute`);
  validateProxyName(config.masterFrps.containerName, "publicExposure.masterFrps.containerName");
  validatePublicHostname(config.masterCaddy.domain, "publicExposure.masterCaddy.domain");
  if (!config.masterCaddy.configPath.startsWith("/")) throw new Error(`${codexPoolConfigPath}.publicExposure.masterCaddy.configPath must be absolute`);
  validateProxyName(config.masterCaddy.serviceName, "publicExposure.masterCaddy.serviceName");
  const upstream = new URL(config.masterCaddy.upstreamBaseUrl);
  if (upstream.protocol !== "http:") throw new Error(`${codexPoolConfigPath}.publicExposure.masterCaddy.upstreamBaseUrl must use http:// for the local upstream`);
  return config;
}

export function readLocalCodexConfig(value: unknown): CodexPoolLocalCodexConfig {
  if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.localCodex must be a YAML object`);
  const config = {
    backupSuffix: requiredStringConfigField(value, "backupSuffix", "localCodex"),
    providerName: requiredStringConfigField(value, "providerName", "localCodex"),
    wireApi: requiredStringConfigField(value, "wireApi", "localCodex"),
    modelContextWindow: readPositiveIntegerConfig(value.modelContextWindow, "localCodex.modelContextWindow"),
    modelAutoCompactTokenLimit: readPositiveIntegerConfig(value.modelAutoCompactTokenLimit, "localCodex.modelAutoCompactTokenLimit"),
    supportsWebSockets: readBooleanConfig(value.supportsWebSockets, "localCodex.supportsWebSockets"),
    responsesWebSocketsV2: readBooleanConfig(value.responsesWebSocketsV2, "localCodex.responsesWebSocketsV2"),
    responsesSmokeModel: requiredStringConfigField(value, "responsesSmokeModel", "localCodex"),
  };
  if (!/^[A-Za-z0-9._-]+$/u.test(config.backupSuffix)) throw new Error(`${codexPoolConfigPath}.localCodex.backupSuffix has an unsupported format`);
  validateProxyName(config.providerName, "localCodex.providerName");
  validateProxyName(config.wireApi, "localCodex.wireApi");
  validateModelName(config.responsesSmokeModel, "localCodex.responsesSmokeModel");
  return config;
}

export function validateCodexFileName(value: string, key: string): void {
  if (!/^(config\.toml|config\.toml\.[A-Za-z0-9._-]+|auth\.json|auth\.json\.[A-Za-z0-9._-]+)$/u.test(value)) {
    throw new Error(`${codexPoolConfigPath}.${key} has an unsupported file name`);
  }
}

export function validateProxyName(value: string, key: string): void {
  if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${codexPoolConfigPath}.${key} has an unsupported format`);
}

export function validateModelName(value: string, key: string): void {
  if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`${codexPoolConfigPath}.${key} has an unsupported model name`);
}

export function validatePublicHostname(value: string, key: string): void {
  if (value.length > 253 || !/^[A-Za-z0-9.-]+$/u.test(value) || value.startsWith(".") || value.endsWith(".")) {
    throw new Error(`${codexPoolConfigPath}.${key} has an unsupported hostname format`);
  }
}

export function validatePort(value: number, key: string): void {
  if (!Number.isInteger(value) || value < 1 || value > 65535) throw new Error(`${codexPoolConfigPath}.${key} must be an integer port`);
}
