diff --git a/docs/reference/cicd-standardization.md b/docs/reference/cicd-standardization.md index bf6bad3c..1d3d7179 100644 --- a/docs/reference/cicd-standardization.md +++ b/docs/reference/cicd-standardization.md @@ -64,6 +64,17 @@ Cataloged but blocked: `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 ` 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:` | master-server Compose service `baidu-netdisk`, container `baidu-netdisk-backend` | `deploy plan/apply --env dev --service baidu-netdisk` 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 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 | D601 registry artifact must exist; live apply also requires 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:` | 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. diff --git a/scripts/deploy-artifact-matrix-contract-test.ts b/scripts/deploy-artifact-matrix-contract-test.ts index 3493963c..fdd1b2ca 100644 --- a/scripts/deploy-artifact-matrix-contract-test.ts +++ b/scripts/deploy-artifact-matrix-contract-test.ts @@ -32,6 +32,39 @@ function listIncludes(value: unknown, expected: string): boolean { return Array.isArray(value) && value.some((item) => item === expected); } +function assertMainServerComposeConsumer( + environment: "dev" | "prod", + serviceId: string, + expectedComposeService: string, + expectedContainerName: string, +): void { + const plan = runDeployPlan(environment, serviceId); + const artifact = asRecord(plan.artifactConsumer, `${serviceId} ${environment} artifactConsumer`); + const target = asRecord(plan.target, `${serviceId} ${environment} target`); + const registry = asRecord(artifact.registry, `${serviceId} ${environment} registry`); + const build = asRecord(artifact.build, `${serviceId} ${environment} build`); + assertCondition(plan.deploymentPath === "d601-registry-artifact-consumer", `${serviceId} ${environment} deployment path must be artifact consumer`, plan); + assertCondition(artifact.consumerKind === "main-server-compose", `${serviceId} ${environment} must be a main-server Compose artifact consumer`, artifact); + assertCondition(artifact.noRuntimeSourceBuild === true, `${serviceId} ${environment} plan must declare no runtime source build`, artifact); + assertCondition(artifact.dryRunOnly === false, `${serviceId} ${environment} should not be dry-run-only`, artifact); + assertCondition(String(artifact.registryImage ?? "").includes(`127.0.0.1:5000/unidesk/${serviceId}:`), `${serviceId} registry image missing`, artifact); + assertCondition(registry.repository === `unidesk/${serviceId}`, `${serviceId} registry repository mismatch`, registry); + assertCondition(registry.digest === null, `${serviceId} plan must not fake digest`, registry); + assertCondition(build.willRunDockerBuild === false, `${serviceId} CD must not run docker build`, build); + assertCondition(build.willRunDockerComposeBuild === false, `${serviceId} CD must not run docker compose build`, build); + assertCondition(build.producerBoundary === "ci publish-user-service", `${serviceId} producer boundary mismatch`, build); + assertCondition(target.runtimeHost === "main-server", `${serviceId} target should be main-server`, target); + assertCondition(target.composeService === expectedComposeService, `${serviceId} compose service mismatch`, target); + assertCondition(target.containerName === expectedContainerName, `${serviceId} container mismatch`, target); + assertCondition(listIncludes(target.forbiddenActions, "docker build"), `${serviceId} plan should forbid docker build`, target); + assertCondition(listIncludes(target.forbiddenActions, "docker compose build"), `${serviceId} plan should forbid compose build`, target); +} + +assertMainServerComposeConsumer("dev", "baidu-netdisk", "baidu-netdisk", "baidu-netdisk-backend"); +assertMainServerComposeConsumer("prod", "baidu-netdisk", "baidu-netdisk", "baidu-netdisk-backend"); +assertMainServerComposeConsumer("dev", "project-manager", "project-manager", "project-manager-backend"); +assertMainServerComposeConsumer("prod", "project-manager", "project-manager", "project-manager-backend"); + const findjob = runDeployPlan("dev", "findjob"); const findjobArtifact = asRecord(findjob.artifactConsumer, "findjob artifactConsumer"); const findjobTarget = asRecord(findjob.target, "findjob target"); @@ -93,6 +126,7 @@ process.stdout.write(`${JSON.stringify({ checks: [ "deploy plan models dev backend-core as a no-build D601 k3s artifact consumer", "dev backend-core plan exposes registry/source/build boundaries and target metadata", + "baidu-netdisk and project-manager dev/prod plans are no-build main-server Compose artifact consumers", "deploy plan distinguishes D601 direct Compose from D601 k3s-managed artifact consumers", "deploy plan exposes no-runtime-source-build and forbidden build/public-port actions", "met-nonlinear remains runtime-verification-blocked",