Files
pikasTech-unidesk/docs/reference/cicd-standardization.md
T

337 lines
58 KiB
Markdown

# CI/CD Standardization
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).
## 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, and resource profile identifiers. The planned first keys are `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.
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 and port policy, backend health path, Compose file/service/container names, development SSH/worktree details, and secret source locations.
- Compose and Kubernetes manifests: concrete container specs, volumes, PVCs, security context, rollout strategy and raw resource requests/limits until a renderer owns those files.
- Artifact-registry executor code: low-level pull/import/retag/recreate commands, registry probes and platform-specific verification scripts. The executor must consume the normalized deploy plan instead of hardcoding release intent.
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 contract is `bun scripts/issue-60-cicd-drift-contract-test.ts`. The test parses local JSON/docs only; it does not publish images, deploy services, restart containers, run Playwright or run full e2e.
## 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`, `todo-note`, `code-queue-mgr`, `findjob`, `pipeline`, `met-nonlinear`, `k3sctl-adapter`, `mdtodo`, `claudeqq` through `ci publish-user-service`;
- `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/contract 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 contract tests and dev dry-run coverage; design a separate supervisor-approved production CD consumer before any prod mutation is considered. |
| `decision-center` | D601 native k3s user service; dev runs `unidesk-dev/decision-center-dev`, prod runs `unidesk/decision-center`, both behind backend-core -> provider-gateway -> k3sctl-adapter -> Kubernetes API service proxy. | `CI.json` source-build supported through `ci publish-user-service --service decision-center`; artifact is `127.0.0.1:5000/unidesk/decision-center:<commit>` from the UniDesk Dockerfile. | Dev and prod are reviewed D601 k3s artifact consumers. Desired state, live health, and registry artifact must point at the same commit; drift where live is newer than `deploy.json` is corrected by repinning `deploy.json`, not by redeploying. | Closed for artifact CD when `deploy plan --env dev --service decision-center` is no-build and health reports matching `deploy.commit` / `deploy.requestedCommit`. Focused product gates remain record CRUD, diary lifecycle and frontend Decision Center visibility. | Closed for artifact CD when `deploy plan --env prod --service decision-center` is no-build and health reports matching `deploy.commit` / `deploy.requestedCommit`. Remaining acceptance is manual UI/product verification: health, records, diary editor, frontend page, no public business ports and live commit/artifact information. | Product completeness and manual UI acceptance can remain open, but they are not deployment drift. Registry artifact digest and health commit are the release evidence. | Keep `scripts/decision-center-desired-state-contract-test.ts` in the lightweight script gate so future desired-state edits cannot reintroduce source-build or stale-commit drift. |
Minimum evidence for this lane is:
| Evidence | Command |
| --- | --- |
| Code Queue dev/prod boundary contract | `bun scripts/code-queue-cicd-dry-run-contract-test.ts` |
| 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` |
| Decision Center desired/live no-build drift guard | `bun scripts/decision-center-desired-state-contract-test.ts` |
| Issue #60 phase-one deploy.json drift guard | `bun scripts/issue-60-cicd-drift-contract-test.ts` |
### 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`, `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`, `decision-center`) | Must stay disabled/degraded as a standard prod path | blocked before source build; operator is pointed to `deploy apply --env prod` | 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`, `todo-note`, `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 |
| `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, 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. 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` | external source-build supported | dev + prod Compose artifact consumer | consumer dry-run is ready; live dev remains blocked until the desired artifact exists | prod behavior is healthy, but runtime commit proof is absent until the no-build recreate lands image labels and health deploy metadata | registry artifact, runtime commit proof and health deploy metadata | `DeepSeek` for digesting external-source evidence, `GPT-5.5` for final gate |
| `decision-center` | `master` | source-build supported | dev + prod k3s consumer closed when desired/live/artifact commit match and dry-run stays no-build | dev artifact CD closed; remaining dev acceptance is focused record CRUD, diary lifecycle, doc-number uniqueness and frontend visibility | prod artifact CD closed; remaining prod acceptance is manual UI/product verification after health/live commit proof | doc-management completeness, PostgreSQL truth and UI acceptance; no deployment drift when desired/live/artifact are aligned | `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:
```bash
bun scripts/frontend-artifact-lane-contract-test.ts
```
The contract fixes the current sample around one artifact lane: `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 issue #9 gap contract is intentionally lightweight and non-mutating:
```bash
bun scripts/issue-9-mdtodo-health-metadata-contract-test.ts
bun scripts/issue-9-user-service-artifact-gap-contract-test.ts
```
The contract pins the current `mdtodo`, `claudeqq` and `todo-note` gap surface: `deploy.json` dev/prod desired commits, `CI.json` producer metadata, structured status fields and dev/prod artifact-consumer dry-runs. `mdtodo` also has a local health metadata contract that starts the service against a temporary Markdown workspace and verifies `/health.deploy` plus `/live.deploy` before publication. These tests do not publish artifacts, apply manifests, recreate services, restart services, run full check/e2e, or probe browser UI.
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.