// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. service-plan module for scripts/src/deploy.ts. // SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets. // Moved mechanically from scripts/src/deploy.ts:634-1236 for #903. import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { runCommand } from "../command"; import { type UniDeskConfig, type UniDeskMicroserviceConfig, repoRoot, rootPath } from "../config"; import { ensureGithubSshIdentityForProvider } from "../deploy-ssh-identity"; import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText, type RuntimeSecretContract, runArtifactRegistryCommand } from "../artifact-registry"; import { startJob } from "../jobs"; import { coreInternalFetch } from "../microservices"; import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard"; import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard"; import { composeRuntimeEnvValue } from "../runtime-env"; import { compareDeployJsonExecutorMirrors, deployJsonCommitImage, deployJsonDriftResult, deployJsonSourceOfTruth, encodeDeployJsonServiceContract, hasDeployJsonExecutorContract, k3sManifestExecutorMirror, parseDeployJsonServiceContract, type DeployJsonExecutorMirror, type DeployJsonServiceContract, } from "../deploy-json-contract"; import type { ArtifactConsumerPlanKind, DeployEnvironment, DeployEnvironmentTarget, DeployManifest, DeployManifestService } from "./types"; import { targetIsMain, targetWorkDir } from "./paths"; import { artifactConsumerDryRunBlockedServiceIds, artifactDeployNodeId, artifactDeployProviderId, targetSideMaintenanceDeployAllowedServiceIds, devApplySupportedServiceIds, devArtifactConsumerServiceIds, k8sNamespace, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds, unideskRepoUrl } from "./types"; export function frontendCoreDeployService(config: UniDeskConfig): UniDeskMicroserviceConfig { return { id: "frontend", name: "UniDesk Frontend", providerId: "main-server", description: "UniDesk core frontend entrypoint deployed by the main-server direct Compose executor.", repository: { url: unideskRepoUrl, commitId: "local", dockerfile: "src/components/frontend/Dockerfile", composeFile: config.docker.composeFile, composeService: "frontend", containerName: "unidesk-frontend", }, backend: { nodeBaseUrl: `http://127.0.0.1:${config.network.frontend.port}`, nodeBindHost: "127.0.0.1", nodePort: config.network.frontend.port, proxyMode: "core-direct", frontendOnly: false, public: true, allowedMethods: ["GET", "HEAD"], allowedPathPrefixes: ["/"], healthPath: "/health", timeoutMs: 8000, }, deployment: { mode: "unidesk-direct" }, development: { providerId: "main-server", sshPassthrough: false, worktreePath: repoRoot, }, frontend: { route: "/", integrated: true, }, }; } export function devK3sDeployService(id: string): UniDeskMicroserviceConfig | undefined { const specs: Record = { "backend-core": { name: "UniDesk Dev Backend Core", description: "Isolated dev backend-core deployed into D601 native k3s namespace unidesk-dev.", dockerfile: "src/components/backend-core/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml", composeService: "backend-core-dev", containerName: "k3s:backend-core-dev", nodeBaseUrl: "k3s://backend-core-dev", nodePort: 8080, healthPath: "/health", route: "/dev/backend-core", allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"], allowedPathPrefixes: ["/", "/api/", "/logs"], }, frontend: { name: "UniDesk Dev Frontend", description: "Isolated dev frontend deployed into D601 native k3s namespace unidesk-dev.", dockerfile: "src/components/frontend/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml", composeService: "frontend-dev", containerName: "k3s:frontend-dev", nodeBaseUrl: "k3s://frontend-dev", nodePort: 8080, healthPath: "/health", route: "/dev/frontend", allowedMethods: ["GET", "HEAD"], allowedPathPrefixes: ["/"], }, "decision-center": { name: "UniDesk Dev Decision Center", description: "Isolated dev Decision Center deployed into D601 native k3s namespace unidesk-dev.", dockerfile: "src/components/microservices/decision-center/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml", composeService: "decision-center-dev", containerName: "k3s:decision-center-dev", nodeBaseUrl: "k3s://decision-center-dev", nodePort: 4277, healthPath: "/health", route: "/dev/decision-center", allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"], allowedPathPrefixes: ["/", "/api/", "/logs"], }, mdtodo: { name: "UniDesk Dev MDTODO", description: "Isolated dev MDTODO deployed into D601 native k3s namespace unidesk-dev.", dockerfile: "src/components/microservices/mdtodo/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-mdtodo.k8s.yaml", composeService: "mdtodo-dev", containerName: "k3s:mdtodo-dev", nodeBaseUrl: "k3s://mdtodo-dev", nodePort: 4267, healthPath: "/health", route: "/dev/mdtodo", allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"], allowedPathPrefixes: ["/health", "/live", "/logs", "/api/"], }, claudeqq: { name: "UniDesk Dev ClaudeQQ", description: "Isolated dev ClaudeQQ deployed into D601 native k3s namespace unidesk-dev.", repoUrl: "https://gitee.com/lyon1998/agent_skills", dockerfile: "claudeqq/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-claudeqq.k8s.yaml", composeService: "claudeqq-dev", containerName: "k3s:claudeqq-dev", nodeBaseUrl: "k3s://claudeqq-dev", nodePort: 3290, healthPath: "/health", route: "/dev/claudeqq", allowedMethods: ["GET", "HEAD", "POST", "DELETE"], allowedPathPrefixes: ["/health", "/logs", "/api/"], }, "code-queue": { name: "UniDesk Dev Code Queue", description: "Isolated dev Code Queue execution plane deployed into D601 native k3s namespace unidesk-dev.", dockerfile: "src/components/microservices/code-queue/Dockerfile", composeFile: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml", composeService: "code-queue-scheduler-dev", containerName: "k3s:code-queue-scheduler-dev", nodeBaseUrl: "k3s://code-queue-dev", nodePort: 4222, healthPath: "/health", route: "/dev/code-queue", allowedMethods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"], allowedPathPrefixes: ["/", "/api/", "/logs"], }, }; const spec = specs[id]; if (spec === undefined) return undefined; return { id, name: spec.name, providerId: artifactDeployProviderId, description: spec.description, repository: { url: spec.repoUrl ?? unideskRepoUrl, commitId: "deploy-env", dockerfile: spec.dockerfile, composeFile: spec.composeFile, composeService: spec.composeService, containerName: spec.containerName, }, backend: { nodeBaseUrl: spec.nodeBaseUrl, nodeBindHost: `k3s://unidesk-dev/${spec.composeService}`, nodePort: spec.nodePort, proxyMode: "dev-k3s-direct", frontendOnly: true, public: false, allowedMethods: spec.allowedMethods, allowedPathPrefixes: spec.allowedPathPrefixes, healthPath: spec.healthPath, timeoutMs: 30_000, }, deployment: { mode: "k3sctl-managed", adapterServiceId: "k3sctl-adapter", k3sServiceId: spec.composeService, namespace: "unidesk-dev", expectedNodeIds: [artifactDeployNodeId], activeNodeId: artifactDeployNodeId, }, development: { providerId: artifactDeployProviderId, sshPassthrough: true, worktreePath: id === "code-queue" ? "/home/ubuntu/unidesk-dev-code-queue-deploy/code-queue" : `/home/ubuntu/unidesk-dev-core-deploy/${id}`, }, frontend: { route: spec.route, integrated: false, }, }; } export function coreDeployService(config: UniDeskConfig, id: string, environment: DeployEnvironment | null): UniDeskMicroserviceConfig | undefined { if (environment === "dev") return devK3sDeployService(id); if (id === "frontend") return frontendCoreDeployService(config); return undefined; } export function isCoreDeployService(service: UniDeskMicroserviceConfig): boolean { return service.id === "frontend" && service.providerId === "main-server" && service.backend.proxyMode === "core-direct"; } export function isDevK3sDeployService(service: UniDeskMicroserviceConfig): boolean { return service.deployment.mode === "k3sctl-managed" && (devApplySupportedServiceIds.has(service.id) || devArtifactConsumerServiceIds.has(service.id)); } export function isDevArtifactConsumerService(service: UniDeskMicroserviceConfig): boolean { return (service.deployment.mode === "k3sctl-managed" || isDirectComposeDeployMode(service)) && devArtifactConsumerServiceIds.has(service.id); } export function isTargetSideMaintenanceDeployBlocked(service: UniDeskMicroserviceConfig): boolean { if (targetIsMain(service)) return false; if (service.providerId !== "D601") return false; return !targetSideMaintenanceDeployAllowedServiceIds.has(service.id); } export function isDirectComposeDeployMode(service: UniDeskMicroserviceConfig): boolean { return service.deployment.mode === "unidesk-direct" || service.deployment.mode === "internal-sidecar"; } export function selectServices(config: UniDeskConfig, manifest: DeployManifest, serviceId: string | null): Array<{ desired: DeployManifestService; config: UniDeskMicroserviceConfig; }> { const configById = new Map(config.microservices.map((service) => [service.id, service])); const selected = serviceId === null ? manifest.services : manifest.services.filter((service) => service.id === serviceId); if (serviceId !== null && selected.length === 0) throw new Error(`deploy manifest does not contain service: ${serviceId}`); return selected.map((desired) => { if (manifest.environment === "dev") { const service = devK3sDeployService(desired.id); if (service !== undefined) return { desired, config: service }; const directService = configById.get(desired.id); if (directService !== undefined && isDirectComposeDeployMode(directService) && devArtifactConsumerServiceIds.has(directService.id)) { return { desired, config: directService }; } throw new Error(`deploy --env dev service ${desired.id} is not enabled for direct rollout in the current CI-only phase`); } const service = configById.get(desired.id) ?? coreDeployService(config, desired.id, manifest.environment); if (service === undefined) throw new Error(`deploy manifest service ${desired.id} is not present in config.json microservices or supported core deploy services`); return { desired, config: service }; }); } export function unsupportedReason(service: UniDeskMicroserviceConfig): string | null { if (service.repository.dockerfile.startsWith("docker.io/")) return "image-only service has no Dockerfile source artifact"; if (service.repository.composeFile.startsWith("docker run")) return "docker-run image-only service has no compose/k8s build path"; if (isTargetSideMaintenanceDeployBlocked(service)) return "D601 maintenance-channel deployment is not allowed for this service; use reviewed registry artifact consumers instead"; if (service.repository.commitId === "local") return null; return null; } export function databaseFingerprint(target: DeployEnvironmentTarget): string { const material = [ target.environment, target.namespace, target.database.serviceId, target.database.host, target.database.port.toString(), target.database.name, target.provider.providerId, ].join("|"); return `sha256:${createHash("sha256").update(material).digest("hex").slice(0, 16)}`; } export function environmentTargetSummary(target: DeployEnvironmentTarget): Record { return { namespace: target.namespace, runtimeScope: target.runtimeScope, database: { ...target.database, fingerprint: databaseFingerprint(target), }, provider: target.provider, }; } export function artifactConsumerPlanKind(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): ArtifactConsumerPlanKind { if (environment === "dev" && devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id)) { return "d601-dev-target-side-build"; } if (service.deployment.mode === "k3sctl-managed") return "d601-k3s-managed"; if (service.providerId === "D601" && service.deployment.mode === "unidesk-direct") return "d601-direct-compose"; if (targetIsMain(service) && isDirectComposeDeployMode(service)) return "main-server-compose"; return "unsupported"; } export function artifactConsumerPlanKindFromDeployJson(service: DeployManifestService, fallback: ArtifactConsumerPlanKind): ArtifactConsumerPlanKind { if (service.consumer?.kind === "d601-k3s-managed") return "d601-k3s-managed"; return fallback; } export function deploymentPathForEnvironmentService(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string { return deploymentPathForEnvironmentServiceId(service.id, environment); } export function deploymentPathForEnvironmentServiceId(serviceId: string, environment: DeployEnvironment): string { if (environment === "prod") return prodArtifactConsumerServiceIds.has(serviceId) ? "d601-registry-artifact-consumer" : "unsupported"; if (devArtifactConsumerServiceIds.has(serviceId)) return "d601-registry-artifact-consumer"; if (devApplySupportedServiceIds.has(serviceId)) return "d601-dev-target-side-build"; return "unsupported"; } export function artifactConsumerPlanTarget(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): Record { const kind = artifactConsumerPlanKind(service, environment); const liveBlockReason = environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null; const dryRunBlockedReason = artifactConsumerDryRunBlockedServiceIds.get(service.id) ?? null; const targetImage = artifactConsumerTargetImageHint(service, environment); const common = { consumerKind: kind, runtimeHost: kind === "main-server-compose" ? "main-server" : "D601", deploymentMode: service.deployment.mode, noRuntimeSourceBuild: kind !== "d601-dev-target-side-build", dryRunOnly: dryRunBlockedReason !== null || (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)), blockedReason: dryRunBlockedReason ?? liveBlockReason, }; if (kind === "d601-k3s-managed") { return { ...common, namespace: service.deployment.namespace ?? (environment === "dev" ? "unidesk-dev" : k8sNamespace), deployment: service.repository.composeService, service: service.deployment.k3sServiceId ?? service.repository.composeService, containerName: service.repository.containerName, manifest: service.repository.composeFile, targetImage, deployCommandShape: "registry manifest check + native k3s containerd import + Kubernetes Deployment image/env/annotation update + API service proxy health", forbiddenActions: ["docker build", "docker compose build", "NodePort", "hostPort", "provider-gateway direct business backend"], }; } if (kind === "d601-direct-compose" || kind === "main-server-compose") { return { ...common, workDir: targetWorkDir(service), composeFile: service.repository.composeFile, composeService: service.repository.composeService, containerName: service.repository.containerName, targetImage, deployCommandShape: `docker compose up -d --no-build --no-deps --force-recreate ${service.repository.composeService}`, forbiddenActions: ["docker build", "docker compose build", "docker compose up --build", "dirty worktree source deploy"], }; } if (kind === "d601-dev-target-side-build") { return { ...common, namespace: service.deployment.namespace ?? "unidesk-dev", deployment: service.repository.composeService, manifest: service.repository.composeFile, workDir: targetWorkDir(service), deployCommandShape: "D601 dev target-side source materialization/build/import/rollout", forbiddenActions: ["production namespace mutation", "production database mutation"], }; } return common; } export function artifactConsumerPlanTargetFromDeployJson( service: DeployManifestService, fallbackService: UniDeskMicroserviceConfig, environment: DeployEnvironment, fallbackTarget: Record | null, ): Record | null { if (service.consumer?.kind !== "d601-k3s-managed") return fallbackTarget; const target = service.consumer.target; const liveBlockReason = environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null; const dryRunBlockedReason = artifactConsumerDryRunBlockedServiceIds.get(service.id) ?? null; return { consumerKind: service.consumer.kind, runtimeHost: "D601", deploymentMode: fallbackService.deployment.mode, noRuntimeSourceBuild: service.consumer.noRuntimeSourceBuild, dryRunOnly: dryRunBlockedReason !== null || (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)), blockedReason: dryRunBlockedReason ?? liveBlockReason, namespace: target.namespace, deployment: target.deployment, service: target.service, containerName: target.containerName, manifest: target.manifestRepoPath, targetImage: target.stableImage, deployCommandShape: "registry manifest check + native k3s containerd import + Kubernetes Deployment image/env/annotation update + API service proxy health", forbiddenActions: ["docker build", "docker compose build", "NodePort", "hostPort", "provider-gateway direct business backend"], sourceOfTruth: deployJsonSourceOfTruth(service, environment), }; } export function artifactConsumerTargetImageHint(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string { if (service.id === "frontend" && environment === "prod") return "unidesk-frontend"; if (service.id === "frontend" && environment === "dev") return "unidesk-frontend:dev"; if (service.id === "k3sctl-adapter") return "unidesk-k3sctl-adapter:d601"; if (service.id === "met-nonlinear") return "met-nonlinear-ml:tf26"; if (service.deployment.mode === "k3sctl-managed") return `unidesk-${service.id}:${environment === "dev" ? "dev" : "d601"}`; const container = service.repository.containerName; if (container.endsWith("-backend")) return container.slice(0, -"-backend".length); return container; } export function deployJsonExecutorMirrors( service: DeployManifestService, serviceConfig: UniDeskMicroserviceConfig, environment: DeployEnvironment, planKind: ArtifactConsumerPlanKind, planTarget: Record | null, ): DeployJsonExecutorMirror[] { const mirrors: DeployJsonExecutorMirror[] = []; mirrors.push({ surface: "deploy-executor-plan", artifact: { kind: "source-build", repository: `unidesk/${service.id}`, tag: "commitId", }, consumer: { kind: planKind === "d601-k3s-managed" ? "d601-k3s-managed" : planKind, noRuntimeSourceBuild: planKind !== "d601-dev-target-side-build", target: planTarget === null ? undefined : { namespace: typeof planTarget.namespace === "string" ? planTarget.namespace : undefined, deployment: typeof planTarget.deployment === "string" ? planTarget.deployment : undefined, service: typeof planTarget.service === "string" ? planTarget.service : undefined, containerName: typeof planTarget.containerName === "string" ? planTarget.containerName : undefined, stableImage: typeof planTarget.targetImage === "string" ? planTarget.targetImage : undefined, manifestRepoPath: typeof planTarget.manifest === "string" ? planTarget.manifest : undefined, }, }, runtime: { containerPort: serviceConfig.backend.nodePort, servicePort: serviceConfig.backend.nodePort, healthPath: serviceConfig.backend.healthPath, health: service.runtime?.health === undefined ? undefined : { deployMetadataRequired: true, }, }, }); const manifestMirror = k3sManifestExecutorMirror(service); if (manifestMirror !== null) mirrors.push(manifestMirror); return mirrors; } export function artifactConsumerPlanValidation(service: UniDeskMicroserviceConfig, environment: DeployEnvironment): string[] { const kind = artifactConsumerPlanKind(service, environment); if (kind === "d601-dev-target-side-build") { return [ "reads origin/master:deploy.json for dev desired state", "materializes and builds only the selected dev service on D601", "rolls out only unidesk-dev resources", ]; } const base = [ "reads origin/master:deploy.json for commit intent", "requires a commit-pinned artifact in 127.0.0.1:5000", "verifies image labels for service id, source repo, source commit, and Dockerfile", "does not build source on the runtime target", ]; if (kind === "d601-k3s-managed") { return [ ...base, "imports the artifact into native k3s containerd", "verifies Deployment metadata and health through the Kubernetes API service proxy", ]; } if (kind === "d601-direct-compose" || kind === "main-server-compose") { return [ ...base, "recreates only the selected Compose service with --no-build --no-deps --force-recreate", "verifies running image labels and private service health", ]; } return ["unsupported service remains blocked before source materialization or runtime mutation"]; } export function unsupportedEnvironmentPlanReason(serviceId: string, environment: DeployEnvironment): string { if (environment === "prod" && serviceId === "code-queue") { return "Code Queue production CD is intentionally unsupported in this phase: only CI image publication and dev artifact-consumer validation are implemented; prod artifact deploy, rollout and manifest mutation are blocked."; } return environment === "prod" ? "No standardized prod D601 registry artifact consumer is implemented for this service; legacy maintenance-channel deployment is not allowed." : "No standardized dev deployment or artifact validation path is implemented for this service."; } export function unsupportedPlanTarget(serviceId: string, environment: DeployEnvironment, reason: string): Record { const forbiddenActions = [ "docker build", "docker compose build", "docker compose up --build", "maintenance-channel source deploy", "dirty worktree source deploy", ]; if (serviceId === "code-queue") { forbiddenActions.push( "codex deploy", "server rebuild code-queue", "production namespace mutation", "production manifest mutation", "interrupt running Code Queue tasks", "cancel running Code Queue tasks", ); } return { consumerKind: "unsupported", runtimeHost: null, deploymentMode: "none", noRuntimeSourceBuild: true, dryRunOnly: true, blockedReason: reason, deployCommandShape: "none", forbiddenActions, ...(serviceId === "code-queue" ? { excludedTargets: [ { namespace: "unidesk", deployments: ["code-queue", "code-queue-read", "code-queue-write", "d601-provider-egress-proxy", "d601-tcp-egress-gateway"], reason: `${environment} dry-run must not mutate production Code Queue execution-plane objects.`, }, { operations: ["interrupt", "cancel", "restart scheduler", "restart runner"], reason: "Code Queue cannot self-deploy or alter active task control state from this runner path.", }, ], } : {}), }; } export function codeQueueSelfBootstrapGuard(environment: DeployEnvironment): Record | undefined { if (environment !== "dev" && environment !== "prod") return undefined; return { check: "code-queue-self-bootstrap-guard", selfBootstrapBlocked: true, requiresSupervisorApproval: true, actorBoundary: "a running Code Queue task may publish contract evidence and dry-run plans only; it must not deploy Code Queue itself", devApply: "requires explicit supervisor or human authorization outside the running Code Queue task", prodApply: "unsupported until a separate supervisor-approved production Code Queue CD design exists", allowedWithoutApproval: [ "CI artifact publication", "deploy plan", "deploy apply --dry-run", "artifact-registry deploy-service --dry-run", ], forbiddenActions: [ "self-deploy Code Queue from Code Queue", "run a non-dry-run DEV apply from Code Queue", "production namespace mutation", "production manifest mutation", "scheduler or runner restart", "active task interrupt", "active task cancel", ], environment, }; } export function codeQueueCiCdBoundary(serviceId: string, environment: DeployEnvironment): Record | undefined { if (serviceId !== "code-queue") return undefined; return { serviceId, selfDeployBlocked: true, requiresSupervisorApproval: true, selfBootstrapGuard: codeQueueSelfBootstrapGuard(environment), ciProducer: { command: "bun scripts/cli.ts ci publish-user-service --service code-queue --commit ", allowed: true, outputImage: "127.0.0.1:5000/unidesk/code-queue:", responsibility: "build a commit-pinned Code Queue image from pushed Git source, push it to the D601 registry, and report digest/label evidence", forbiddenActions: ["deploy apply", "kubectl apply", "rollout restart", "interrupt", "cancel"], }, cdConsumer: environment === "dev" ? { environment: "dev", dryRunCommand: "bun scripts/cli.ts deploy apply --env dev --service code-queue --commit --dry-run", liveApplyCommandShape: null, liveApplyAllowed: false, requiresSupervisorApproval: true, allowedTarget: "unidesk-dev Code Queue scheduler/read/write/provider-egress-proxy only", manualAuthorizationPoint: "operator reviews CI artifact summary and dev dry-run plan, then explicitly authorizes DEV apply outside this Code Queue task", prodMutationAllowed: false, } : { environment: "prod", dryRunCommand: "bun scripts/cli.ts deploy plan --env prod --service code-queue", liveApplyCommandShape: null, liveApplyAllowed: false, requiresSupervisorApproval: true, allowedTarget: null, manualAuthorizationPoint: "PROD requires a future supervisor-approved Code Queue CD design; current runner output is plan/unsupported only", prodMutationAllowed: false, }, manualAuthorization: { dev: "DEV live apply is a human operator decision after dry-run evidence; this task may not execute it.", prod: "PROD live apply is not implemented and must remain unsupported, even with manual intent, until a separate reviewed CD consumer exists.", }, forbiddenActions: [ "codex deploy", "server rebuild code-queue", "deploy apply --env prod --service code-queue", "artifact-registry deploy-service --env prod --service code-queue", "kubectl apply to namespace unidesk", "rollout restart production Code Queue scheduler/read/write", "interrupt or cancel active Code Queue tasks", ], }; }