Files
pikasTech-unidesk/scripts/src/deploy/service-plan.ts
T
2026-07-10 05:35:20 +02:00

635 lines
28 KiB
TypeScript

// 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<string, {
name: string;
description: string;
repoUrl?: string;
dockerfile: string;
composeService: string;
composeFile: string;
containerName: string;
nodeBaseUrl: string;
nodePort: number;
healthPath: string;
route: string;
allowedMethods: string[];
allowedPathPrefixes: string[];
}> = {
"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<string, unknown> {
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<string, unknown> {
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<string, unknown> | null,
): Record<string, unknown> | 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<string, unknown> | 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<string, unknown> {
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<string, unknown> | 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<string, unknown> | 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 <full-sha>",
allowed: true,
outputImage: "127.0.0.1:5000/unidesk/code-queue:<full-sha>",
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 <full-sha> --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",
],
};
}