import { newId, stableHash } from "../common/validation.js"; import type { BackendProfile, ExecutionPolicy, JsonRecord, JsonValue, RunRecord, SecretRef } from "../common/types.js"; import { backendProfileSpec } from "../common/backend-profiles.js"; import { staticWorkReadyCapabilitySummary } from "../common/work-ready.js"; import { gitTransportSummary, runnerGitTransportEnvVars } from "../common/git-transport.js"; const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git"; const defaultResourceBinPath = "/usr/local/bin"; const defaultCodexShellSandbox = "danger-full-access"; const defaultRunnerIdleTimeoutMs = 600_000; const fallbackRunnerEgressProxyUrl = "http://g14-provider-egress-proxy.unidesk.svc.cluster.local:18789"; const defaultRunnerNoProxyItems = [ "localhost", "127.0.0.1", "::1", "agentrun-mgr", "agentrun-mgr.agentrun-v01", "agentrun-mgr.agentrun-v01.svc", "agentrun-mgr.agentrun-v01.svc.cluster.local", "agentrun-v01-postgres", "agentrun-v01-postgres.agentrun-v01", "agentrun-v01-postgres.agentrun-v01.svc", "agentrun-v01-postgres.agentrun-v01.svc.cluster.local", "g14-provider-egress-proxy", "g14-provider-egress-proxy.unidesk", "g14-provider-egress-proxy.unidesk.svc", "g14-provider-egress-proxy.unidesk.svc.cluster.local", "registry.npmjs.org", "registry.npmmirror.com", "g14-tcp-egress-gateway", "g14-tcp-egress-gateway.unidesk", "g14-tcp-egress-gateway.unidesk.svc", "g14-tcp-egress-gateway.unidesk.svc.cluster.local", "hyueapi.com", ".hyueapi.com", "10.42.0.0/16", "10.43.0.0/16", ".svc", ".cluster.local", ]; export interface RunnerJobRenderOptions { run: RunRecord; commandId: string; runnerJobId?: string; managerUrl: string; image: string; bootRepoUrl?: string; namespace?: string; attemptId?: string; runnerId?: string; sourceCommit?: string; serviceAccountName?: string; jobNamePrefix?: string; lane?: string; imagePullPolicy?: string; backoffLimit?: number; ttlSecondsAfterFinished?: number; runnerIdleTimeoutMs?: number; transientEnv?: RunnerTransientEnv[]; sessionPvc?: RunnerSessionPvcOptions; dryRun?: boolean; } export interface RunnerSessionPvcOptions { pvcName: string; namespace: string; mountPath: string; codexRolloutSubdir: string; } export interface RunnerTransientEnv { name: string; value: string; sensitive?: boolean; secretRef?: RunnerTransientEnvSecretRef; } export interface RunnerTransientEnvSecretRef { name: string; key: string; } interface CredentialProjection { profile: BackendProfile | string; secretRef: SecretRef; volumeName: string; runtimeMountPath: string; projectionMountPath: string; } type ToolCredentialProjection = ToolCredentialEnvProjection | ToolCredentialVolumeProjection; interface ToolCredentialBaseProjection { tool: string; purpose: string | null; secretRef: SecretRef; } interface ToolCredentialEnvProjection extends ToolCredentialBaseProjection { kind: "env"; envName: string; secretKey: string; } interface ToolCredentialVolumeProjection extends ToolCredentialBaseProjection { kind: "volume"; volumeName: string; mountPath: string; } export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonRecord { const render = renderRunnerJobManifest({ ...options, dryRun: true }); const manifest = redactTransientEnvInManifest(render.manifest, options.transientEnv ?? []); return { dryRun: true, mutation: false, action: "render-kubernetes-job", jobIdentity: { kind: "Job", namespace: render.namespace, name: render.jobName, serviceAccountName: render.serviceAccountName, }, runner: { runId: options.run.id, commandId: options.commandId, attemptId: render.attemptId, runnerId: render.runnerId, backendProfile: options.run.backendProfile, managerUrl: options.managerUrl, sourceCommit: render.sourceCommit, }, secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })), toolCredentials: summarizeToolCredentials(render.toolCredentials, render.namespace), gitTransport: gitTransportSummary(), transientEnv: summarizeTransientEnv(options.transientEnv ?? []), workReady: staticWorkReadyCapabilitySummary(), retention: { ttlSecondsAfterFinished: render.ttlSecondsAfterFinished, runnerIdleTimeoutMs: render.runnerIdleTimeoutMs, }, pollCommands: { run: `./scripts/agentrun runs show ${options.run.id} --manager-url ${options.managerUrl}`, events: `./scripts/agentrun runs events ${options.run.id} --manager-url ${options.managerUrl} --after-seq 0 --limit 100`, }, warnings: render.warnings, manifest, }; } export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerJobId: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; toolCredentials: ToolCredentialProjection[]; warnings: string[]; ttlSecondsAfterFinished: number; runnerIdleTimeoutMs: number } { const namespace = options.namespace ?? "agentrun-v01"; const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`; const runnerId = options.runnerId ?? `runner_${shortHash(`${options.run.id}:${attemptId}:${options.commandId}`)}`; const runnerJobId = options.runnerJobId ?? newId("rjob"); const sourceCommit = options.sourceCommit ?? process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown"; const serviceAccountName = options.serviceAccountName ?? "agentrun-v01-runner"; const jobNamePrefix = normalizeJobNamePrefix(options.jobNamePrefix ?? serviceAccountName); const lane = options.lane ?? process.env.AGENTRUN_LANE ?? "v0.1"; const ttlSecondsAfterFinished = options.ttlSecondsAfterFinished ?? 86_400; const runnerIdleTimeoutMs = normalizeRunnerIdleTimeoutMs(options.runnerIdleTimeoutMs); const jobName = `${jobNamePrefix}-${shortDnsHash(options.run.id, attemptId)}`; const secretRefs = credentialProjections(options.run, namespace); const toolCredentials = toolCredentialProjections(options.run, namespace); const sessionPvc = options.sessionPvc; const warnings: string[] = []; if (secretRefs.length === 0) warnings.push("run executionPolicy.secretScope 未声明 provider SecretRef;runner 将按 secret-unavailable 上报,而不会降级直连外部凭据"); const env = runnerEnv(options, { namespace, jobName, runnerJobId, runnerId, attemptId, sourceCommit, secretRefs, toolCredentials, sessionPvc, runnerIdleTimeoutMs }); const manifest: JsonRecord = { apiVersion: "batch/v1", kind: "Job", metadata: { name: jobName, namespace, labels: labels(options.run, jobName, lane), annotations: { "agentrun.pikastech.local/run-id": options.run.id, "agentrun.pikastech.local/command-id": options.commandId, "agentrun.pikastech.local/dry-run-render": String(options.dryRun === true), }, }, spec: { backoffLimit: options.backoffLimit ?? 0, ttlSecondsAfterFinished, template: { metadata: { labels: labels(options.run, jobName, lane), annotations: { "agentrun.pikastech.local/run-id": options.run.id, "agentrun.pikastech.local/command-id": options.commandId, }, }, spec: { serviceAccountName, automountServiceAccountToken: false, restartPolicy: "Never", containers: [ { name: "runner", image: options.image, imagePullPolicy: options.imagePullPolicy ?? "IfNotPresent", command: ["/opt/agentrun/deploy/runtime/boot/agentrun-runner.sh"], env, volumeMounts: [ { name: "runner-home", mountPath: "/home/agentrun" }, ...secretRefs.map((item) => ({ name: item.volumeName, mountPath: item.projectionMountPath, readOnly: true })), ...toolCredentialVolumeMounts(toolCredentials), ...(sessionPvc ? [{ name: "agentrun-sessions", mountPath: sessionPvc.mountPath, readOnly: false }] : []), ], resources: { requests: { cpu: "250m", memory: "512Mi" }, limits: { cpu: "2", memory: "4Gi" }, }, securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: false, capabilities: { drop: ["ALL"] }, }, }, ], volumes: [ { name: "runner-home", emptyDir: {} }, ...secretRefs.map(secretVolume), ...toolCredentialVolumes(toolCredentials), ...(sessionPvc ? [{ name: "agentrun-sessions", persistentVolumeClaim: { claimName: sessionPvc.pvcName } }] : []), ], }, }, }, }; return { manifest, namespace, jobName, runnerJobId, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, toolCredentials, warnings, ttlSecondsAfterFinished, runnerIdleTimeoutMs }; } function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string; jobName: string; runnerJobId: string; runnerId: string; attemptId: string; sourceCommit: string; secretRefs: CredentialProjection[]; toolCredentials: ToolCredentialProjection[]; sessionPvc: RunnerSessionPvcOptions | undefined; runnerIdleTimeoutMs: number }): JsonRecord[] { const selectedSecret = context.secretRefs.find((item) => item.profile === options.run.backendProfile); const codexHome = selectedSecret?.runtimeMountPath ?? defaultRuntimeHome(options.run.backendProfile); const bootRepoUrl = optionalString(options.bootRepoUrl) ?? defaultBootRepoUrl; return dedupeEnvVars([ { name: "AGENTRUN_MGR_URL", value: options.managerUrl }, { name: "AGENTRUN_API_KEY", valueFrom: { secretKeyRef: { name: "agentrun-v01-api-key", key: "HWLAB_API_KEY" } } }, { name: "AGENTRUN_RUN_ID", value: options.run.id }, { name: "AGENTRUN_COMMAND_ID", value: options.commandId }, { name: "AGENTRUN_RUNNER_JOB_ID", value: context.runnerJobId }, { name: "AGENTRUN_ATTEMPT_ID", value: context.attemptId }, { name: "AGENTRUN_RUNNER_ID", value: context.runnerId }, { name: "AGENTRUN_BACKEND_PROFILE", value: options.run.backendProfile }, { name: "AGENTRUN_EXECUTION_POLICY_JSON", value: JSON.stringify(options.run.executionPolicy) }, { name: "AGENTRUN_CODEX_SHELL_SANDBOX", value: codexShellSandbox(options.run.executionPolicy) }, { name: "AGENTRUN_SESSION_REF_JSON", value: JSON.stringify(options.run.sessionRef ?? null) }, { name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) }, { name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" }, { name: "AGENTRUN_RESOURCE_BIN_PATH", value: defaultResourceBinPath }, { name: "AGENTRUN_SOURCE_COMMIT", value: context.sourceCommit }, { name: "AGENTRUN_BOOT_COMMIT", value: context.sourceCommit }, { name: "AGENTRUN_BOOT_REPO_URL", value: bootRepoUrl }, { name: "AGENTRUN_BOOT_MODE", value: "runner" }, { name: "AGENTRUN_APP_ROOT", value: "/home/agentrun/agentrun-source" }, { name: "AGENTRUN_RUNTIME_NAMESPACE", value: context.namespace }, { name: "AGENTRUN_K8S_JOB_NAME", value: context.jobName }, { name: "AGENTRUN_LOG_PATH", value: "/tmp/agentrun-runner.jsonl" }, { name: "AGENTRUN_WORK_READY_VERSION", value: String(staticWorkReadyCapabilitySummary().version) }, { name: "AGENTRUN_PROJECT_DEPENDENCY_POLICY", value: "explicit-cache-or-derived-image-only" }, { name: "AGENTRUN_RUNNER_IDLE_TIMEOUT_MS", value: String(context.runnerIdleTimeoutMs) }, { name: "AGENTRUN_RUNNER_POLL_INTERVAL_MS", value: "250" }, { name: "HOME", value: "/home/agentrun" }, { name: "CODEX_HOME", value: codexHome }, ...runnerOtelEnvVars(process.env), ...(selectedSecret ? [{ name: "AGENTRUN_CODEX_SECRET_HOME", value: selectedSecret.projectionMountPath }] : []), ...(context.sessionPvc ? [ { name: "AGENTRUN_SESSION_PVC_NAME", value: context.sessionPvc.pvcName }, { name: "AGENTRUN_SESSION_PVC_NAMESPACE", value: context.sessionPvc.namespace }, { name: "AGENTRUN_SESSION_PVC_MOUNT_PATH", value: context.sessionPvc.mountPath }, { name: "AGENTRUN_CODEX_ROLLOUT_SUBDIR", value: context.sessionPvc.codexRolloutSubdir }, ] : []), ...runnerEgressProxyEnvVars(), ...runnerGitTransportEnvVars(), ...toolCredentialEnvVars(context.toolCredentials), ...transientEnvVars(options.transientEnv ?? []), ]); } function normalizeRunnerIdleTimeoutMs(value: number | undefined): number { if (value === undefined) return defaultRunnerIdleTimeoutMs; if (!Number.isInteger(value) || value <= 0) throw new Error("runnerIdleTimeoutMs must be a positive integer"); return value; } function codexShellSandbox(policy: ExecutionPolicy): string { if (policy.sandbox === "workspace-write") return defaultCodexShellSandbox; return policy.sandbox; } function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] { return [ ...items.filter((item): item is ToolCredentialEnvProjection => item.kind === "env").map((item) => ({ name: item.envName, valueFrom: { secretKeyRef: { name: item.secretRef.name, key: item.secretKey, }, }, })), ...githubSshCommandEnvVars(items), ]; } function githubSshCommandEnvVars(items: ToolCredentialProjection[]): JsonRecord[] { const credential = items.find((item): item is ToolCredentialVolumeProjection => item.kind === "volume" && item.tool === "github" && item.purpose === "github-ssh"); if (!credential) return []; const mountPath = credential.mountPath.replace(/\/+$/u, ""); return [{ name: "GIT_SSH_COMMAND", value: [ "ssh", "-F", `${mountPath}/config`, "-i", `${mountPath}/id_ed25519`, "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=yes", "-o", `UserKnownHostsFile=${mountPath}/known_hosts`, ].join(" "), }]; } function toolCredentialVolumeMounts(items: ToolCredentialProjection[]): JsonRecord[] { return items.filter((item): item is ToolCredentialVolumeProjection => item.kind === "volume").map((item) => ({ name: item.volumeName, mountPath: item.mountPath, readOnly: true })); } function toolCredentialVolumes(items: ToolCredentialProjection[]): JsonRecord[] { return items.filter((item): item is ToolCredentialVolumeProjection => item.kind === "volume").map((item) => secretVolume({ profile: item.tool, secretRef: item.secretRef, volumeName: item.volumeName, runtimeMountPath: item.mountPath, projectionMountPath: item.mountPath })); } function transientEnvVars(items: RunnerTransientEnv[]): JsonRecord[] { return items.map((item) => { if (item.secretRef) { return { name: item.name, valueFrom: { secretKeyRef: { name: item.secretRef.name, key: item.secretRef.key, }, }, }; } return { name: item.name, value: item.value }; }); } function runnerEgressProxyEnvVars(): JsonRecord[] { const proxyUrl = runnerEgressProxyUrl(process.env); const noProxy = runnerNoProxy(process.env, proxyUrl); return [ { name: "HTTP_PROXY", value: proxyUrl }, { name: "HTTPS_PROXY", value: proxyUrl }, { name: "ALL_PROXY", value: proxyUrl }, { name: "NO_PROXY", value: noProxy }, { name: "http_proxy", value: proxyUrl }, { name: "https_proxy", value: proxyUrl }, { name: "all_proxy", value: proxyUrl }, { name: "no_proxy", value: noProxy }, ]; } function runnerOtelEnvVars(env: NodeJS.ProcessEnv): JsonRecord[] { const tracesEndpoint = firstNonEmpty(env.AGENTRUN_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT); const baseEndpoint = firstNonEmpty(env.AGENTRUN_OTEL_EXPORTER_OTLP_ENDPOINT, env.OTEL_EXPORTER_OTLP_ENDPOINT); return [ ...(tracesEndpoint ? [ { name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", value: tracesEndpoint }, { name: "AGENTRUN_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", value: tracesEndpoint }, ] : []), ...(!tracesEndpoint && baseEndpoint ? [ { name: "OTEL_EXPORTER_OTLP_ENDPOINT", value: baseEndpoint }, { name: "AGENTRUN_OTEL_EXPORTER_OTLP_ENDPOINT", value: baseEndpoint }, ] : []), { name: "OTEL_SERVICE_NAME", value: "agentrun-runner" }, ...optionalEnvVar("AGENTRUN_LANE", env.AGENTRUN_LANE), ...optionalEnvVar("UNIDESK_NODE_ID", env.UNIDESK_NODE_ID ?? env.AGENTRUN_NODE_ID), ...optionalEnvVar("AGENTRUN_NODE_ID", env.AGENTRUN_NODE_ID ?? env.UNIDESK_NODE_ID), ...optionalEnvVar("HWLAB_RUNTIME_LANE", env.HWLAB_RUNTIME_LANE), ]; } function optionalEnvVar(name: string, value: string | undefined): JsonRecord[] { const normalized = value?.trim(); return normalized ? [{ name, value: normalized }] : []; } function firstNonEmpty(...values: Array): string | null { for (const value of values) { const text = value?.trim(); if (text) return text; } return null; } function runnerEgressProxyUrl(env: NodeJS.ProcessEnv): string { const value = env.AGENTRUN_RUNNER_EGRESS_PROXY_URL?.trim(); return value && value.length > 0 ? value : fallbackRunnerEgressProxyUrl; } function runnerNoProxy(env: NodeJS.ProcessEnv, proxyUrl: string): string { const items = new Set(defaultRunnerNoProxyItems); const proxyHost = hostFromUrl(proxyUrl); if (proxyHost) items.add(proxyHost); for (const item of (env.AGENTRUN_RUNNER_NO_PROXY_EXTRA ?? "").split(",")) { const value = item.trim(); if (value.length > 0) items.add(value); } return [...items].join(","); } function hostFromUrl(value: string): string | null { try { return new URL(value).hostname || null; } catch { return null; } } function dedupeEnvVars(items: JsonRecord[]): JsonRecord[] { const order: string[] = []; const byName = new Map(); for (const item of items) { const name = typeof item.name === "string" ? item.name : ""; if (!name) continue; if (!byName.has(name)) order.push(name); byName.set(name, item); } return order.map((name) => byName.get(name)).filter((item): item is JsonRecord => item !== undefined); } function summarizeTransientEnv(items: RunnerTransientEnv[]): JsonRecord { return { count: items.length, names: items.map((item) => item.name), valuesPrinted: false, }; } function summarizeToolCredentials(items: ToolCredentialProjection[], namespace: string): JsonRecord { return { count: items.length, items: items.map((item) => ({ tool: item.tool, purpose: item.purpose, name: item.secretRef.name, namespace: item.secretRef.namespace ?? namespace, keys: item.secretRef.keys ?? [], projection: item.kind === "env" ? { kind: "env", envName: item.envName, secretKey: item.secretKey } : { kind: "volume", mountPath: item.mountPath }, valuesPrinted: false, })), valuesPrinted: false, }; } function redactTransientEnvInManifest(manifest: JsonRecord, items: RunnerTransientEnv[]): JsonRecord { if (items.length === 0) return manifest; const names = new Set(items.map((item) => item.name)); const copy = JSON.parse(JSON.stringify(manifest)) as JsonRecord; const spec = copy.spec as JsonRecord | undefined; const template = spec?.template as JsonRecord | undefined; const podSpec = template?.spec as JsonRecord | undefined; const containers = Array.isArray(podSpec?.containers) ? podSpec.containers as JsonRecord[] : []; for (const container of containers) { const env = Array.isArray(container.env) ? container.env as JsonRecord[] : []; for (const entry of env) { if (typeof entry.name === "string" && names.has(entry.name) && entry.value !== undefined) entry.value = "REDACTED"; } } return copy; } function credentialProjections(run: RunRecord, namespace: string): CredentialProjection[] { const policy: ExecutionPolicy = run.executionPolicy; const credentials = (policy.secretScope.providerCredentials ?? []).filter((item) => item.profile === run.backendProfile); return credentials.map((item, index) => ({ profile: item.profile, secretRef: credentialSecretRef(item.profile, item.secretRef, namespace), volumeName: sanitizeVolumeName(`${String(item.profile)}-${index}`), runtimeMountPath: normalizeMountPath(item.secretRef.mountPath, String(item.profile)), projectionMountPath: `/var/run/agentrun/secrets/${sanitizeVolumeName(`${String(item.profile)}-${index}`)}`, })); } function credentialSecretRef(profile: string, secretRef: SecretRef, namespace: string): SecretRef { const spec = backendProfileSpec(profile); const keys = [...new Set([...(secretRef.keys ?? []), ...(spec?.requiredSecretKeys ?? [])])]; return { ...secretRef, namespace: secretRef.namespace ?? namespace, ...(keys.length > 0 ? { keys } : {}) }; } function toolCredentialProjections(run: RunRecord, namespace: string): ToolCredentialProjection[] { const policy: ExecutionPolicy = run.executionPolicy; const credentials = policy.secretScope.toolCredentials ?? []; return credentials.map((item, index) => { const secretRef = item.secretRef.namespace ? item.secretRef : { ...item.secretRef, namespace }; const base = { tool: item.tool, purpose: item.purpose ?? null, secretRef }; if (item.projection.kind === "env") return { ...base, kind: "env" as const, envName: item.projection.envName, secretKey: item.projection.secretKey ?? item.secretRef.keys?.[0] ?? item.projection.envName }; return { ...base, kind: "volume" as const, volumeName: sanitizeVolumeName(`tool-${item.tool}-${index}`), mountPath: item.projection.mountPath }; }); } function secretVolume(item: CredentialProjection): JsonRecord { const secret: JsonRecord = { secretName: item.secretRef.name, defaultMode: 256, }; const keys = item.secretRef.keys ?? []; if (keys.length > 0) secret.items = keys.map((key) => ({ key, path: key, mode: 256 })); return { name: item.volumeName, secret }; } function normalizeMountPath(value: string | undefined, profile: string): string { const spec = backendProfileSpec(profile); const suffix = spec ? spec.profile : sanitizeVolumeName(profile); if (!value || value === "~/.codex") return defaultRuntimeHome(suffix); if (value.startsWith("~/")) return `/home/agentrun/${value.slice(2)}-${suffix}`; return value; } function defaultRuntimeHome(profile: string): string { return `/home/agentrun/.codex-${sanitizeVolumeName(profile)}`; } function labels(run: RunRecord, jobName: string, lane: string): JsonRecord { return { "app.kubernetes.io/name": "agentrun-runner", "app.kubernetes.io/component": "runner", "app.kubernetes.io/part-of": "agentrun", "agentrun.pikastech.local/lane": lane, "agentrun.pikastech.local/run-hash": shortHash(run.id), "job-name": jobName, }; } function normalizeJobNamePrefix(value: string): string { const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, ""); if (!normalized) throw new Error("jobNamePrefix must contain at least one DNS label character"); if (normalized.length > 46) return normalized.slice(0, 46).replace(/-+$/u, "") || "agentrun-runner"; return normalized; } function shortDnsHash(...parts: string[]): string { return shortHash(parts.join(":")); } function shortHash(value: JsonValue): string { return stableHash(value).slice(0, 12); } function optionalString(value: unknown): string | undefined { return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; } function sanitizeVolumeName(value: string): string { const sanitized = value.toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, ""); return sanitized.length > 0 ? sanitized.slice(0, 40) : "credential"; }