# CI/CD Standardization

本文中的 `mdtodo` 是遗留交付对象名；任务管理与迁移统一遵循 `$unidesk-tasktree`。

This document defines the stable split between CI artifact producers, artifact catalog data, legacy guardrails and CD consumers. Detailed Tekton rules remain in `docs/reference/ci.md`; registry runtime and consumers remain in `docs/reference/artifact-registry.md`; user-service delivery order remains in `docs/reference/user-service-delivery.md`.

`deploy.json` is the single release-intent source for deployment-time parameters that must stay aligned across CI/CD, dev and prod consumers, and runtime health checks. Other files and manifests may mirror those values for wiring or compatibility, but they must be derived from `deploy.json` and not become a second source of truth. The long-term standardization work is tracked in [GitHub issue #60](https://github.com/pikasTech/unidesk/issues/60).

## D601 Control-Plane Guard

D601 CI/CD must target native k3s only. Docker Desktop Kubernetes has been disabled and must not be reintroduced; the incident and governance plan are tracked in [GitHub issue #138](https://github.com/pikasTech/unidesk/issues/138), with recovery context in [GitHub issue #118](https://github.com/pikasTech/unidesk/issues/118). CI producer, Tekton, deploy, artifact-registry and manual recovery scripts must not rely on default kubeconfig. They must export `KUBECONFIG=/etc/rancher/k3s/k3s.yaml`, verify node `d601`, and fail fast if the actual target context/server/nodes indicate `docker-desktop`, `desktop-control-plane`, or `127.0.0.1:11700`. A stale default kubeconfig may be reported as a diagnostic, but it is not a blocker when the explicit D601 kubeconfig passes.

## Actual Runner Debugging

CI/CD debugging must verify the path from the environment that actually owns the failing step. The master server is a control plane and observability entrypoint; it is not automatically the same network, filesystem, credential or kubeconfig perspective as a Code Queue runner, Tekton task, D601 host process, registry helper or k3s workload. When a rollout, artifact lookup, credential mount, worktree, or registry check behaves differently from the commander's view, the next diagnostic step is to enter the real runner container or pod and reproduce the official CLI path there.

The standard method is:

1. Identify the active execution owner from scheduler/task metadata, not from a convenient local clone.
2. Enter that exact container or pod through the documented maintenance path, then run the repo-owned CLI from its worktree.
3. Prove the control plane explicitly with `KUBECONFIG=/etc/rancher/k3s/k3s.yaml`, target namespace, current context, node `d601`, and service endpoints before mutation.
4. Run at least one artifact producer action and one CD consumer action when validating a full CI/CD path; checking only build, only registry, or only live health is not enough.
5. Record bounded job status, duration, blocker fields and final live identity. Full logs belong in job artifacts or issue comments, not in long-lived reference docs.

Every CLI used in this lane must preserve progressive disclosure: `submit` returns a job id quickly, while `status`, `logs` and `report` read bounded state. Long Docker builds, registry pushes, `kubectl wait`, rollout status and e2e smoke tests must not occupy the commander's foreground context as a blocking command.

Network identity is part of the evidence. `127.0.0.1:5000` may mean runner-pod loopback to a Node HTTP client, D601 host loopback to Docker, or node-local loopback to k3s/containerd. A fix must preserve the release artifact identity while using the correct verification route for that runtime view; it must not rewrite `deploy.json`, image references or manifests merely to satisfy one observer's loopback.

Manual hotfixes are allowed only to recover and learn the missing contract. The durable close-out is a repo-owned preflight, desired-state check, CLI behavior, or reference update, followed by the same verification from the actual runner environment. A healthy old live service does not prove CD success; success requires the formal job to pass and live commit or digest identity to match the desired state.

- 对于 node-scoped HWLAB/Gitea/PaC lane：
  - CI/CD 收口只覆盖 PipelineRun 成功、GitOps/Argo 同步与健康、runtime readiness/provenance、git-mirror 对齐，以及 YAML 声明的正式公网 HTTPS `/health` 探针；
  - 浏览器自动化、`web-probe`、Playwright、DOM 断言和用户路径 E2E 属于部署后的产品验收，不是 CI/CD 门禁；
  - 公网暴露故障必须按 owning YAML 选中的 managed edge 诊断；
  - Gitea 通过 NC01 public-edge 直连 ClusterIP；
  - 仍由 YAML 选择 FRP 的服务继续检查各自声明的 FRP/Caddy 路径；
  - Secret 值不得写入文档或 issue 评论。

## Target Shape

The standard release shape is:

1. CI builds or locks the artifact from a pushed commit or verified upstream digest.
2. The artifact is published to the D601 artifact registry or a controlled mirror.
3. Dev validation consumes that same artifact.
4. Production CD is pull-only and verifies live commit or upstream digest.
5. Runtime metadata can be traced back to a live Git commit or upstream image digest.

`backend-core` and D601 `code-queue` may be validated only in dev in this phase. This document must not be used to introduce production deploy validation for either service.

## Phase-One Deploy.json Consolidation Contract

Issue #60 moves CI/CD image deployment toward one release-intent source. The implemented `deploy.json` schema still carries only `id`, `repo` and `commitId`; this section defines the phase-one migration contract before changing runtime behavior.

Current duplicated configuration surfaces are:

- Service source and version: `deploy.json.environments.*.services[]`, `config.json.microservices[].repository`, `CI.json.artifacts[].source`, runtime `UNIDESK_DEPLOY_*` metadata and service health payloads.
- Image repository, tag and digest: `CI.json.artifacts[].image`, artifact-registry consumer specs, Compose/Kubernetes image fields, registry digest probes and docs tables.
- Runtime target metadata: `config.json` provider/deployment entries, Compose service/container names, k3s namespaces, Kubernetes Deployment/Service names, artifact-registry target specs and runtime manifests.
- Deployment parameters: runtime env prefixes, required secret keys, health deploy metadata requirements, resource requests/limits, rollout strategy and dev/prod support gates.
- Consumer scope: `deploy.json` dev/prod service lists, deploy/artifact-registry allowlists, dev/prod Kubernetes manifests and service-specific documentation.

Phase one extends the desired-state model in this order:

1. Keep existing `deploy.json` `id`, `repo` and `commitId` as the deployed source/version authority. All environment-ref deploy commands continue reading `origin/master:deploy.json`.
2. Add optional deploy-owned artifact identity fields after the static drift guard is green: `artifact.kind`, `artifact.repository`, `artifact.tag`, optional `artifact.digestRef`, and `artifact.upstreamDigestRef` for upstream-image services. For source-build services, `artifact.tag` must equal the selected commit unless a future schema explicitly records a digest-only release.
3. Add consumer intent fields: `consumer.kind`, `consumer.dev.enabled`, `consumer.prod.enabled`, `consumer.supportLevel`, `consumer.targetRef` and `consumer.noRuntimeSourceBuild`. These describe whether the service is `main-server-compose`, `d601-k3s-managed`, `d601-direct-compose`, `upstream-digest`, `dry-run-only` or `unsupported`; low-level object names still come from config/manifests until renderers exist.
4. Add deployment-time metadata that must be common across CI, dev CD and prod CD: deploy env prefix, health deploy-metadata requirement, required runtime secret key names, rollback policy, service port, health path, and resource profile identifiers. The planned first keys are `runtime.containerPort`, `runtime.healthPath`, `runtime.memory.request`, `runtime.memory.limit`, `health.deployMetadataRequired` and `runtime.requiredSecretKeys`. Secret values, credentials, volumes and host paths do not move into `deploy.json`.
5. Teach CI producer dry-run, deploy plan and artifact-registry dry-run to render from `deploy.json` first and compare mirrored `CI.json`/`config.json`/manifest values as derived copies. A mismatch is drift, not an alternate source of truth.

Phase two starts with low-risk D601 k3s user-service artifact consumers plus the dev-only Code Queue artifact consumer: `dev/mdtodo` and `dev/code-queue`. Decision Center has since moved out of this D601 artifact-consumer lane; its current production truth is NC01 YAML-first k8s plus GitHub repo storage. The remaining phase-two `deploy.json` entries own the source-build artifact repository/tag policy, D601 k3s consumer target, stable image, manifest reference, service port, health path, memory request/limit and health deploy-metadata requirement. `deploy plan --env dev --service <mdtodo|code-queue>` and dry-run artifact consumer plans must read those fields from `deploy.json`; the k8s manifest and artifact-registry executor constants are derived mirrors checked by a structured `deploy-json-drift` preflight.

Fields that stay outside `deploy.json` during phase one:

- `CI.json`: producer pipeline name, source root, Dockerfile path and success summary shape. Once artifact identity is in `deploy.json`, `CI.json.artifacts[].image.repository` becomes a compatibility mirror checked for drift.
- `config.json`: provider id, proxy route policy, Compose file/service/container names for services not yet migrated, development SSH/worktree details, and secret source locations. For `dev/mdtodo` and `dev/code-queue`, port/health target values in dry-run are deploy-owned and config is no longer an alternate source. Decision Center config moved to the NC01 YAML-first k8s target and GitHub storage declaration.
- Compose and Kubernetes manifests: volumes, PVCs, security context and rollout strategy remain concrete manifests. For `dev/mdtodo` and `dev/code-queue`, container port, memory request/limit and deploy metadata env presence are deploy-owned values and manifest copies are drift-checked mirrors until a renderer owns the file.
- Artifact-registry executor code: low-level pull/import/retag/recreate commands, registry probes and platform-specific verification scripts. For `dev/mdtodo` and `dev/code-queue`, executor dry-run consumes the deploy-owned artifact/consumer/runtime contract; any hardcoded mismatch returns `deploy-json-drift`.

The drift contract is:

- `deploy.json` wins for release intent. `config.json.microservices[].repository.commitId` is compatibility data only and must not drive env-ref deploys.
- `deploy.json.repo` and `CI.json.artifacts[].source.repo` must match for every source-build service that appears in `deploy.json`.
- Image tags used by CI/CD must be commit-pinned or digest-pinned. Mutable tags such as `latest` are never valid release evidence.
- Runtime topology may be mirrored in dry-run output, but deployment commands must treat it as derived target metadata and keep `noRuntimeSourceBuild=true` for reviewed artifact consumers.
- Upstream-image services must stay `blocked` in CI source-build publishing until an upstream digest or mirror digest consumer exists.

Lightweight evidence for this boundary comes from config review, `deploy plan`, `deploy apply --dry-run`, and `ci publish-user-service --dry-run` output. These checks must stay non-mutating: no image publish, service deploy, restart, Playwright or full e2e. Unless the user explicitly asks for it, do not add or run unit tests or contract tests for this CLI boundary.

## Artifact Catalog

Root `CI.json` is the CI producer catalog. It is not a deployment manifest.

Allowed catalog data:

- stable `serviceId`;
- artifact `kind`: `source-build` or `upstream-image`;
- producer command: `ci publish-backend-core` or `ci publish-user-service`;
- source repository URL, optional repo root and repo-relative Dockerfile path;
- image repository naming and commit tag policy;
- upstream image digest, upstream source revision and D601 mirror intent for image-only services;
- the required success summary contract.

Forbidden catalog data:

- provider IDs;
- runtime namespace, Compose service, Kubernetes Service or health path;
- ports, environment variables, replicas or volumes;
- desired deployment commits or rollout targets.

Runtime topology belongs to `config.json`, `deploy.json`, existing Kubernetes manifests and the artifact-registry executor.

## Producer Contract

`bun scripts/cli.ts ci publish-user-service --service <id> --commit <full-sha>` reads `source.repo`, `source.dockerfile` and image repository naming from `CI.json`. It rejects command-line repo overrides. Successful source-build producers must label the image with:

- `unidesk.ai/service-id`;
- `unidesk.ai/source-commit`;
- `unidesk.ai/source-repo`;
- `unidesk.ai/dockerfile`.

The successful `artifactSummary` must contain `serviceId`, `sourceCommit`, `sourceRepo`, `dockerfile`, `imageRef`, `tag`, `digest` and `digestRef`.

`blocked` catalog entries must return a structured blocked result. They must not silently skip, build from a dirty worktree, fall back to target-side maintenance deployment, or mutate production.

## Current Coverage

Supported source-build artifact producers:

- `backend-core` through `ci publish-backend-core`;
- `frontend`, `baidu-netdisk`, `decision-center`, `project-manager`, `oa-event-flow`, `code-queue-mgr`, `findjob`, `pipeline`, `met-nonlinear`, `k3sctl-adapter`, `mdtodo`, `claudeqq` through `ci publish-user-service`;
- `todo-note` through `platform-infra pipelines-as-code --target NC01 --consumer unidesk-host`; the generic D601 publisher rejects it before remote execution;
- `code-queue` through `ci publish-user-service` for dev image validation only.

Cataloged but blocked:

- `filebrowser` and `filebrowser-d601`: upstream image-only services pinned to `docker.io/filebrowser/filebrowser@sha256:289c5dd677c56662440f26eeb44266ed9746fe563d2e9100f546bff558534d70`; they need a future upstream mirror producer before CI can publish them.

`code-queue-mgr` is a supported CI producer because the source-build input is known and the remote consumer commit already added a reviewed artifact consumer shape. Its production live apply remains supervisor-gated by deploy/artifact-registry and is not authorized by `CI.json`.

## Main-Server Compose User Services

Main-server Compose user services are normal source-build artifacts even though their runtime lives on the master server. CI builds them on D601, publishes a commit-pinned image to the D601 registry, and CD streams that artifact back to the master-server Compose project. The runtime target must not build source, use a dirty worktree, expose the service port publicly, or use `server rebuild <service>` as release truth.

| Service | Producer | Artifact | Consumer | Dev validation | Prod validation | Blocker |
| --- | --- | --- | --- | --- | --- | --- |
| `baidu-netdisk` | `ci publish-user-service --service baidu-netdisk` from `src/components/microservices/baidu-netdisk/Dockerfile` | `127.0.0.1:5000/unidesk/baidu-netdisk:<commit>` | master-server Compose service `baidu-netdisk`, container `baidu-netdisk-backend` | `deploy plan/apply --env dev --service baidu-netdisk` consumes the registry artifact and must expose the redacted `runtimeSecrets` source contract; dry-run is acceptable while the artifact, registry or secret source condition is absent | `deploy plan/apply --env prod --service baidu-netdisk` recreates only `baidu-netdisk` with `--no-build --no-deps --force-recreate`, then verifies image labels, `/health.deploy.commit`, requested commit and auth health; current prod artifact/health evidence is aligned | D601 registry artifact must exist; dev live apply remains blocked until canonical Compose env source has non-empty Baidu client id, client secret and token key plus logged-in auth health |
| `project-manager` | `ci publish-user-service --service project-manager` from `src/components/microservices/project-manager/Dockerfile` | `127.0.0.1:5000/unidesk/project-manager:<commit>` | master-server Compose service `project-manager`, container `project-manager-backend` | `deploy plan/apply --env dev --service project-manager` consumes the registry artifact and verifies labels plus health; dry-run is acceptable while the artifact or registry is absent | `deploy plan/apply --env prod --service project-manager` recreates only `project-manager` with `--no-build --no-deps --force-recreate`, then verifies image labels, `/health.deploy.commit` and requested commit | D601 registry artifact must exist before live dev or prod apply |

Focused smoke for this class is intentionally narrow: health, running image labels/digest, live `deploy.commit` / `deploy.requestedCommit`, and one private proxy API check such as `baidu-netdisk /api/transfers?limit=20` or `project-manager /api/projects`. Full e2e, Playwright, broad `check`, public-port probing and unrelated service restarts are outside this lane.

## Upstream Image Consumers

`filebrowser` and `filebrowser-d601` are upstream-image consumers, not source-built UniDesk services.

| Service | Upstream image | Source revision | Catalog home | CI Dockerfile build | Digest / mirror strategy | CD validation |
| --- | --- | --- | --- | --- | --- | --- |
| `filebrowser` | `docker.io/filebrowser/filebrowser:v2.63.3` | `ca5e249e3c0c94159c2136a0cd431a424eb18472` | `CI.json.artifacts[]` with `kind=upstream-image` plus `config.json.microservices[].repository.artifactSource` | forbidden | resolve tag to `docker.io/filebrowser/filebrowser@sha256:<manifest-digest>`, then optionally mirror to `127.0.0.1:5000/upstream/filebrowser/filebrowser@sha256:<manifest-digest>` | pull by digest or mirror digest, verify OCI labels, container image id/digest, and private proxy health |
| `filebrowser-d601` | `docker.io/filebrowser/filebrowser:v2.63.3` | `ca5e249e3c0c94159c2136a0cd431a424eb18472` | `CI.json.artifacts[]` with `kind=upstream-image` plus `config.json.microservices[].repository.artifactSource` | forbidden | same as `filebrowser` | same as `filebrowser` |

The catalog records the resolved upstream digest for the current image. If a future tag refresh cannot resolve the registry manifest digest, rollout must remain blocked until a reachable registry path resolves the manifest digest and records the mirror digest. A local Docker image id is supporting evidence only and not a registry digest pin.

## D601 Direct Services

D601 direct / host-managed services keep runtime ownership outside native k3s. Their standard path is still the artifact split: CI builds a commit-pinned image on D601, CD on D601 only checks the registry manifest, pulls or retags the artifact, recreates the existing Compose service with `--no-build --no-deps --force-recreate`, and verifies image labels plus service health. The provider-gateway/SSH path is a controlled maintenance bridge for those D601-local actions, not a target-side build standard and not a new public ingress.

| Service | Producer | Consumer | Dev validation | Prod validation | Blocker |
| --- | --- | --- | --- | --- | --- |
| `findjob` | `ci publish-user-service --service findjob` builds `Dockerfile` from `https://gitee.com/Lyon1998/findjob` into `unidesk/findjob:<commit>` | D601 direct Compose artifact consumer, service `server`, container `findjob-server` | `deploy apply --env dev --service findjob --dry-run` plans pull-only CD and live apply is allowed when the artifact exists | `deploy apply --env prod --service findjob --dry-run` plans the same path; prod live apply is allowed by policy | health does not report deploy commit, so strict commit proof relies on image and container labels plus `/api/health` |
| `pipeline` | `ci publish-user-service --service pipeline` builds `Dockerfile` from `https://github.com/pikasTech/pipeline` into `unidesk/pipeline:<commit>` | D601 direct Compose artifact consumer, service `pipeline-control`, container `pipeline-v2-control` | `deploy apply --env dev --service pipeline --dry-run` plans pull-only CD and live apply is allowed when the artifact exists | `deploy apply --env prod --service pipeline --dry-run` plans the same path; prod live apply is allowed by policy | health does not report deploy commit, so strict commit proof relies on image and container labels plus `/health` |
| `met-nonlinear` | `ci publish-user-service --service met-nonlinear` builds `docker/unidesk/Dockerfile.ml` from `https://github.com/pikasTech/met_nonlinear` into `unidesk/met-nonlinear:<commit>` | D601 direct Compose dry-run contract for service `met-nonlinear-ts` | `deploy apply --env dev --service met-nonlinear --dry-run` returns `runtime-verification-blocked` until the runtime image contract is fixed | `deploy apply --env prod --service met-nonlinear --dry-run` returns `runtime-verification-blocked`; live prod apply is unsupported | catalog Dockerfile is the ML image while the long-running service is `met-nonlinear-ts`; publish a labeled artifact for the running TS service or separate the server Dockerfile contract |
| `k3sctl-adapter` | `ci publish-user-service --service k3sctl-adapter` builds `src/components/microservices/k3sctl-adapter/Dockerfile` from UniDesk into `unidesk/k3sctl-adapter:<commit>` | D601 direct Compose plan/dry-run for service `k3sctl-adapter`, container `k3sctl-adapter` | no normal dev target; it is the control bridge for dev/prod k3s visibility | `deploy apply --env prod --service k3sctl-adapter --dry-run` exposes the pull-only contract; live prod apply is supervisor-only | must remain outside the k3s failure domain and be recoverable before any live replacement; worker automation must not replace it without explicit supervisor confirmation |
| `filebrowser-d601` | no UniDesk source-build producer; `CI.json` marks it `upstream-image/blocked` | future pull-only upstream digest or D601 mirror digest consumer only | not implemented | not implemented | must not be represented as a Dockerfile build; first implement upstream digest resolution and mirror governance for `docker.io/filebrowser/filebrowser:v2.63.3` |

`findjob`, `pipeline` and `met-nonlinear` deliberately do not create NodePort, hostPort or new public business ports. Runtime traffic stays behind backend-core, provider-gateway and the configured private service proxy. `k3sctl-adapter` is a control bridge, not an ordinary business service; it must be versioned, dry-run verifiable and manually recoverable before live replacement.

### D601 Lane C Closure Matrix

This matrix is the single review surface for the remaining D601 service lane. It separates execution-plane ownership, control-plane ownership, CI artifact production, CD consumption and acceptance scope. All commands listed here are non-mutating unless they explicitly say live apply; Code Queue live apply is limited to dev and must not be performed by a running Code Queue task.

| Service | Deployment / ownership | CI current state | CD current state | DEV acceptance | PROD acceptance | Blockers | Next task |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `findjob` | D601 `unidesk-direct` execution service in Docker Compose; control path is backend-core -> provider-gateway private HTTP proxy -> D601 loopback `/api/health`. | `CI.json` source-build supported through `ci publish-user-service --service findjob`; artifact is `127.0.0.1:5000/unidesk/findjob:<commit>` from the external Gitee repo `Dockerfile`. | Reviewed D601 direct Compose artifact consumer for service `server` / container `findjob-server`; CD is pull-only, retag, `docker compose up -d --no-build --no-deps --force-recreate server`, then label and health verification. | Allowed after `deploy apply --env dev --service findjob --dry-run` plus a matching registry artifact; live dev apply is permitted by policy. | Allowed after `deploy apply --env prod --service findjob --dry-run` plus artifact/operator review; no public port or target-side build. | `/api/health` does not report deploy commit, so strict proof depends on image/container labels plus health. Registry health and artifact existence are required before live apply. | Add or upstream a deploy metadata field in health; keep label-based proof as the minimum contract until that lands. |
| `pipeline` | D601 `unidesk-direct` execution service in Docker Compose; control path is backend-core -> provider-gateway private HTTP proxy -> D601 loopback `/health` and `/api/` / `/oa/`. | `CI.json` source-build supported through `ci publish-user-service --service pipeline`; artifact is `127.0.0.1:5000/unidesk/pipeline:<commit>` from the external GitHub repo `Dockerfile`. | Reviewed D601 direct Compose artifact consumer for service `pipeline-control` / container `pipeline-v2-control`; CD is pull-only, retag, `docker compose up -d --no-build --no-deps --force-recreate pipeline-control`, then label and health verification. | Allowed after `deploy apply --env dev --service pipeline --dry-run` plus a matching registry artifact; live dev apply is permitted by policy. | Allowed after `deploy apply --env prod --service pipeline --dry-run` plus artifact/operator review; no public port or target-side build. | `/health` does not report deploy commit, so strict proof depends on image/container labels plus health. Registry health and artifact existence are required before live apply. | Add health deploy metadata and keep OA Event Flow integration checks as a focused post-apply smoke. |
| `met-nonlinear` | D601 `unidesk-direct` GPU/business execution service in Docker Compose; control path is backend-core -> provider-gateway private HTTP proxy -> D601 loopback `/health` and `/api/`. | `CI.json` source-build supported through `ci publish-user-service --service met-nonlinear`; cataloged artifact uses `docker/unidesk/Dockerfile.ml` from `https://github.com/pikasTech/met_nonlinear`. | D601 direct Compose consumer is plan/dry-run only for service/container `met-nonlinear-ts`; dry-run exposes the no-build pull-only shape but returns `runtime-verification-blocked`. | Dry-run/read-only only. `deploy apply --env dev --service met-nonlinear --dry-run` must remain blocked until the running service image contract matches the published artifact. | Not authorized. Prod dry-run must remain `runtime-verification-blocked`; live prod apply is unsupported. | Published artifact is the ML image contract while the long-running service is `met-nonlinear-ts`, so CD cannot prove the running container image label equals the requested commit. | Split the TS server artifact from the ML image or publish a labeled artifact that exactly matches `met-nonlinear-ts`; then add live commit proof before enabling apply. |
| `k3sctl-adapter` | UniDesk-managed D601 direct Compose control bridge, outside the native k3s fault domain; it is the control path for k3s-managed services and must not be moved into k3s. | `CI.json` source-build supported through `ci publish-user-service --service k3sctl-adapter`; artifact is `127.0.0.1:5000/unidesk/k3sctl-adapter:<commit>` from the UniDesk Dockerfile. | Artifact consumer exposes plan/dry-run only for service/container `k3sctl-adapter`; live replacement is supervisor-only because replacing the bridge can remove the repair path for k3s. | No normal dev target. DEV acceptance is read-only bridge health, service catalog/proxy checks and dry-run contract review only. | Dry-run/read-only only in this lane. Real prod replacement requires explicit supervisor confirmation, rollback proof and out-of-band recovery access. | Must remain recoverable while k3s may be broken; worker automation must not self-replace or k3s-manage the bridge. | Write a supervised bridge-upgrade runbook with rollback and out-of-band access checks; keep CLI dry-run as the standard preflight. |
| `code-queue` | Production execution plane is D601 native k3s (`unidesk` namespace) behind `k3sctl-adapter`; dev execution plane is `unidesk-dev` scheduler/read/write/provider-egress-proxy. Main-server `code-queue-mgr` is a separate control-plane sidecar. | `CI.json` source-build supported through `ci publish-user-service --service code-queue` for dev image validation only; artifact is `127.0.0.1:5000/unidesk/code-queue:<commit>`. | Reviewed dev-only k3s artifact consumer updates only `unidesk-dev` Code Queue objects. `deploy plan --env prod --service code-queue` and `artifact-registry deploy-service --env prod --service code-queue` must stay unsupported. | Allowed only as dry-run/source evidence here; a later human-approved dev live apply may consume the artifact into `unidesk-dev` outside the running Code Queue task. | Not implemented and not authorized. No production artifact deploy, manifest mutation, scheduler/runner restart, interrupt or cancel is allowed. | Production still has hostPath/source and active-run safety boundaries; self-deploy would couple the deployment actor to the target being replaced. | Keep dev dry-run coverage; design a separate supervisor-approved production CD consumer before any prod mutation is considered. |
| `decision-center` | NC01 k8s user service; prod runs `unidesk/deployment/decision-center` and `unidesk/service/decision-center` selected by `config/unidesk-host-k8s.yaml`. | Source image may still be built from the UniDesk Dockerfile, but production release evidence is YAML-first NC01 k8s state plus GitHub repo storage, not D601 registry artifact consumption. | D601 k3s artifact consumer is retired for the current production path. Desired state must include NC01 target, namespace, GitHub repo `pikasTech/decision-center-data`, base path `data`, and Secret sourceRef for `decision-center-github-ssh`. | Dev gate remains focused product validation: record CRUD, diary lifecycle, doc-number uniqueness and frontend visibility. It must not require a D601 `decision-center-dev` runtime as current truth. | Prod acceptance verifies NC01 `/health`, records, diary editor, frontend page, no public business ports, GitHub storage read/write and Secret presence/fingerprint. | Product completeness and manual UI acceptance can remain open, but PostgreSQL is index/cache only and D601 artifact evidence is not storage/deploy truth. | Keep YAML-first NC01 deploy and GitHub storage checks sufficient so future desired-state edits cannot reintroduce old D601/PostgreSQL truth. |
| `todo-note` | NC01 k8s user service; prod runs private `unidesk/deployment/todo-note` and `unidesk/service/todo-note` selected by `config/unidesk-host-k8s.yaml`. | `CI.json` assigns production to `platform-infra pipelines-as-code`; consumer `unidesk-host` reuses Repository `sentinel-nc01-v03`, builds the vendored Dockerfile and publishes a digest-pinned image. | GitOps/Argo CD owns the Todo Note Service/Deployment. The full host renderer supplies shared catalog and Secret objects but skips Todo Note runtime objects to avoid dual ownership. | PaC plan, render and health checks must prove the NC01 namespace, digest pin, private ClusterIP and no Secret material in GitOps output. | Six lists, 1167 history rows, action/undo/redo writes, GitHub `todo-note/` read/write, PostgreSQL cache alignment, frontend and CLI access passed. | CC01 container and legacy `config.json`/`deploy.json`/Compose ownership are retired. | Keep PaC/Argo and GitHub storage as the sole truth; do not restore the old runtime path. |

Minimum evidence for this lane is:

| Evidence | Command |
| --- | --- |
| Code Queue dev target shape | `bun scripts/cli.ts deploy plan --env dev --service code-queue` |
| Code Queue prod unsupported shape | `bun scripts/cli.ts deploy plan --env prod --service code-queue` |
| D601 direct Compose dry-run for `findjob` / `pipeline` | `bun scripts/cli.ts deploy apply --env dev --service <findjob|pipeline> --dry-run` |
| MET Nonlinear blocked dry-run | `bun scripts/cli.ts deploy apply --env dev --service met-nonlinear --dry-run` |
| k3s control bridge dry-run | `bun scripts/cli.ts deploy apply --env prod --service k3sctl-adapter --dry-run` |
| CI producer preflight | `bun scripts/cli.ts ci publish-user-service --service <service> --commit <full-sha> --dry-run` |

### Upstream Image Evidence

The catalog expression is intentionally minimal and parseable:

| Evidence command | Required result shape |
| --- | --- |
| `jq '.artifacts[] | select(.kind=="upstream-image") | {serviceId, upstream, status}' CI.json` | both File Browser services show `upstream.imageRef=docker.io/filebrowser/filebrowser:v2.63.3`, a sha256 `upstream.digestRef`, `sourceRevision=ca5e249e3c0c94159c2136a0cd431a424eb18472`, mirror intent under `upstream/filebrowser/filebrowser`, and `status=blocked` |
| `bun scripts/cli.ts config show` with the File Browser `artifactSource` projection | both services parse as `kind=upstream-image`, `digestPinRequired=true`, `mirrorRepository=127.0.0.1:5000/upstream/filebrowser/filebrowser`, `ciDockerfileBuild=false`, and `pullOnlyCd=true` |
| `docker manifest inspect --verbose docker.io/filebrowser/filebrowser:v2.63.3` | must resolve the upstream manifest digest before rollout; if the registry request times out, rollout remains blocked |
| `bun scripts/cli.ts ci publish-user-service --service filebrowser --commit <full-sha> --dry-run` | returns `ok=false` with `status=blocked`, upstream digest/mirror metadata, and no Dockerfile source build |

The digest/mirror dry-run contract is:

1. Resolve `docker.io/filebrowser/filebrowser:v2.63.3` to `docker.io/filebrowser/filebrowser@sha256:<manifest-digest>`.
2. Mirror only that resolved digest to `127.0.0.1:5000/upstream/filebrowser/filebrowser@sha256:<manifest-digest>`.
3. Record the digest ref used by CD; do not promote a mutable tag as release truth.
4. Refuse pull-only CD while the digest is unknown or the mirror digest differs from the upstream digest.

Pull-only CD validation must be expressed as concrete checks:

| Check | Form |
| --- | --- |
| Pull source | `docker pull docker.io/filebrowser/filebrowser@sha256:<manifest-digest>` or `docker pull 127.0.0.1:5000/upstream/filebrowser/filebrowser@sha256:<manifest-digest>` |
| Runtime identity | `docker inspect` reports the expected image id/digest and OCI labels `org.opencontainers.image.version=2.63.3` and `org.opencontainers.image.revision=ca5e249e3c0c94159c2136a0cd431a424eb18472` |
| Service health | UniDesk private proxy health succeeds for `filebrowser` and `filebrowser-d601`; direct public exposure remains forbidden |
| Build absence | no `docker build`, `docker compose up --build`, CI Dockerfile producer, or source checkout is used for these services |

## Frozen Special-Case Inventory

This table freezes the boundary between standard artifact services, upstream image consumers, bootstrap or maintenance paths, unclear manual-risk paths, and legacy paths that may be removed only after replacement evidence exists.

| Object | Classification | Current Path | Target Path | Cleanup Prerequisites | Risk If Misclassified | Next Stage / Remove Later |
| --- | --- | --- | --- | --- | --- | --- |
| `filebrowser` | `upstream-digest` | `config.json` registers D518 `unidesk-direct` with `repository.dockerfile=docker.io/filebrowser/filebrowser:v2.63.3`, `artifactSource.kind=upstream-image`, and provider-local `docker run --restart unless-stopped`. `CI.json` marks it blocked from source-build CI. | Pull-only upstream image consumer: resolve `docker.io/filebrowser/filebrowser:v2.63.3` to a manifest digest, optionally mirror that digest to D601 registry, run by digest, then verify image identity and private proxy health. | Implement upstream digest resolver/mirror worker, record mirror digest, add container image-id/digest validation, preserve File Browser state paths and rollback runbook. | Treating it as source-build would build the wrong artifact; deleting the docker-run repair path before pull-only CD exists can break D518 file access. | Do not delete in the next stage; implement digest/mirror CD first. |
| `filebrowser-d601` | `upstream-digest` | Same upstream image and blocked CI catalog as `filebrowser`, but provider-local container is `unidesk-filebrowser-d601` on D601. | Same pull-only digest or mirror digest model as `filebrowser`, scoped to D601. | Same digest/mirror worker and runtime verification, plus D601 path/volume audit. | Same source-build confusion; premature cleanup can remove D601 file access used by operations. | Do not delete in the next stage; implement with `filebrowser`. |
| `database` | `upstream-digest` | Main-server Compose service uses `postgres:16-alpine`, named volume `unidesk_pgdata_10gb`, init SQL, and pinned config files. It is lifecycle-managed by `server start`/`server stop`, not by `server rebuild`. | Upstream digest pin or mirror for PostgreSQL image; Compose remains volume-preserving and pull-only for the image. | Digest pin for `postgres:16-alpine`, database backup/restore drill, major-version upgrade policy, and no `down -v` or volume deletion. | Data loss or unplanned PostgreSQL upgrade; a source-build model is meaningless for this service. | Keep; never remove as legacy. |
| `provider-gateway` | `bootstrap-keep` | Main-server Compose `server rebuild provider-gateway` and provider `provider.upgrade mode=schedule` can repair the control bridge. Host SSH keys are mounted read-only for maintenance, while normal automation uses WebSocket/provider tasks. | Standard source artifact publication for normal releases plus retained protected self-upgrade/repair path; Host SSH remains explicit maintenance only. | Prove provider-gateway artifact consumer or protected upgrade can recover a broken provider without SSH session lifetime coupling; keep Docker socket scope and loopback egress checks. | Removing the bridge repair path can strand providers and block CD, diagnostics, or registry relay. | Keep; not removable next stage. |
| `dev-frontend-proxy` | `bootstrap-keep` | Main-server Compose service built from `src/components/dev-frontend-proxy/Dockerfile`, exposed on the dev frontend port, and rebuildable by `server rebuild dev-frontend-proxy`. | Small bootstrap proxy kept as a main-server support service; optional future source artifact only after dev frontend proxy health and D601 dev UI rollout are covered. | Decide whether the proxy should become a standard artifact, add CI image and pull-only consumer if yes, and preserve dev UI recovery. | Deleting it breaks the public dev UI path even when production is healthy. | Keep; not removable next stage. |
| `server rebuild backend-core` | `bootstrap-keep` | Local main-server Compose build/recreate, serialized by Compose lock and validated after up. Docs forbid using it for Rust iteration or standard prod CD. | Recovery-only path; standard production path is D601 CI artifact plus `deploy apply --env prod --service backend-core`. | Backend-core artifact CD rollback proven under outage conditions; recovery runbook can repair a broken core without local Rust build. | Removing too early can block recovery when core or registry relay is degraded. | Keep; not removable next stage. |
| `server rebuild frontend` | `bootstrap-keep` | Local main-server Compose build/recreate for production frontend; maintenance-only and not release evidence. | Standard frontend release is CI `unidesk/frontend:<commit>` plus dev/prod artifact consumer and health/image-label verification. | Equivalent pull-only repair command, registry rollback, and frontend health verification runbook. | Local dirty bundle can become accidental production truth; deleting too early removes emergency UI repair. | Keep for now; degrade only after pull-only repair is proven. |
| `server rebuild baidu-netdisk` | `bootstrap-keep` | Local main-server Compose build/recreate for Baidu Netdisk; maintenance-only. | D601 CI source-build artifact plus dev/prod pull-only consumer. | Pull-only repair and token/staging persistence validation. | Dirty rebuild can hide token/staging regressions; premature deletion can block storage service repair. | Keep for now; degrade after pull-only repair is proven. |
| `server rebuild todo-note` | `removed` | Legacy main-server/CC01 Compose recovery path retired after NC01 import verification. | NC01 PaC/Argo delivery plus GitHub-backed rollback and storage verification. | Six lists, 1167 history rows, temporary write/undo/redo/delete lifecycle, GitHub revision and PostgreSQL cache alignment are verified on NC01. | Restoring it can reintroduce split-brain writes. | Keep the command, catalog/deploy/Compose entry and old container absent. |
| `server rebuild code-queue-mgr`, `project-manager`, `oa-event-flow` | `bootstrap-keep` | Local main-server Compose rebuild for internal or direct main-server services; some have artifact consumer validation, while live prod apply may remain gated. | Convert each to CI artifact plus pull-only consumer and live health commit verification where supported; keep recovery entry until equivalent repair exists. | Per-service artifact consumer, runtime deploy metadata, rollback, and persistence validation. | Dirty local rebuilds can bypass `deploy.json`; deleting before rollback can break main-server service recovery. | Keep; reassess service by service. |
| `server rebuild provider-gateway` | `bootstrap-keep` | Local Compose rebuild for the main-server provider gateway. | Keep as bridge repair; normal upgrades should use protected provider upgrade or artifact path when available. | Same provider-gateway recovery proof as above. | May strand host-level observability and maintenance dispatch. | Keep. |
| `server rebuild filebrowser*`, `database`, `code-queue`, `k3sctl-adapter`, unknown services | `allowed-remove-later` for silent fallback only | Historically the CLI threw a generic usage error; no valid main-server Compose rebuild exists for these objects. | Structured `unsupported-server-rebuild` result; no job creation, no source build, no Compose mutation. | Operators must use documented upstream digest, k3s artifact, bridge repair, or manual-risk path instead. | Generic errors can lead operators to invent hand-built fallback commands. | Silent/generic fallback is removed; do not delete the real upstream/bootstrap runtime paths. |
| `deploy apply --env prod` unsupported services | `needs-manual` | Environment-ref prod apply reads `origin/master:deploy.json`; services without reviewed prod artifact consumers return structured unsupported. | Add service-specific CI producer, D601 registry artifact, prod consumer, and live commit/digest verification before enabling. | Silent target-side builds would bypass registry, dirty-worktree, and live verification guardrails. | Not removable as a class; enable per service only after review. |
| Local-manifest `deploy apply --file ...` for reviewed prod artifact consumers | `allowed-remove-later` | CLI blocks reviewed prod artifact consumers before source materialization and points to `deploy apply --env prod --service <id>`. | Environment-ref deploy and artifact consumer only for standard production. | Confirm no recovery runbook requires local-manifest prod apply; provide rollback via registry. | Dirty local `deploy.json` or target-side source build can become production truth. | Allow removing or further degrading after recovery runbooks move to env-ref artifacts. |
| D601 target-side build path for dev `backend-core` | `allowed-remove-later` | Replaced by `deploy apply --env dev --service backend-core` artifact consumer, which imports `127.0.0.1:5000/unidesk/backend-core:<commit>` into `unidesk-dev/backend-core-dev` without compiling. | Keep only structured no-build artifact consumer; remove stale target-side callers/docs. | D601 CI artifact publication, dev consumer, Rust check/build parity, and rollback evidence. | Any fallback rebuild could bypass the CI artifact and hide Rust build failures. | Legacy path is no longer the standard dev route. |
| D601 maintenance-channel direct deploy for non-bridge services | `allowed-remove-later` | Direct provider `host.ssh` build/apply is blocked for normal D601 services; `codex deploy` is disabled. | Only artifact consumers or reviewed native bootstrap may mutate D601 services. | Confirm no supported runbook still calls the disabled path; keep structured unsupported output. | A second deploy control plane can bypass registry artifacts and k3s verification. | Remove stale callers/docs after no references remain. |
| `artifact-registry deploy-backend-core` | `allowed-remove-later` | Compatibility name returns a structured deprecated result; replacement is `deploy apply --env dev|prod --service backend-core`. | Use only deploy reconciler entry for backend-core CD. | All callers and docs stop using the compatibility action. | Bypasses common deploy guardrails if re-enabled casually. | Remove compatibility after callers are migrated. |
| `k3sctl-adapter` | `bootstrap-keep` | D601 direct Compose control bridge outside k3s; artifact consumer is plan/dry-run only and live prod apply needs supervisor confirmation. | Keep outside the k3s fault domain; optionally publish artifact for controlled upgrade, but retain bridge repair. | Supervisor-approved live upgrade path, adapter health/control-plane validation, and rollback without losing Kubernetes access. | Removing or moving it into k3s can remove the bridge needed to repair k3s. | Keep; not removable next stage. |
| Maintenance SSH / manual Docker / manual k3s commands | `needs-manual` | Available through provider-gateway Host SSH, WSL SSH, manual Docker, and native k3s commands for repair and investigation. | Explicit maintenance-only runbooks with bounded commands, no silent source builds, no dirty worktree release truth, and no default CD fallback. | For each manual entry, document owner, trigger, rollback, volume/scope safety, and replacement artifact path. | Manual state can become undocumented production truth or destroy bootstrap access if over-cleaned. | Do not delete globally; constrain and document per path. |
| Dirty worktree, mutable `latest` tag, hand-mutated container state | `allowed-remove-later` as release truth | These may exist as local operator state or Docker cache, but they are not valid deployment evidence. | Standard releases use `deploy.json` commit, commit-tagged or digest-pinned images, image labels, and live commit/digest verification. | Runtime label checks, digest pins, env-ref deploy, and runbooks that reject `latest` and dirty worktree inputs. | Reproducibility and rollback are lost; a healthy old or hand-built container may be accepted accidentally. | Remove as accepted release evidence; keep only as diagnostic observations. |

## Legacy Path Classification

| Path | Classification | Current guardrail | Cleanup condition |
| --- | --- | --- | --- |
| `deploy apply --env prod` for services without a reviewed artifact consumer | Must stay disabled/degraded | returns structured unsupported and refuses maintenance-channel fallback | enable only after each service has a documented artifact producer, dev consumer, prod consumer and live verification |
| Local-manifest `deploy apply` for prod artifact consumers (`backend-core`, `frontend`, `baidu-netdisk`) | Must stay disabled/degraded as a standard prod path | blocked before source build; operator is pointed to `deploy apply --env prod`; Decision Center is excluded because its current prod path is NC01 YAML-first k8s + GitHub storage | remove only after all production deploys use env-ref artifact consumers and no recovery runbook depends on local manifests |
| `artifact-registry deploy-backend-core` | Legacy compatibility entry | returns structured deprecated result; replacement is `deploy apply --env dev|prod --service backend-core` | remove after callers and docs use only `deploy apply --env dev|prod` |
| `server rebuild frontend` and `server rebuild baidu-netdisk` | Maintenance / non-standard | docs classify as maintenance-only; standard release requires CI artifact plus dev/prod artifact consumer | keep until recovery runbooks have equivalent pull-only repair commands |
| `server rebuild backend-core` | Diagnostic/recovery only; not Rust iteration or prod CD | docs forbid Rust iteration and prod backend-core artifact CD through this command | keep for bootstrap/recovery until backend-core artifact CD and rollback are proven under outage conditions |
| `server rebuild dev-frontend-proxy`, `code-queue-mgr`, `project-manager`, `oa-event-flow`, `provider-gateway` on main-server Compose | Bootstrap / recovery / diagnostic | still serialized through Compose lock and post-up validation | reassess service by service after artifact consumers exist |
| `server rebuild todo-note` on the legacy runtime | Removed | NC01 import and GitHub read/write verification completed | keep absent to prevent split-brain writes |
| `provider.upgrade mode=schedule` for provider-gateway | Must be retained | protected upgrade path with validation; Host SSH self-rebuild remains forbidden | do not delete; it is the provider-gateway recovery path |
| D601 direct `docker build`, `kubectl apply`, `docker compose up --build` used by deploy executor for native k3s setup or approved recovery | Bootstrap / recovery | limited to native k3s initialization or documented recovery; dev backend-core CD is now artifact-only; Code Queue prod is not enabled | convert only after artifact consumers or native bootstrap replacements exist |
| D601 direct Code Queue / old `codex deploy` | Must stay disabled/degraded | compatibility command throws; docs classify direct deployment as forbidden | wait for controlled Code Queue dev/prod CD worker |
| File Browser `docker run` image-only path | Needs later worker | now cataloged as upstream-image consumer; no CI Dockerfile build allowed | implement digest-resolved pull-only/mirror CD before treating it as standard deployment |

## Guardrails Added

- Upstream-image services are represented in `CI.json.artifacts[]` with `kind=upstream-image` and in `config.json.microservices[].repository.artifactSource`; they are explicitly outside source-build producers.
- `ci publish-user-service` returns a structured blocked result for registered `upstream-image` services instead of trying to interpret `repository.dockerfile` as a source Dockerfile.
- Local-manifest production deploy for reviewed artifact consumers is blocked before source materialization/build, so prod cannot silently fall back to target-side source build or a dirty worktree.
- `artifact-registry deploy-backend-core` is demoted to a structured deprecated result; backend-core production CD must enter through `deploy apply --env prod`.
- `server rebuild` returns `unsupported-server-rebuild` for upstream image consumers, database, D601 execution/control-plane objects and unknown services; it does not create a job or mutate Compose for those objects.

### Guardrail Evidence Matrix

| Guardrail name / result key | Command evidence | Legacy path covered | Deletion status |
| --- | --- | --- | --- |
| `upstream-image` CI publish rejection | `bun scripts/cli.ts ci publish-user-service --service filebrowser --commit <full-sha> --dry-run` returns `ok=false`, `status=blocked`, and upstream metadata | File Browser accidentally entering Dockerfile CI | keep; deletion of docker-run repair waits for digest/mirror CD |
| `prod-artifact-consumer-local-manifest-blocked` | `bun scripts/cli.ts deploy apply --file deploy.json --service frontend --dry-run` returns `ok=false`, this error key, and points to `deploy apply --env prod --service <service-id> --commit <full-sha>` | prod source-build fallback for reviewed artifact consumers | keep; local manifest mode may still be needed for non-prod/recovery until runbooks are replaced |
| `artifact-registry deploy-backend-core` deprecated result | `bun scripts/cli.ts artifact-registry deploy-backend-core --commit <full-sha>` returns `ok=false`, `deprecated=true`, and replacement `deploy apply --env prod --service backend-core --commit <full-sha>` | backend-core prod CD bypassing deploy reconciler guardrails | keep name only as compatibility until all callers stop using it |
| prod unsupported result for services without artifact consumers | `deploy apply --env prod --service <unsupported-service> --dry-run` must return unsupported instead of falling back to source build | target-side source build/maintenance-channel prod deploy | keep disabled until service-specific artifact consumers exist |
| `unsupported-server-rebuild` | `bun scripts/cli.ts server rebuild filebrowser` and `bun scripts/cli.ts server rebuild database` return `ok=false`, `error=unsupported-server-rebuild`, classification, replacement path and allowed services | accidental source-build/Compose fallback for upstream images, database, k3s/control bridges and unknown services | keep; it removes only silent/generic fallback, not bootstrap or maintenance runtime paths |
| backend-core/code-queue prod boundary | docs and deploy support matrix allow backend-core and D601 Code Queue only in dev validation for this phase | accidental prod validation entrypoints for backend-core or Code Queue | do not add executable prod test/deploy validation in this precheck |

The guarded-but-not-deletable paths are: `server rebuild backend-core`, `server rebuild frontend`, `server rebuild baidu-netdisk`, provider-gateway protected upgrade, native k3s bootstrap, k3sctl-adapter bridge repair, and File Browser provider-local docker-run repair. Dev backend-core target-side rollout is no longer a standard guarded path; its replacement is the D601 registry artifact consumer, and stale target-side callers/docs should be removed once no runbook references remain.

## Safety Boundary

CI may build images, push to the D601 loopback registry and report immutable digests. CI must not run production CD, call `deploy apply` for production, mutate production namespaces, recreate production Compose services or update `deploy.json`.

backend-core now has explicit dev/prod artifact consumers; CI still only produces the image and must not deploy it as a side effect. D601 `code-queue` remains restricted to dev image validation in this phase. Any future production rollout for Code Queue 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 tag/digest provenance, pull-only/no-runtime-build validation, `selfBootstrapGuard`, `requiresSupervisorApproval` 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 and docs only. Dry-run output must show that dev targets only `unidesk-dev`, prod exposes no runtime deploy target, production mutation is unsupported, and Code Queue self-deploy, scheduler/runner mutation, interrupt and cancel actions are forbidden.

## 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. D601 `code-queue` production validation is out of scope. backend-core dev rollout must use the artifact consumer path, and a provider-offline result is an infrastructure blocker rather than permission to validate production.

The structured read-only preflight entrypoints are `artifact-registry status|health` and `ci publish-user-service --dry-run`. Remote runners may call them through the frontend passthrough path, and the result must classify missing backend-core, database, provider or registry channels as `runnerDisposition=infra-blocked`. The detailed probe list remains in `missingChannels`; the stable runner-facing domain list is `missingControlChannels` with only `backend-core`, `database`, `provider` and `registry`. Those cases are infrastructure blockers, not business failures and not a license to retry a real publish. A non-dry-run publish may be attempted only where `controlledPublish` points: D601 CI, namespace `unidesk-ci`, PipelineRun `unidesk-user-service-artifact-publish`.

## Next Stage Dispatch Matrix

This matrix describes the next promotion stage after dry-run coverage is in place. It favors correctness over throughput, keeps `backend-core` and D601 `code-queue` dev-only, and splits dev and prod only where the runtime policy already allows it.

| Service class | Target branch | CI current state | CD current state | DEV acceptance | PROD acceptance | Blockers | Suggested model |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `backend-core` | `master` | source-build supported through `ci publish-backend-core` | dev + prod artifact consumer | dev artifact rollout to `unidesk-dev/backend-core-dev` with no CD compile | prod artifact recreate with live commit proof | CI resource, registry artifact, and runtime health evidence | `GPT-5.5` |
| `code-queue` | `master` | source-build supported, dev-only | dev-only k3s consumer | dev artifact validation for `unidesk-dev` scheduler/read/write/provider-egress-proxy | not implemented; must remain unsupported | production boundary, hostPath/source contract, scheduler/egress dependency health | `GPT-5.5` |
| `frontend` | `master` | source-build supported | dev + prod artifact consumer | commit-pinned dev rollout and `/health.deploy.commit` | commit-pinned prod recreate and UI route verification | none beyond standard artifact/CD checks | `GPT-5.5` |
| `baidu-netdisk` | `master` | source-build supported | dev + prod artifact consumer | pull-only dev validation is diagnosable through `runtimeSecrets.secretSource`, `requiredSecretsPresent`, `missingSecretKeys` and `recommendedAction`; live dev apply waits for the secret source condition | prod artifact and health are aligned; further prod action is only focused post-apply proxy/auth verification after operator approval | canonical Compose env secret source and `/health.auth` gate; no manual secret operation in worker tasks | `GPT-5.5` |
| `project-manager` | `master` | source-build supported | dev + prod artifact consumer | dev artifact validation with `/api/projects` | prod artifact validation with live commit proof | none beyond standard artifact/CD checks | `MiniMax` for dry-run/reporting, `GPT-5.5` for release sign-off |
| `oa-event-flow` | `master` | source-build supported | dev + prod artifact consumer | dev artifact validation with `/api/diagnostics` | prod artifact validation with live commit proof | none beyond standard artifact/CD checks | `MiniMax` for dry-run/reporting, `GPT-5.5` for release sign-off |
| `todo-note` | `master` | vendored source build owned by NC01 `platform-infra pipelines-as-code` | GitOps/Argo CD consumes a digest-pinned image into `unidesk`; durable state is GitHub `pikasTech/decision-center-data/todo-note` | PaC plan/render proves private Service, digest pin, Secret-free GitOps output and shared Repository parameter isolation | NC01 health, six-list/1167-history migration, action/undo/redo write lifecycle, GitHub verify, PostgreSQL cache, frontend and CLI proof | cutover must finish before removing CC01; dual runtime writes are forbidden | `GPT-5.5` |
| `decision-center` | `master` | source-build supported | NC01 YAML-first k8s + GitHub repo storage | dev acceptance is focused record CRUD, diary lifecycle, doc-number uniqueness and frontend visibility, without D601 runtime as current truth | prod acceptance is NC01 health, GitHub read/write, frontend and CLI proof | doc-management completeness, GitHub storage migration/backfill and UI acceptance; PostgreSQL is index/cache only | `GPT-5.5` |
| `mdtodo` | `master` | source-build supported | dev + prod k3s consumer | dev service is absent until the health-metadata-capable desired artifact is published and `unidesk-dev/mdtodo-dev` is created/verified | prod is healthy at the previous annotated commit, but the desired artifact now points at the commit that added `/health.deploy` and `/live.deploy`; prod replacement remains blocked until dev proof exists | registry artifact, dev service, runtime health metadata proof and prod runtime commit drift; no NodePort/hostPort/public backend exposure | `MiniMax` for prompt prep, `GPT-5.5` for approval |
| `claudeqq` | `master` | source-build supported | dev + prod k3s consumer | dev service is absent until the desired artifact is published and `unidesk-dev/claudeqq-dev` is created/verified | prod health reports the desired commit and NapCat login, but artifact and event API proof remain open | registry artifact, dev service, event API surface; NapCat/backend port exposure must stay private | `MiniMax` for prompt prep, `GPT-5.5` for approval |
| `findjob` | `master` | source-build supported | dev + prod direct Compose consumer | pull-only dev validation on D601 with image labels and `/api/health` | pull-only prod recreate with live commit proof | target-side compose health/labels only, no public business ports | `DeepSeek` for dry-run matrix drafting, `GPT-5.5` for final gate |
| `pipeline` | `master` | source-build supported | dev + prod direct Compose consumer | pull-only dev validation on D601 with image labels and `/health` | pull-only prod recreate with live commit proof | runtime contract is commit-label + compose service identity | `DeepSeek` for dry-run matrix drafting, `GPT-5.5` for final gate |
| `met-nonlinear` | `master` | source-build supported | dev dry-run only | runtime-verification-blocked until long-running TS service image contract is fixed | not authorized | image contract mismatch between ML Dockerfile and TS runtime service | `GPT-5.5` |
| `k3sctl-adapter` | `master` | source-build supported | plan/dry-run only | no normal dev target; only control-bridge health and recovery evidence | prod live apply requires supervisor confirmation | bridge recovery, k3s fault-domain isolation, no worker self-replacement | `GPT-5.5` |
| `filebrowser` / `filebrowser-d601` | `master` | upstream-image blocked | pull-only mirror target | digest resolution, mirror governance and private proxy health only | not in this phase | upstream digest/mirror worker not yet implemented | `DeepSeek` for evidence summarization, `GPT-5.5` for blocker resolution |

Frontend lane closure evidence is intentionally lightweight and non-mutating: `deploy.json` dev/prod both request the same frontend commit, `CI.json` keeps the producer as `ci publish-user-service`, the D601 registry manifest digest is recorded, dev and prod health expose matching `deploy.commit` / `deploy.requestedCommit`, `ci publish-user-service --dry-run` is ready, and both CD dry-runs are artifact consumers with no source build.

User-service artifact gap reviews must report the same normalized fields for each service: `desiredCommit`, `runtimeCommit`, `artifactExists`, `devStatus`, `prodStatus`, `blockedScopes` and `recommendedAction`. The review remains lightweight and non-mutating: `deploy.json` dev/prod desired commits, `CI.json` producer metadata, structured status fields, dev/prod artifact-consumer dry-runs, and service health metadata such as `/health.deploy` plus `/live.deploy` are the evidence. These checks do not publish artifacts, apply manifests, recreate services, restart services, run full check/e2e, or probe browser UI unless separately authorized.

Planned parallelism for the next wave should be three lanes:

1. Lane A: `frontend`, `baidu-netdisk`, `project-manager`, `oa-event-flow`.
2. Lane B: `decision-center`, `mdtodo`, `claudeqq`, `todo-note`.
3. Lane C: `findjob`, `pipeline`, `met-nonlinear`, `k3sctl-adapter`, `code-queue`, `backend-core`.

Lane C must stay split into dev-only work for `backend-core` and `code-queue`, plus read-only or dry-run work for `met-nonlinear` and `k3sctl-adapter`. `backend-core` and `code-queue` must not be promoted into prod acceptance tasks in this phase.
