feat: add D601 direct artifact consumer

This commit is contained in:
Codex
2026-05-20 02:09:31 +00:00
parent 852476448f
commit 1faacb94e5
11 changed files with 431 additions and 32 deletions
+360 -8
View File
@@ -71,14 +71,27 @@ const defaultOptions: ArtifactRegistryOptions = {
sourceRepoExplicit: false,
deployRef: null,
};
const supportedArtifactConsumerServices = ["backend-core", "baidu-netdisk", "code-queue-mgr", "decision-center", "frontend", "oa-event-flow", "project-manager", "todo-note"] as const;
const supportedArtifactConsumerServices = [
"backend-core",
"baidu-netdisk",
"code-queue-mgr",
"decision-center",
"findjob",
"frontend",
"k3sctl-adapter",
"met-nonlinear",
"oa-event-flow",
"pipeline",
"project-manager",
"todo-note",
] as const;
type SupportedArtifactConsumerService = typeof supportedArtifactConsumerServices[number];
const legacyDeployBackendCoreDisabled = true;
interface ArtifactConsumerSpec {
serviceId: SupportedArtifactConsumerService;
environment?: ArtifactDeployEnvironment;
kind: "compose" | "d601-k3s";
kind: "compose" | "d601-compose" | "d601-k3s";
registryRepository: string;
sourceRepo?: string;
dockerfile: string;
@@ -99,6 +112,9 @@ interface ArtifactConsumerTarget {
deployEnvPrefix: string;
healthProbeCommand: string;
requireHealthCommit: boolean;
workDir?: string;
composeFile?: string;
projectHint?: string;
};
k3s?: {
namespace: string;
@@ -270,6 +286,117 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
},
},
},
"findjob": {
serviceId: "findjob",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://gitee.com/Lyon1998/findjob",
registryRepository: "unidesk/findjob",
dockerfile: "Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "findjob-server",
targetCommitImage: (commit: string) => `findjob-server:${commit}`,
deployRef: "deploy.json#environments.dev.services.findjob",
compose: {
serviceName: "server",
containerName: "findjob-server",
deployEnvPrefix: "UNIDESK_FINDJOB_DEPLOY",
workDir: "/home/ubuntu/findjob",
composeFile: "docker-compose.yml",
projectHint: "findjob",
healthProbeCommand: "curl -fsS --max-time 12 http://127.0.0.1:3254/api/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "findjob-server",
targetCommitImage: (commit: string) => `findjob-server:${commit}`,
deployRef: "deploy.json#environments.prod.services.findjob",
compose: {
serviceName: "server",
containerName: "findjob-server",
deployEnvPrefix: "UNIDESK_FINDJOB_DEPLOY",
workDir: "/home/ubuntu/findjob",
composeFile: "docker-compose.yml",
projectHint: "findjob",
healthProbeCommand: "curl -fsS --max-time 12 http://127.0.0.1:3254/api/health",
requireHealthCommit: false,
},
},
},
},
"k3sctl-adapter": {
serviceId: "k3sctl-adapter",
environment: "prod",
kind: "d601-compose",
registryRepository: "unidesk/k3sctl-adapter",
dockerfile: "src/components/microservices/k3sctl-adapter/Dockerfile",
prodLiveApply: "supervisor-only",
prodLiveBlockReason: "k3sctl-adapter is an infrastructure control bridge; this executor exposes artifact consumer plan/dry-run only. Real production deployment requires supervisor confirmation outside this task.",
targets: {
prod: {
targetImage: "unidesk-k3sctl-adapter:d601",
targetCommitImage: (commit: string) => `unidesk-k3sctl-adapter:${commit}`,
deployRef: "deploy.json#environments.prod.services.k3sctl-adapter",
compose: {
serviceName: "k3sctl-adapter",
containerName: "k3sctl-adapter",
deployEnvPrefix: "UNIDESK_K3SCTL_ADAPTER_DEPLOY",
workDir: "/home/ubuntu/cq-deploy",
composeFile: "src/components/microservices/k3sctl-adapter/docker-compose.d601.yml",
projectHint: "k3sctl-adapter",
healthProbeCommand: "curl -fsS --max-time 10 http://127.0.0.1:4266/health",
requireHealthCommit: false,
},
},
},
},
"met-nonlinear": {
serviceId: "met-nonlinear",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://github.com/pikasTech/met_nonlinear",
registryRepository: "unidesk/met-nonlinear",
dockerfile: "docker/unidesk/Dockerfile.ml",
prodLiveApply: "unsupported",
prodLiveBlockReason: "met-nonlinear is blocked for live artifact deploy because config.json points at docker/unidesk/Dockerfile.ml while the compose service is met-nonlinear-ts. The current compose contract does not let CD prove that the recreated long-running container image label equals the requested commit.",
runtimeVerification: "blocked",
runtimeVerificationBlockReason: "D601 direct artifact consumer is implemented, but this service's registered Dockerfile is the ML image contract while the long-running Compose service is met-nonlinear-ts. Publish a labeled artifact that matches the running service image contract before live deploy, or update the service contract to a server Dockerfile.",
targets: {
dev: {
targetImage: "met-nonlinear-ml:tf26",
targetCommitImage: (commit: string) => `met-nonlinear-ml:${commit}`,
deployRef: "deploy.json#environments.dev.services.met-nonlinear",
compose: {
serviceName: "met-nonlinear-ts",
containerName: "met-nonlinear-ts",
deployEnvPrefix: "UNIDESK_MET_NONLINEAR_DEPLOY",
workDir: "/home/ubuntu/met_nonlinear",
composeFile: "docker-compose.unidesk.yml",
projectHint: "met-nonlinear",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:3288/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "met-nonlinear-ml:tf26",
targetCommitImage: (commit: string) => `met-nonlinear-ml:${commit}`,
deployRef: "deploy.json#environments.prod.services.met-nonlinear",
compose: {
serviceName: "met-nonlinear-ts",
containerName: "met-nonlinear-ts",
deployEnvPrefix: "UNIDESK_MET_NONLINEAR_DEPLOY",
workDir: "/home/ubuntu/met_nonlinear",
composeFile: "docker-compose.unidesk.yml",
projectHint: "met-nonlinear",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:3288/health",
requireHealthCommit: false,
},
},
},
},
"oa-event-flow": {
serviceId: "oa-event-flow",
environment: "prod",
@@ -338,6 +465,47 @@ const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
},
},
},
"pipeline": {
serviceId: "pipeline",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://github.com/pikasTech/pipeline",
registryRepository: "unidesk/pipeline",
dockerfile: "Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "pipeline-v2-control",
targetCommitImage: (commit: string) => `pipeline-v2-control:${commit}`,
deployRef: "deploy.json#environments.dev.services.pipeline",
compose: {
serviceName: "pipeline-control",
containerName: "pipeline-v2-control",
deployEnvPrefix: "UNIDESK_PIPELINE_DEPLOY",
workDir: "/home/ubuntu/pipeline",
composeFile: "docker-compose.yml",
projectHint: "pipeline",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:18082/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "pipeline-v2-control",
targetCommitImage: (commit: string) => `pipeline-v2-control:${commit}`,
deployRef: "deploy.json#environments.prod.services.pipeline",
compose: {
serviceName: "pipeline-control",
containerName: "pipeline-v2-control",
deployEnvPrefix: "UNIDESK_PIPELINE_DEPLOY",
workDir: "/home/ubuntu/pipeline",
composeFile: "docker-compose.yml",
projectHint: "pipeline",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:18082/health",
requireHealthCommit: false,
},
},
},
},
"todo-note": {
serviceId: "todo-note",
environment: "prod",
@@ -1318,6 +1486,167 @@ async function deployBackendCoreNow(options: ArtifactRegistryOptions): Promise<R
return deployComposeArtifactNow(options, spec, target);
}
function d601ComposeArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const compose = target.compose;
const sourceImage = artifactImageRef(options, spec, commit);
const sourceRepo = sourceRepoFor(options, spec);
const commitImage = target.targetCommitImage(commit);
const envValues = composeArtifactEnvValues(spec, target, options, commit);
const labels = {
"unidesk.ai/deploy-service-id": spec.serviceId,
"unidesk.ai/deploy-ref": deployRefFor(options, spec),
"unidesk.ai/deploy-repo": sourceRepo,
"unidesk.ai/deploy-commit": commit,
"unidesk.ai/deploy-requested-commit": commit,
"unidesk.ai/image-source": sourceImage,
"unidesk.ai/deploy-environment": options.environment ?? "prod",
};
const override = {
services: {
[compose.serviceName]: {
image: "${UNIDESK_ARTIFACT_STABLE_IMAGE}",
labels,
environment: {
UNIDESK_DEPLOY_SERVICE_ID: spec.serviceId,
UNIDESK_DEPLOY_REF: deployRefFor(options, spec),
UNIDESK_DEPLOY_REPO: sourceRepo,
UNIDESK_DEPLOY_COMMIT: commit,
UNIDESK_DEPLOY_REQUESTED_COMMIT: commit,
...envValues,
},
},
},
};
return [
"set -euo pipefail",
`registry_image=${shellQuote(sourceImage)}`,
`stable_image=${shellQuote(target.targetImage)}`,
`commit_image=${shellQuote(commitImage)}`,
`service_id=${shellQuote(spec.serviceId)}`,
`source_repo=${shellQuote(sourceRepo)}`,
`deploy_ref=${shellQuote(deployRefFor(options, spec))}`,
`commit=${shellQuote(commit)}`,
`dockerfile=${shellQuote(spec.dockerfile)}`,
`work_dir=${shellQuote(compose.workDir ?? "/home/ubuntu")}`,
`compose_file=${shellQuote(compose.composeFile ?? "docker-compose.yml")}`,
`compose_service=${shellQuote(compose.serviceName)}`,
`container=${shellQuote(compose.containerName)}`,
`project_hint=${shellQuote(compose.projectHint ?? "")}`,
`health_probe_b64=${shellQuote(base64(compose.healthProbeCommand))}`,
`override_b64=${shellQuote(base64(JSON.stringify(override, null, 2)))}`,
"command -v docker >/dev/null",
"docker compose version >/dev/null",
`curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' ${shellQuote(`http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`)} >/dev/null`,
"docker pull -q \"$registry_image\" >/dev/null",
"label_commit=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}')",
"label_service=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/service-id\" }}')",
"label_dockerfile=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/dockerfile\" }}')",
"test \"$label_commit\" = \"$commit\"",
"test \"$label_service\" = \"$service_id\"",
"test \"$label_dockerfile\" = \"$dockerfile\"",
"test -d \"$work_dir\"",
"test -f \"$work_dir/$compose_file\"",
"docker tag \"$registry_image\" \"$stable_image\"",
"docker tag \"$registry_image\" \"$commit_image\"",
"override=\"$work_dir/.unidesk-artifact-consumer.override.yml\"",
"printf '%s' \"$override_b64\" | base64 -d > \"$override\"",
"export UNIDESK_ARTIFACT_STABLE_IMAGE=\"$stable_image\"",
"project=$(docker inspect \"$container\" --format '{{ index .Config.Labels \"com.docker.compose.project\" }}' 2>/dev/null || true)",
"if [ -z \"$project\" ]; then project=\"$project_hint\"; fi",
"if [ -z \"$project\" ]; then project=$(basename \"$work_dir\"); fi",
"cd \"$work_dir\"",
"docker compose -p \"$project\" -f \"$compose_file\" -f \"$override\" up -d --no-build --no-deps --force-recreate \"$compose_service\"",
"ready=0",
"for attempt in $(seq 1 90); do",
" cid=$(docker ps -q --filter label=com.docker.compose.project=\"$project\" --filter label=com.docker.compose.service=\"$compose_service\" --filter label=com.docker.compose.oneoff=False | head -1 || true)",
" if [ -n \"$cid\" ]; then",
" health=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \"$cid\" 2>/dev/null || true)",
" echo \"artifact_cd_container_probe attempt=$attempt cid=$cid health=$health\"",
" if [ \"$health\" = \"healthy\" ] || [ \"$health\" = \"running\" ]; then ready=1; break; fi",
" else",
" echo \"artifact_cd_container_probe attempt=$attempt cid=missing\"",
" fi",
" sleep 2",
"done",
"test \"$ready\" = \"1\"",
"cid=$(docker ps -q --filter label=com.docker.compose.project=\"$project\" --filter label=com.docker.compose.service=\"$compose_service\" --filter label=com.docker.compose.oneoff=False | head -1)",
"image_id=$(docker inspect -f '{{.Image}}' \"$cid\")",
"actual_commit=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}' \"$image_id\")",
"actual_service=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/service-id\" }}' \"$image_id\")",
"container_commit=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-commit\" }}' \"$cid\")",
"test \"$actual_commit\" = \"$commit\"",
"test \"$actual_service\" = \"$service_id\"",
"test \"$container_commit\" = \"$commit\"",
"health_probe=$(printf '%s' \"$health_probe_b64\" | base64 -d)",
"docker exec \"$cid\" sh -lc \"$health_probe\" >/tmp/unidesk-artifact-health.out",
"cat /tmp/unidesk-artifact-health.out",
"printf 'artifact_cd_service=%s\\nartifact_cd_source_repo=%s\\nartifact_cd_deploy_ref=%s\\nartifact_cd_source_image=%s\\nartifact_cd_stable_image=%s\\nartifact_cd_runtime_image=%s\\nartifact_cd_commit=%s\\nartifact_cd_container=%s\\nartifact_cd_container_id=%s\\nartifact_cd_image_label_commit=%s\\nartifact_cd_container_label_commit=%s\\n' \"$service_id\" \"$source_repo\" \"$deploy_ref\" \"$registry_image\" \"$stable_image\" \"$commit_image\" \"$commit\" \"$container\" \"$cid\" \"$actual_commit\" \"$container_commit\"",
].join("\n");
}
async function deployD601ComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const environment = options.environment ?? "prod";
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const health = runReadonlyStatus(options, true);
if (health.ok !== true) return { ok: false, serviceId: spec.serviceId, error: "D601 artifact registry is not healthy", health };
const sourceImage = artifactImageRef(options, spec, commit);
const registryProbe = runRemoteScript(options, registryArtifactProbeScript(options, spec, commit), Math.max(options.timeoutMs, 120_000));
if (registryProbe.exitCode !== 0 || registryProbe.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "registry-artifact-check",
error: registryArtifactMissingMessage(spec),
sourceImage,
registryProbe: commandTail(registryProbe),
};
}
const deploy = runRemoteScript(options, d601ComposeArtifactDeployScript(options, spec, target, commit), Math.max(options.timeoutMs, 420_000));
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "d601-compose-artifact-deploy",
sourceImage,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
rollback: rollbackInfo(spec, target, environment, commit),
};
}
return {
ok: true,
supported: true,
serviceId: spec.serviceId,
environment,
commit,
providerId: options.providerId,
sourceRepo: sourceRepoFor(options, spec),
deployRef: deployRefFor(options, spec),
sourceImage,
targetImage: target.targetImage,
targetCommitImage: target.targetCommitImage(commit),
composeService: target.compose.serviceName,
containerName: target.compose.containerName,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
validation: {
liveCommit: commit,
liveRequestedCommit: commit,
imageLabelCommit: commit,
containerDeployLabelCommit: commit,
serviceHealthCommit: target.compose.requireHealthCommit ? commit : "not-required",
serviceHealthRequestedCommit: target.compose.requireHealthCommit ? commit : "not-required",
healthyOldVersionAccepted: false,
},
rollback: rollbackInfo(spec, target, environment, commit),
};
}
function deployBackendCoreJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
const spec = artifactConsumerSpecs["backend-core"];
@@ -1383,12 +1712,15 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
reason: spec.runtimeVerificationBlockReason ?? spec.prodLiveBlockReason ?? null,
},
};
if (spec.kind === "compose") {
if (spec.kind === "compose" || spec.kind === "d601-compose") {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
return {
...common,
target: {
kind: "compose",
kind: spec.kind,
runtimeHost: spec.kind === "d601-compose" ? "D601" : "main-server",
workDir: target.compose.workDir,
composeFile: target.compose.composeFile,
composeService: target.compose.serviceName,
containerName: target.compose.containerName,
targetImage: options.targetImage ?? target.targetImage,
@@ -1398,8 +1730,13 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
},
validation: [
"D601 registry /v2 manifest exists for the commit tag",
"loaded image labels match service id, source commit, and Dockerfile",
"running Compose container image label matches the requested commit",
spec.kind === "d601-compose"
? "D601-pulled image labels match service id, source commit, and Dockerfile"
: "loaded image labels match service id, source commit, and Dockerfile",
spec.kind === "d601-compose"
? "running D601 Compose container is recreated with a no-build override that points the service image at the artifact"
: "running Compose container image label matches the requested commit",
...(spec.kind === "d601-compose" ? ["running Compose container image label matches the requested commit"] : []),
verificationBlocked
? `blocked: ${spec.runtimeVerificationBlockReason}`
: target.compose.requireHealthCommit
@@ -1434,11 +1771,14 @@ function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: Arti
}
function rollbackInfo(spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, environment: ArtifactDeployEnvironment, commit: string): Record<string, unknown> {
if (spec.kind === "compose") {
if (spec.kind === "compose" || spec.kind === "d601-compose") {
const compose = target.compose;
return {
type: "compose-retag-recreate",
type: spec.kind === "d601-compose" ? "d601-compose-retag-recreate" : "compose-retag-recreate",
composeService: compose?.serviceName,
containerName: compose?.containerName,
workDir: compose?.workDir,
composeFile: compose?.composeFile,
previousImageHint: `Use docker image ls / docker inspect to find the previous labeled ${spec.serviceId} image id; Compose volumes are unchanged.`,
commandShape: `bun scripts/cli.ts deploy apply --env ${environment} --service ${spec.serviceId}${environment === "prod" ? " --commit <previous-full-sha>" : ""}`,
};
@@ -1670,6 +2010,7 @@ async function deployServiceNow(options: ArtifactRegistryOptions): Promise<Recor
const liveBlock = artifactConsumerLiveBlock(spec, options);
if (liveBlock !== null) return liveBlock;
if (spec.kind === "compose") return deployComposeArtifactNow(options, spec, target);
if (spec.kind === "d601-compose") return deployD601ComposeArtifactNow(options, spec, target);
return deployD601K3sArtifactNow(options, spec, target);
}
@@ -1724,6 +2065,10 @@ function localHelp(): Record<string, unknown> {
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service code-queue-mgr --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service findjob --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service pipeline --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service met-nonlinear --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service k3sctl-adapter --commit <full-sha> --dry-run [--provider-id D601]",
],
firstStage: "install now writes the rendered systemd/Compose/config files and starts the registry",
artifactConsumers: {
@@ -1739,6 +2084,10 @@ function localHelp(): Record<string, unknown> {
"bun scripts/cli.ts deploy apply --env prod --service oa-event-flow",
"bun scripts/cli.ts deploy apply --env prod --service code-queue-mgr --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service todo-note",
"bun scripts/cli.ts deploy apply --env prod --service findjob --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service pipeline --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service met-nonlinear --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service k3sctl-adapter --dry-run",
],
devCommands: [
"bun scripts/cli.ts deploy apply --env dev --service frontend",
@@ -1747,6 +2096,9 @@ function localHelp(): Record<string, unknown> {
"bun scripts/cli.ts deploy apply --env dev --service oa-event-flow --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service code-queue-mgr --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service todo-note",
"bun scripts/cli.ts deploy apply --env dev --service findjob --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service pipeline --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service met-nonlinear --dry-run",
],
rollbackShape: "rerun the same artifact consumer with a previous commit-pinned image",
},
+13 -9
View File
@@ -136,12 +136,14 @@ const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock";
const unideskRepoUrl = "https://github.com/pikasTech/unidesk";
const d601MaintenanceDeployAllowedServiceIds = new Set<string>(["backend-core", "k3sctl-adapter", "code-queue"]);
const devApplySupportedServiceIds = new Set<string>(["backend-core"]);
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "code-queue-mgr", "decision-center", "frontend", "oa-event-flow", "project-manager", "todo-note"]);
const devArtifactConsumerServiceIds = new Set<string>(["baidu-netdisk", "code-queue-mgr", "decision-center", "findjob", "frontend", "met-nonlinear", "oa-event-flow", "pipeline", "project-manager", "todo-note"]);
const devArtifactConsumerProdDesiredFallbackServiceIds = new Set<string>(["code-queue-mgr", "oa-event-flow", "project-manager", "todo-note"]);
const prodArtifactConsumerServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "code-queue-mgr", "decision-center", "frontend", "oa-event-flow", "project-manager", "todo-note"]);
const prodForbiddenTargetSideBuildServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "decision-center", "frontend"]);
const prodArtifactConsumerServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "code-queue-mgr", "decision-center", "findjob", "frontend", "k3sctl-adapter", "met-nonlinear", "oa-event-flow", "pipeline", "project-manager", "todo-note"]);
const prodForbiddenTargetSideBuildServiceIds = new Set<string>(["backend-core", "baidu-netdisk", "decision-center", "findjob", "frontend", "k3sctl-adapter", "met-nonlinear", "pipeline"]);
const prodArtifactLiveApplyBlockedServiceIds = new Map<string, string>([
["code-queue-mgr", "code-queue-mgr is the main-server Code Queue control-plane sidecar; live production apply requires explicit supervisor confirmation."],
["met-nonlinear", "met-nonlinear is blocked for live artifact deploy because config.json points at docker/unidesk/Dockerfile.ml while the compose service is met-nonlinear-ts."],
["k3sctl-adapter", "k3sctl-adapter is an infrastructure control bridge; this executor exposes artifact consumer plan/dry-run only. Real production deployment requires supervisor confirmation outside this task."],
]);
const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironmentTarget> = {
dev: {
@@ -205,7 +207,7 @@ export function deployHelp(action: string | undefined = undefined): Record<strin
},
options: [
{ name: "--file <path>", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js. Local manifest apply allows k3sctl-adapter and explicit production code-queue controlled rollout on D601." },
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus reviewed artifact consumers for frontend, baidu-netdisk, decision-center, project-manager, oa-event-flow, code-queue-mgr, and todo-note. Prod apply uses the D601 registry artifact consumer for reviewed services; code-queue-mgr live apply is supervisor-gated." },
{ name: "--env <dev|prod>", description: "Read the named environment from origin/master:deploy.json. Dev apply supports backend-core target-side rollout plus reviewed artifact consumers for frontend, baidu-netdisk, decision-center, project-manager, oa-event-flow, code-queue-mgr, todo-note, findjob, pipeline, and met-nonlinear. Prod apply uses the D601 registry artifact consumer for reviewed services; code-queue-mgr and D601 direct infrastructure/incomplete contracts are dry-run or supervisor-gated." },
{ name: "--service <id>", description: "Limit reconcile to one service from the manifest." },
{ name: "--commit <full-sha>", description: "Prod artifact rollback/apply override for a selected service; the image must already exist in D601 registry." },
{ name: "--dry-run", description: "Prepare and validate without mutating the target service." },
@@ -767,7 +769,7 @@ function isDevK3sDeployService(service: UniDeskMicroserviceConfig): boolean {
}
function isDevArtifactConsumerService(service: UniDeskMicroserviceConfig): boolean {
return service.deployment.mode === "k3sctl-managed"
return (service.deployment.mode === "k3sctl-managed" || isDirectComposeDeployMode(service))
&& devArtifactConsumerServiceIds.has(service.id);
}
@@ -791,10 +793,12 @@ function selectServices(config: UniDeskConfig, manifest: DeployManifest, service
return selected.map((desired) => {
if (manifest.environment === "dev") {
const service = devK3sDeployService(desired.id);
if (service === undefined) {
throw new Error(`deploy --env dev service ${desired.id} is not enabled for direct rollout in the current CI-only phase`);
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 };
}
return { desired, config: service };
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`);
@@ -2885,7 +2889,7 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
}
const unsupported = unsupportedDevApplyServices(manifest, options.serviceId);
if (unsupported.length > 0) {
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk/decision-center/project-manager/oa-event-flow/code-queue-mgr/todo-note artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
throw new Error(`deploy apply --env dev currently supports backend-core target-side rollout plus frontend/baidu-netdisk/decision-center/project-manager/oa-event-flow/code-queue-mgr/todo-note/findjob/pipeline/met-nonlinear artifact consumers; unsupported selected services: ${unsupported.join(", ")}. Use ci run-dev-e2e for smoke verification.`);
}
const devArtifactServices = selectedDevArtifactServicesWithProdFallback(manifest, options.serviceId);
const devTargetServices = selectedDevTargetServices(manifest, options.serviceId);