test: add code queue cicd dry-run contract
This commit is contained in:
@@ -111,6 +111,10 @@
|
||||
|
||||
随后登录公网 frontend `http://74.48.78.17:18081/`,进入 `用户服务 / Code Queue`,确认页面显示默认模型 `gpt-5.5`、默认执行 Provider `D601`、默认工作目录 `/workspace`、模型下拉菜单包含 `gpt-5.4-mini`/`gpt-5.4`/`gpt-5.5`、入队份数、队列指标、任务 ID、复制任务 ID、引用按钮、任务耗时、引用任务 ID、清空输入、创建成功提示、任务提交表单、Trace 输出、attempt 表、MiniMax/fallback judge 状态、追加 prompt、打断和重试控件;通过页面提交一个小任务,确认任务进入 queued/running/succeeded 或可解释的 failed 状态,并且输出区能看到运行中的 Codex 消息。需要验证运行中纠偏时,提交一个明确会保持 running 的长任务,使用 `printf '<纠偏提示>' | bun scripts/cli.ts codex steer <runningTaskId> --prompt-stdin` 注入 prompt,确认命令返回有界 task/queue 摘要和 `codex task`/`codex output` 后续命令,且 `bun scripts/cli.ts codex task <runningTaskId> --trace --tail --limit 80` 能看到 `Steer prompt` 或 `turn/steer`;正式验收不得再使用底层 `microservice proxy code-queue /api/tasks/<taskId>/steer` 作为主入口。批量验收时设置 `入队份数=5` 或用 `---` 分隔 5 段 prompt,一次性入队 5 条任务,确认 5 条任务按顺序运行并全部进入 succeeded 或可解释的非成功终态,不能只运行第一条后停止;其中任一任务被 judge 判定 `fail` 时只能把当前任务标为 failed,后续 queued 任务仍必须继续推进。测试异常中断时可以提交长任务后点击 `打断`,确认任务变为 canceled 或被 judge 标记为非成功终态;自动重试只应在服务端/传输异常、任务正常结束但 execution record 显示未完成、或 judge 判定 retry 时发生;retry 必须复用已有 Codex thread 并 append 继续执行 prompt,只有当前任务 complete 后才推进队列中的下一个任务。MiniMax judge 必须能处理 Markdown fence/夹杂文本等 JSON 去噪;若去噪后仍失败,必须把解析错误和上一轮去噪前原始回答反馈给 MiniMax 修复后重试,日志中应出现 `judge_json_parse_retry`,且 repair 成功时仍以 `source=minimax` 返回。Codex provider key 只能通过 `OPENAI_API_KEY`、`CRS_OAI_KEY` 这类运行时环境透传,MiniMax API key 只能通过 D601 env-file 运行时环境传入,禁止写入 `config.json`、Dockerfile、源码或测试文档。
|
||||
|
||||
## T23D Code Queue CI/CD Dry-Run Contract
|
||||
|
||||
阅读 `AGENTS.md`(本项目 `AGENTS.md` 同时承担 `SKILL.md` 对 `scripts/cli.ts` 的解释职责),然后用 cli 手动测试以下内容:运行 `bun scripts/code-queue-cicd-dry-run-contract-test.ts`,确认 Code Queue 标准镜像化 CI/CD dry-run 合同输出 `ok=true`。该测试必须证明 CI 只负责发布 commit-pinned `unidesk/code-queue:<commit>` artifact 与 digest/label summary,DEV CD dry-run 只指向 `unidesk-dev` Code Queue scheduler/read/write/provider-egress-proxy,PROD Code Queue 是 structured unsupported 且不暴露 production k3s runtime deploy target,并且输出显式禁止 `codex deploy`、`server rebuild code-queue`、production namespace/manifest mutation、scheduler/runner restart、任务 interrupt 和 cancel。该测试不得执行真实 deploy/rebuild/restart,不得 interrupt/cancel 运行中任务,也不得执行 prod apply。
|
||||
|
||||
## T23A D601 k3s CI Gate
|
||||
|
||||
阅读 `AGENTS.md` 和 `docs/reference/ci.md`,运行 `bun scripts/cli.ts ci install`,确认 Tekton Pipelines `v1.12.0`、Tekton Triggers `v0.34.0` 和 `unidesk-ci` Pipeline/Task/EventListener 已部署到 D601 原生 k3s;随后运行 `bun scripts/cli.ts ci run --revision <已push的commitId> --wait-ms 1200000`,确认 PipelineRun 只执行 clone/check/performance,不调用 `deploy apply` 或 `codex deploy`,并确认临时 `code-queue-ci-read` 使用主 PostgreSQL 只读查询 Code Queue 首屏、TraceView summary、TraceView steps 和 step detail 的性能指标。若失败,使用 `bun scripts/cli.ts ci logs <pipelineRun>` 查看 TaskRun 和 Pod 日志;交付说明必须记录性能预算是否通过。
|
||||
|
||||
@@ -184,6 +184,27 @@ CI may build images, push to the D601 loopback registry and report immutable dig
|
||||
|
||||
backend-core and D601 `code-queue` remain restricted to dev image validation in this phase. Any future production rollout for them must be implemented as an explicit CD consumer change, not as a CI producer side effect.
|
||||
|
||||
### Code Queue Dry-Run Delivery Boundary
|
||||
|
||||
Code Queue follows the standard artifact split only up to a dev-only consumer:
|
||||
|
||||
| Stage | Owner | Allowed output | Explicitly forbidden |
|
||||
| --- | --- | --- | --- |
|
||||
| CI producer | Tekton / CI runner outside Code Queue self-deploy | Build from pushed Git, publish `127.0.0.1:5000/unidesk/code-queue:<commit>`, report service id, source repo, source commit, Dockerfile, tag, digest and digest ref | `deploy apply`, `kubectl apply`, scheduler/read/write rollout, task `interrupt` or `cancel` |
|
||||
| CD dry-run | deploy/artifact-registry CLI | Read `origin/master:deploy.json`, show `unidesk-dev` target objects, required registry image, no-runtime-build validation and forbidden actions | Mutating runtime objects, using dirty worktrees, using mutable tags, falling back to `codex deploy` or D601 maintenance-channel source deploy |
|
||||
| DEV live apply | Human operator after dry-run evidence | Pull/import the existing commit image, update only `unidesk-dev` Code Queue scheduler/read/write/provider-egress-proxy objects, verify health through the Kubernetes API service proxy | Touching production `unidesk` namespace, production PostgreSQL, production scheduler/runner, running task state or Code Queue Manager prod sidecar |
|
||||
| PROD | Not implemented | Structured unsupported / dry-run evidence only | Any production Code Queue artifact deploy, manifest mutation, rollout restart, scheduler/runner rebuild, task interrupt/cancel, or self-deploy by the running Code Queue task |
|
||||
|
||||
The operator review points are fixed. DEV requires an operator to compare the CI artifact summary with `deploy plan --env dev --service code-queue` or `deploy apply --env dev --service code-queue --dry-run`, then explicitly authorize a DEV apply outside the Code Queue task. PROD has no apply authorization point in this phase; even a manual request must first land a separate reviewed Code Queue production CD design. The current runner may produce plans, preflight output, docs and contract tests only.
|
||||
|
||||
Lightweight contract evidence for this boundary is:
|
||||
|
||||
```bash
|
||||
bun scripts/code-queue-cicd-dry-run-contract-test.ts
|
||||
```
|
||||
|
||||
The test checks that dev targets only `unidesk-dev`, prod exposes no runtime deploy target, production mutation is unsupported, and dry-run output forbids Code Queue self-deploy, scheduler/runner mutation, interrupt and cancel actions.
|
||||
|
||||
## Validation Boundary
|
||||
|
||||
This precheck uses lightweight parsing and dry-run evidence only. It intentionally does not run full `check`, e2e, Playwright, or other broad browser/runtime test suites on the master server because those are outside the precheck scope and may exceed master-server resource limits. `backend-core` and D601 `code-queue` production validation are also out of scope; backend-core dev rollout can be attempted only through the existing D601 dev path, and a provider-offline result is an infrastructure blocker rather than permission to validate production.
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
|
||||
Code Queue 后续正式生产部署必须走一条受控 CD 路径并单独审查;当前阶段只提供 dev artifact consumer。`deploy apply --env dev --service code-queue` 或 `artifact-registry deploy-service --env dev --service code-queue` 可以消费 D601 registry 中的 `unidesk/code-queue:<commit>`,只更新 `unidesk-dev` Code Queue execution slice,并通过 Kubernetes API service proxy 验证健康。`--env prod --service code-queue` 必须明确 unsupported,不能执行生产 artifact deploy、rollout 或 manifest 变更。persistent dev apply 的完整服务范围见 `docs/reference/dev-environment.md`,Code Queue temporary smoke 仍通过 `ci run-dev-e2e`,规则见 `docs/reference/dev-ci-runner.md`。
|
||||
|
||||
The reproducible dry-run delivery path is:
|
||||
|
||||
1. CI publishes `127.0.0.1:5000/unidesk/code-queue:<full-sha>` from pushed Git and reports the immutable digest/label summary.
|
||||
2. A runner or operator runs `bun scripts/cli.ts deploy plan --env dev --service code-queue` or `bun scripts/cli.ts deploy apply --env dev --service code-queue --commit <full-sha> --dry-run`; this is non-mutating evidence only.
|
||||
3. A human operator reviews the dry-run and, if accepted, separately authorizes DEV apply. That apply may touch only `unidesk-dev` Code Queue execution objects.
|
||||
4. PROD remains unsupported. A dry-run or plan may document the gap, but no Code Queue runner may perform prod apply, rollout restart, scheduler/runner rebuild, interrupt or cancel as part of delivering Code Queue itself.
|
||||
|
||||
This contract is checked by `bun scripts/code-queue-cicd-dry-run-contract-test.ts`. The test is intentionally lightweight and does not run full e2e, Playwright, Code Queue deployment, or task interruption flows.
|
||||
|
||||
## Command
|
||||
|
||||
```bash
|
||||
|
||||
@@ -114,6 +114,8 @@ Production `code-queue-mgr` is a separate main-server Compose sidecar artifact c
|
||||
|
||||
Environment plan output must be sufficient to review the artifact matrix without running a live apply. Each service item includes `deploymentPath`, `artifactConsumer.consumerKind`, `artifactConsumer.registryImage`, `artifactConsumer.noRuntimeSourceBuild`, `artifactConsumer.dryRunOnly`, `target`, `validation` and `liveApply` where relevant. `consumerKind=d601-direct-compose` means the reviewed consumer touches only the D601 Docker/Compose service and private health path; `consumerKind=d601-k3s-managed` means the reviewed consumer imports the artifact into native k3s/containerd and verifies through the Kubernetes API service proxy; `consumerKind=main-server-compose` means the reviewed consumer streams or loads the D601 artifact into the main-server Compose service; `consumerKind=d601-dev-target-side-build` is reserved for the controlled dev backend-core source-build exception. Artifact consumer plan items must explicitly report `noRuntimeSourceBuild=true` and list forbidden build/public exposure actions. Blocked or gated services must keep structured `dryRunOnly` / `blockedReason` output, for example `met-nonlinear` `runtime-verification-blocked` and `k3sctl-adapter` supervisor-only production apply.
|
||||
|
||||
For `--env dev --service code-queue`, the environment plan must also expose a `boundary` block that separates the CI producer from the dev CD consumer. CI is allowed to publish only `127.0.0.1:5000/unidesk/code-queue:<commit>` plus digest/label evidence. DEV CD may consume that artifact only for `unidesk-dev` Code Queue scheduler/read/write/provider-egress-proxy objects after an operator reviews the dry-run. For `--env prod --service code-queue`, the service item must remain `deploymentPath=unsupported`, `artifactConsumer.consumerKind=unsupported`, `target.deployCommandShape=none` and `liveApply.allowed=false`; it must not expose production k3s as an executable target. The prod boundary must state that production Code Queue CD needs a future supervisor-approved design and that this runner cannot self-deploy, mutate the production namespace, restart scheduler/runner, or interrupt/cancel tasks.
|
||||
|
||||
`bun scripts/cli.ts deploy apply [--file deploy.json | --env dev|prod] [--service <id>] [--commit <full-sha>] [--dry-run] [--force]` starts an asynchronous job only for supported targets. Use `bun scripts/cli.ts job status <jobId> --tail-bytes 30000` to observe progress. `--dry-run` resolves the same plan but does not build or replace runtime objects. `--force` rebuilds even when the live commit matches. Environment apply is not the dev e2e trigger; use `bun scripts/cli.ts ci run-dev-e2e` for the Git-controlled temporary namespace smoke flow. `--env dev` apply is enabled for persistent D601 `backend-core` target-side rollout and for `frontend`/`baidu-netdisk`/`decision-center`/`mdtodo`/`claudeqq`/dev-only `code-queue`/`project-manager`/`oa-event-flow`/`code-queue-mgr`/`todo-note`/`findjob`/`pipeline`/`met-nonlinear` artifact consumers. `--env prod` apply exposes the D601 registry artifact consumer for `backend-core`, `frontend`, `baidu-netdisk`, `decision-center`, `mdtodo`, `claudeqq`, `project-manager`, `oa-event-flow`, `todo-note`, `findjob`, `pipeline` and `met-nonlinear`; `code-queue-mgr` prod live apply is supervisor-gated and `k3sctl-adapter` is plan/dry-run only. `--commit` may override one selected reviewed artifact consumer in either dev or prod, for example `deploy apply --env dev --service frontend --commit <release-v1-full-sha>`, and the image must already exist as `127.0.0.1:5000/unidesk/<service-id>:<commit>`. Unsupported prod services, especially `code-queue`, return a structured `unsupported` payload instead of silently falling back to a maintenance-channel source build.
|
||||
|
||||
All deploy commands output JSON. Long operations must use `.state/jobs/` and bounded log tails; no deploy path may succeed with missing progress output.
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, label: string): JsonRecord {
|
||||
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, { value });
|
||||
return value as JsonRecord;
|
||||
}
|
||||
|
||||
function asStringArray(value: unknown, label: string): string[] {
|
||||
assertCondition(Array.isArray(value) && value.every((item) => typeof item === "string"), `${label} must be a string array`, value);
|
||||
return value as string[];
|
||||
}
|
||||
|
||||
function runCli(args: string[], expectStatus: number): JsonRecord {
|
||||
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
maxBuffer: 8 * 1024 * 1024,
|
||||
});
|
||||
assertCondition(result.status === expectStatus, `cli status mismatch for ${args.join(" ")}`, {
|
||||
status: result.status,
|
||||
stdout: result.stdout.slice(-3000),
|
||||
stderr: result.stderr.slice(-3000),
|
||||
});
|
||||
return asRecord(JSON.parse(result.stdout) as unknown, "cli envelope");
|
||||
}
|
||||
|
||||
function firstService(envelope: JsonRecord, label: string): JsonRecord {
|
||||
const data = asRecord(envelope.data, `${label} data`);
|
||||
const services = Array.isArray(data.services) ? data.services : [];
|
||||
assertCondition(services.length === 1, `${label} should return exactly one service`, data);
|
||||
return asRecord(services[0], `${label} service`);
|
||||
}
|
||||
|
||||
function includes(value: unknown, expected: string): boolean {
|
||||
return Array.isArray(value) && value.some((item) => item === expected);
|
||||
}
|
||||
|
||||
const commit = "0123456789abcdef0123456789abcdef01234567";
|
||||
|
||||
const devPlan = firstService(runCli(["deploy", "plan", "--env", "dev", "--service", "code-queue"], 0), "dev plan");
|
||||
const devArtifact = asRecord(devPlan.artifactConsumer, "dev artifactConsumer");
|
||||
const devTarget = asRecord(devPlan.target, "dev target");
|
||||
const devBoundary = asRecord(devPlan.boundary, "dev boundary");
|
||||
const devCd = asRecord(devBoundary.cdConsumer, "dev cdConsumer");
|
||||
|
||||
assertCondition(devArtifact.consumerKind === "d601-k3s-managed", "dev Code Queue must be a k3s-managed artifact consumer", devArtifact);
|
||||
assertCondition(devArtifact.noRuntimeSourceBuild === true, "dev Code Queue must not build source on the runtime target", devArtifact);
|
||||
assertCondition(devTarget.namespace === "unidesk-dev", "dev Code Queue target namespace must be unidesk-dev", devTarget);
|
||||
assertCondition(devTarget.deployment === "code-queue-scheduler-dev", "dev Code Queue should target the dev scheduler deployment", devTarget);
|
||||
assertCondition(devCd.prodMutationAllowed === false, "dev Code Queue boundary must prohibit production mutation", devCd);
|
||||
assertCondition(String(devCd.manualAuthorizationPoint ?? "").includes("DEV apply"), "dev Code Queue boundary should expose the DEV manual authorization point", devCd);
|
||||
assertCondition(asRecord(devBoundary.ciProducer, "dev ciProducer").allowed === true, "Code Queue CI producer should be allowed for image publication", devBoundary);
|
||||
assertCondition(includes(devTarget.forbiddenActions, "docker build"), "dev target must forbid runtime docker build", devTarget);
|
||||
assertCondition(includes(devTarget.forbiddenActions, "NodePort"), "dev target must forbid NodePort", devTarget);
|
||||
|
||||
const prodPlan = firstService(runCli(["deploy", "plan", "--env", "prod", "--service", "code-queue"], 1), "prod plan");
|
||||
const prodArtifact = asRecord(prodPlan.artifactConsumer, "prod artifactConsumer");
|
||||
const prodTarget = asRecord(prodPlan.target, "prod target");
|
||||
const prodBoundary = asRecord(prodPlan.boundary, "prod boundary");
|
||||
const prodCd = asRecord(prodBoundary.cdConsumer, "prod cdConsumer");
|
||||
const prodLiveApply = asRecord(prodPlan.liveApply, "prod liveApply");
|
||||
const prodUnsupported = asRecord(prodPlan.unsupported, "prod unsupported");
|
||||
const prodForbidden = asStringArray(prodTarget.forbiddenActions, "prod forbiddenActions");
|
||||
|
||||
assertCondition(prodPlan.deploymentPath === "unsupported", "prod Code Queue deployment path must be unsupported", prodPlan);
|
||||
assertCondition(prodArtifact.consumerKind === "unsupported", "prod Code Queue artifact consumer must be unsupported", prodArtifact);
|
||||
assertCondition(prodArtifact.registryImage === null, "prod Code Queue plan must not advertise a production registry image target", prodArtifact);
|
||||
assertCondition(prodArtifact.noRuntimeSourceBuild === true, "prod Code Queue plan must still block runtime source builds", prodArtifact);
|
||||
assertCondition(prodTarget.runtimeHost === null, "prod Code Queue plan must not expose a runtime host target", prodTarget);
|
||||
assertCondition(prodTarget.deployCommandShape === "none", "prod Code Queue plan must not expose a deploy command shape", prodTarget);
|
||||
assertCondition(prodLiveApply.allowed === false, "prod Code Queue live apply must be blocked", prodLiveApply);
|
||||
assertCondition(String(prodLiveApply.reason ?? "").includes("production CD is intentionally unsupported"), "prod blocked reason should name the intentional CD gap", prodLiveApply);
|
||||
assertCondition(String(prodUnsupported.reason ?? "").includes("prod artifact deploy"), "prod unsupported reason should mention prod artifact deploy", prodUnsupported);
|
||||
assertCondition(prodCd.prodMutationAllowed === false, "prod Code Queue boundary must prohibit production mutation", prodCd);
|
||||
assertCondition(prodCd.liveApplyCommandShape === null, "prod Code Queue boundary must not advertise a live apply command", prodCd);
|
||||
assertCondition(String(prodCd.manualAuthorizationPoint ?? "").includes("future supervisor-approved"), "prod boundary should require a future supervisor-approved design", prodCd);
|
||||
assertCondition(prodForbidden.includes("production namespace mutation"), "prod forbidden actions must include production namespace mutation", prodTarget);
|
||||
assertCondition(prodForbidden.includes("interrupt running Code Queue tasks"), "prod forbidden actions must include task interrupt", prodTarget);
|
||||
assertCondition(prodForbidden.includes("cancel running Code Queue tasks"), "prod forbidden actions must include task cancel", prodTarget);
|
||||
assertCondition(JSON.stringify(prodTarget).includes("code-queue-read"), "prod excluded targets should name production Code Queue deployments", prodTarget);
|
||||
|
||||
const devArtifactDryRun = asRecord(runCli([
|
||||
"artifact-registry",
|
||||
"deploy-service",
|
||||
"--env",
|
||||
"dev",
|
||||
"--service",
|
||||
"code-queue",
|
||||
"--commit",
|
||||
commit,
|
||||
"--dry-run",
|
||||
], 0).data, "dev artifact-registry dry-run");
|
||||
const artifactTarget = asRecord(devArtifactDryRun.target, "artifact dry-run target");
|
||||
assertCondition(devArtifactDryRun.mutation === false, "artifact-registry dev dry-run must be non-mutating", devArtifactDryRun);
|
||||
assertCondition(artifactTarget.namespace === "unidesk-dev", "artifact-registry dev dry-run must target unidesk-dev", artifactTarget);
|
||||
|
||||
const prodArtifactDryRun = asRecord(runCli([
|
||||
"artifact-registry",
|
||||
"deploy-service",
|
||||
"--env",
|
||||
"prod",
|
||||
"--service",
|
||||
"code-queue",
|
||||
"--commit",
|
||||
commit,
|
||||
"--dry-run",
|
||||
], 1).data, "prod artifact-registry dry-run");
|
||||
assertCondition(prodArtifactDryRun.error === "unsupported-environment", "artifact-registry prod code-queue should be unsupported", prodArtifactDryRun);
|
||||
assertCondition(Array.isArray(prodArtifactDryRun.supportedEnvironments) && prodArtifactDryRun.supportedEnvironments.length === 1 && prodArtifactDryRun.supportedEnvironments[0] === "dev", "artifact-registry prod code-queue should expose only dev as supported", prodArtifactDryRun);
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: true,
|
||||
checks: [
|
||||
"deploy plan exposes Code Queue CI producer and dev CD consumer boundary",
|
||||
"dev Code Queue dry-run targets only unidesk-dev and forbids runtime builds/public ports",
|
||||
"prod Code Queue plan is unsupported and exposes no runtime deploy target",
|
||||
"prod Code Queue boundary forbids self-deploy, prod mutation, interrupt and cancel actions",
|
||||
"artifact-registry dev dry-run is non-mutating while prod remains unsupported",
|
||||
],
|
||||
}, null, 2)}\n`);
|
||||
+124
-11
@@ -1002,6 +1002,105 @@ function artifactConsumerPlanValidation(service: UniDeskMicroserviceConfig, envi
|
||||
return ["unsupported service remains blocked before source materialization or runtime mutation"];
|
||||
}
|
||||
|
||||
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.";
|
||||
}
|
||||
|
||||
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.",
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
function codeQueueCiCdBoundary(serviceId: string, environment: DeployEnvironment): Record<string, unknown> | undefined {
|
||||
if (serviceId !== "code-queue") return undefined;
|
||||
return {
|
||||
serviceId,
|
||||
selfDeployBlocked: true,
|
||||
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: "bun scripts/cli.ts deploy apply --env dev --service code-queue --commit <full-sha> --run-now",
|
||||
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,
|
||||
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",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function environmentPlanServiceConfig(config: UniDeskConfig | null, service: DeployManifestService, environment: DeployEnvironment): UniDeskMicroserviceConfig | null {
|
||||
if (environment === "dev") {
|
||||
const devService = devK3sDeployService(service.id);
|
||||
@@ -2845,7 +2944,12 @@ function environmentDryRunPlan(
|
||||
const unsupported = (environment === "prod" && !prodArtifactConsumerServiceIds.has(service.id))
|
||||
|| (environment === "dev" && !devApplySupportedServiceIds.has(service.id) && !devArtifactConsumerServiceIds.has(service.id));
|
||||
const dryRunBlockedReason = artifactConsumerDryRunBlockedServiceIds.get(service.id) ?? null;
|
||||
const planKind = serviceConfig === null ? "unsupported" : artifactConsumerPlanKind(serviceConfig, environment);
|
||||
const planTarget = serviceConfig === null ? null : artifactConsumerPlanTarget(serviceConfig, environment);
|
||||
const unsupportedReason = unsupported ? unsupportedEnvironmentPlanReason(service.id, environment) : null;
|
||||
const effectiveTarget = unsupported
|
||||
? unsupportedPlanTarget(service.id, environment, unsupportedReason ?? "unsupported")
|
||||
: planTarget;
|
||||
return {
|
||||
id: service.id,
|
||||
repo: service.repo,
|
||||
@@ -2863,24 +2967,33 @@ function environmentDryRunPlan(
|
||||
dryRunOnly: true,
|
||||
blockedReason: "service is missing from config.json and no synthetic deploy service is available for this environment",
|
||||
} : {
|
||||
consumerKind: artifactConsumerPlanKind(serviceConfig, environment),
|
||||
registryImage: `127.0.0.1:5000/unidesk/${service.id}:${service.commitId}`,
|
||||
noRuntimeSourceBuild: artifactConsumerPlanKind(serviceConfig, environment) !== "d601-dev-target-side-build",
|
||||
dryRunOnly: (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)) || dryRunBlockedReason !== null,
|
||||
blockedReason: dryRunBlockedReason ?? (environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null),
|
||||
consumerKind: unsupported ? "unsupported" : planKind,
|
||||
registryImage: unsupported ? null : `127.0.0.1:5000/unidesk/${service.id}:${service.commitId}`,
|
||||
noRuntimeSourceBuild: unsupported || planKind !== "d601-dev-target-side-build",
|
||||
dryRunOnly: unsupported || (environment === "prod" && prodArtifactLiveApplyBlockedServiceIds.has(service.id)) || dryRunBlockedReason !== null,
|
||||
blockedReason: unsupportedReason ?? dryRunBlockedReason ?? (environment === "prod" ? prodArtifactLiveApplyBlockedServiceIds.get(service.id) ?? null : null),
|
||||
},
|
||||
target: planTarget,
|
||||
validation: serviceConfig === null ? undefined : artifactConsumerPlanValidation(serviceConfig, environment),
|
||||
target: effectiveTarget,
|
||||
validation: unsupported
|
||||
? ["unsupported service remains blocked before source materialization, registry pull, manifest update, rollout, or runtime mutation"]
|
||||
: serviceConfig === null
|
||||
? undefined
|
||||
: artifactConsumerPlanValidation(serviceConfig, environment),
|
||||
boundary: codeQueueCiCdBoundary(service.id, environment),
|
||||
unsupported: unsupported
|
||||
? {
|
||||
ok: false,
|
||||
supported: false,
|
||||
reason: 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.",
|
||||
reason: unsupportedReason,
|
||||
}
|
||||
: undefined,
|
||||
liveApply: dryRunBlockedReason !== null
|
||||
liveApply: unsupported
|
||||
? {
|
||||
allowed: false,
|
||||
reason: unsupportedReason,
|
||||
dryRunOnly: true,
|
||||
}
|
||||
: dryRunBlockedReason !== null
|
||||
? {
|
||||
allowed: false,
|
||||
reason: dryRunBlockedReason,
|
||||
|
||||
Reference in New Issue
Block a user