fix: require current-main deploy desired state

This commit is contained in:
unidesk-code-queue-runner
2026-05-23 04:22:19 +00:00
committed by Code Queue Review
parent b9b625c4e8
commit fff45edf4c
11 changed files with 198 additions and 166 deletions
+9 -9
View File
@@ -59,7 +59,7 @@ Desired-state commit/image convergence review:
```sh
node scripts/deploy-desired-state-plan.mjs --plan --pretty
node scripts/deploy-desired-state-plan.mjs --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty
node scripts/deploy-desired-state-plan.mjs --target-tag <tag> --pretty
node scripts/deploy-desired-state-plan.mjs --promotion-commit <sha> --check
@@ -72,18 +72,18 @@ tags, workload images, and present env mirrors such as `HWLAB_COMMIT_ID`,
`HWLAB_IMAGE`, `HWLAB_IMAGE_TAG`, and `HWLAB_SKILLS_COMMIT_ID` converge. It
also blocks if `hwlab-cloud-api` loses the Code Agent provider Secret ref
`hwlab-code-agent-provider/openai-api-key`, the `OPENAI_API_KEY` env name, or
the DEV egress proxy base-url env. With
`--target-ref` or `--target-tag`, a uniform older desired-state is reported as a
read-only promotion plan. Use `--promotion-commit <sha>` when reviewing or
automating a concrete promotion; then `--check` also fails if any authoritative
desired-state commit, image tag, workload image, or env mirror still points to a
different commit/tag.
the DEV egress proxy base-url env. Without `--check`, `--target-ref` or
`--target-tag` reports a uniform older desired-state as a read-only promotion
plan. With `--check`, any explicit target is a hard current-main/commit-pinning
gate: stale deploy, catalog, workload image, or runtime mirror fields are
blocked before apply.
`deploy/deploy.json` is the deploy truth, but artifact catalog and workload
refs are part of the same desired-state contract. Promotion updates must move
all three together by using the refresh command in blocked mode or
publish-report mode, then run the promotion commit check. Report files are
contextual evidence only and must not override these desired-state files.
publish-report mode, then run `--target-ref origin/main --check` or the
promotion commit check. Report files are contextual evidence only and must not
override these desired-state files.
This is source/dry-run support only. It does not prove a registry image exists,
does not build, pull, push, apply, restart, or touch PROD, and must not be used
+84 -84
View File
@@ -5,13 +5,13 @@
"profile": "dev",
"namespace": "hwlab-dev",
"endpoint": "http://74.48.78.17:16667",
"commitId": "7de6edd",
"artifactState": "published",
"commitId": "b9b625c",
"artifactState": "contract-skeleton",
"publish": {
"ciPublished": true,
"registryVerified": true,
"provenance": "reports/dev-gate/dev-artifacts.json",
"note": "Digest fields were copied from a successful DEV artifact publish report for this source commit."
"ciPublished": false,
"registryVerified": false,
"provenance": "not_available_until_publish",
"note": "Artifact identity was refreshed to this source commit, but no publish report proved registry digests."
},
"healthContract": {
"method": "GET",
@@ -66,11 +66,11 @@
"services": [
{
"serviceId": "hwlab-cloud-api",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:8a0120e976f8369c19887d33d004b763c7dbff3f43fd023fb186abffe199b3ea",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -78,15 +78,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-cloud-web",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:a302a8b4f3c6e7a91e5046398b803e9915dba873cefac97bb7069886b5a48d61",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -94,15 +94,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-agent-mgr",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:a16bba1762f58e4daed7ef94243e3f4f0522332704c9f40bcadab77f11acc7a2",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -110,15 +110,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-agent-worker",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:3c75cb9d6c2b7392749d972c78f9b7a89e4f099a64f902d2eb9810f16d551ceb",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -126,15 +126,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-gateway",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:b5374c763d54628c2e81b86c93c826a27629a8546ae789223e0553ca7c8ab80a",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -142,15 +142,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-gateway-simu",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:be6aff770319c6c2b45f7b5974d4fb7aae4aab79a6670b1d8b87e194387e30dd",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -158,15 +158,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-box-simu",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:54e77f4c2ea9e87fd16becde9a5fe05b935f2472dba443458257dfdda1e87c27",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -174,15 +174,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-patch-panel",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:02bec561d5f08f850e2884a2098da72978ad319d5bd799704ad9650778f11ae2",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -190,15 +190,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-router",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-router:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:bb3b10c486bb1305f4cae7ec2751947eeb0cc60c9993d54e93802b5df3b13fb6",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-router:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -206,15 +206,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-tunnel-client",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:ce9fa92362dd10911dcd66c59412690ccde401ce4ef82b548cfcea37499a725f",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -222,15 +222,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-edge-proxy",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:bf19f6812cbac3c54c938ad12a2b1ef9626a746017f85658b9960a1761afa32c",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -238,15 +238,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-cli",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:b169c015c4c06dcdf6d43131d69dd72be1996cb5970e25014c4cec0271b459c0",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -254,15 +254,15 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
},
{
"serviceId": "hwlab-agent-skills",
"commitId": "7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:7de6edd",
"imageTag": "7de6edd",
"digest": "sha256:7d1b6871763613bf7c6e37f5b05b1b49e2c367b4b0b94abfbb9bceb7ab5de4ed",
"publishState": "published",
"commitId": "b9b625c",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:b9b625c",
"imageTag": "b9b625c",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -270,7 +270,7 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null
"notPublishedReason": "publish_not_run"
}
],
"serviceInventory": {
+21 -21
View File
@@ -1,7 +1,7 @@
{
"manifestVersion": "v1",
"environment": "dev",
"commitId": "7de6edd",
"commitId": "b9b625c",
"namespace": "hwlab-dev",
"endpoint": "http://74.48.78.17:16667",
"health": {
@@ -181,7 +181,7 @@
"services": [
{
"serviceId": "hwlab-cloud-api",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -189,9 +189,9 @@
"env": {
"HWLAB_ENVIRONMENT": "dev",
"HWLAB_PUBLIC_ENDPOINT": "http://74.48.78.17:16667",
"HWLAB_COMMIT_ID": "7de6edd",
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd",
"HWLAB_IMAGE_TAG": "7de6edd",
"HWLAB_COMMIT_ID": "b9b625c",
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-api:b9b625c",
"HWLAB_IMAGE_TAG": "b9b625c",
"HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN": "unidesk-backend,provider-gateway,microservice-proxy",
"HWLAB_CLOUD_DB_URL": "secretRef:hwlab-cloud-api-dev-db/database-url",
"HWLAB_CLOUD_DB_SSL_MODE": "disable",
@@ -210,7 +210,7 @@
},
{
"serviceId": "hwlab-cloud-web",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -218,14 +218,14 @@
"env": {
"HWLAB_ENVIRONMENT": "dev",
"HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
"HWLAB_COMMIT_ID": "7de6edd",
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
"HWLAB_IMAGE_TAG": "7de6edd"
"HWLAB_COMMIT_ID": "b9b625c",
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:b9b625c",
"HWLAB_IMAGE_TAG": "b9b625c"
}
},
{
"serviceId": "hwlab-agent-mgr",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -237,7 +237,7 @@
},
{
"serviceId": "hwlab-agent-worker",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -249,7 +249,7 @@
},
{
"serviceId": "hwlab-gateway",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -261,7 +261,7 @@
},
{
"serviceId": "hwlab-gateway-simu",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -281,7 +281,7 @@
},
{
"serviceId": "hwlab-box-simu",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -300,7 +300,7 @@
},
{
"serviceId": "hwlab-patch-panel",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -321,7 +321,7 @@
},
{
"serviceId": "hwlab-router",
"image": "127.0.0.1:5000/hwlab/hwlab-router:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-router:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -333,7 +333,7 @@
},
{
"serviceId": "hwlab-tunnel-client",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -347,7 +347,7 @@
},
{
"serviceId": "hwlab-edge-proxy",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -359,7 +359,7 @@
},
{
"serviceId": "hwlab-cli",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
@@ -370,13 +370,13 @@
},
{
"serviceId": "hwlab-agent-skills",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:b9b625c",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
"profile": "dev",
"replicas": 1,
"env": {
"HWLAB_SKILLS_COMMIT_ID": "7de6edd"
"HWLAB_SKILLS_COMMIT_ID": "b9b625c"
}
}
],
+20 -20
View File
@@ -31,7 +31,7 @@
"containers": [
{
"name": "hwlab-cloud-api",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:b9b625c",
"ports": [
{
"name": "http",
@@ -49,15 +49,15 @@
},
{
"name": "HWLAB_COMMIT_ID",
"value": "7de6edd"
"value": "b9b625c"
},
{
"name": "HWLAB_IMAGE",
"value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd"
"value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:b9b625c"
},
{
"name": "HWLAB_IMAGE_TAG",
"value": "7de6edd"
"value": "b9b625c"
},
{
"name": "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN",
@@ -175,7 +175,7 @@
"containers": [
{
"name": "hwlab-cloud-web",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:b9b625c",
"ports": [
{
"name": "http",
@@ -189,15 +189,15 @@
},
{
"name": "HWLAB_COMMIT_ID",
"value": "7de6edd"
"value": "b9b625c"
},
{
"name": "HWLAB_IMAGE",
"value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7de6edd"
"value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:b9b625c"
},
{
"name": "HWLAB_IMAGE_TAG",
"value": "7de6edd"
"value": "b9b625c"
}
],
"readinessProbe": {
@@ -247,7 +247,7 @@
"containers": [
{
"name": "hwlab-agent-mgr",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:b9b625c",
"ports": [
{
"name": "http",
@@ -303,7 +303,7 @@
"containers": [
{
"name": "hwlab-agent-worker",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:b9b625c",
"env": [
{
"name": "HWLAB_AGENT_SESSION_MODE",
@@ -346,7 +346,7 @@
"containers": [
{
"name": "hwlab-gateway",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:b9b625c",
"ports": [
{
"name": "http",
@@ -407,7 +407,7 @@
"containers": [
{
"name": "hwlab-gateway-simu",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:b9b625c",
"ports": [
{
"name": "http",
@@ -480,7 +480,7 @@
"containers": [
{
"name": "hwlab-box-simu",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:b9b625c",
"ports": [
{
"name": "http",
@@ -548,7 +548,7 @@
"containers": [
{
"name": "hwlab-patch-panel",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:b9b625c",
"ports": [
{
"name": "http",
@@ -612,7 +612,7 @@
"containers": [
{
"name": "hwlab-router",
"image": "127.0.0.1:5000/hwlab/hwlab-router:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-router:b9b625c",
"ports": [
{
"name": "http",
@@ -672,7 +672,7 @@
"containers": [
{
"name": "hwlab-tunnel-client",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:b9b625c",
"ports": [
{
"name": "http",
@@ -744,7 +744,7 @@
"containers": [
{
"name": "hwlab-edge-proxy",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:b9b625c",
"ports": [
{
"name": "http",
@@ -805,7 +805,7 @@
"containers": [
{
"name": "hwlab-cli",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:b9b625c",
"env": [
{
"name": "HWLAB_CLI_ENDPOINT",
@@ -847,7 +847,7 @@
"containers": [
{
"name": "hwlab-agent-skills",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:7de6edd",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:b9b625c",
"ports": [
{
"name": "http",
@@ -857,7 +857,7 @@
"env": [
{
"name": "HWLAB_SKILLS_COMMIT_ID",
"value": "7de6edd"
"value": "b9b625c"
}
],
"readinessProbe": {
+11 -10
View File
@@ -75,7 +75,7 @@ Use the read-only planner before refreshing or reviewing deploy desired-state:
```sh
node scripts/deploy-desired-state-plan.mjs --plan --pretty
node scripts/deploy-desired-state-plan.mjs --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty
node scripts/deploy-desired-state-plan.mjs --target-tag <tag> --pretty
node scripts/deploy-desired-state-plan.mjs --promotion-commit <sha> --check
@@ -88,12 +88,13 @@ service `commitId`/`image`/`imageTag`, deploy service image tags, workload
images, and present env mirrors including `HWLAB_COMMIT_ID`, `HWLAB_IMAGE`,
`HWLAB_IMAGE_TAG`, and `HWLAB_SKILLS_COMMIT_ID`.
`--check` exits non-zero only when the source files are missing or invalid, the
desired-state has internal partial drift, an explicit target tag is invalid or
mutable, or some authoritative fields already point at a target while others do
not. A uniform older state under `--target-ref` is a promotion review plan, not a
failure. The optional report snapshot is contextual evidence only and is not an
authoritative desired-state source.
`--check` exits non-zero when the source files are missing or invalid, the
desired-state has internal drift, an explicit target tag is invalid or mutable,
or any authoritative field does not match the requested target. A uniform older
state under `--target-ref` is only allowed in read-only plan mode; with
`--target-ref origin/main --check` it is a blocker because applying it would keep
DEV pinned to stale artifacts. The optional report snapshot is contextual
evidence only and is not an authoritative desired-state source.
For PR review or CI/CD promotion handoff, use `--promotion-commit <sha>
--check`. That mode is stricter than a `--target-ref` plan: every top-level
@@ -115,7 +116,7 @@ catalog and deploy manifests in blocked mode:
```sh
node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked
node scripts/deploy-desired-state-plan.mjs --promotion-commit $(git rev-parse --short=7 origin/main) --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
```
This updates `deploy/deploy.json`, `deploy/artifact-catalog.dev.json`, and
@@ -128,7 +129,7 @@ report:
```sh
node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json
node scripts/deploy-desired-state-plan.mjs --promotion-commit $(git rev-parse --short=7 origin/main) --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
```
The publish-report mode refuses to update the catalog unless the report says
@@ -177,7 +178,7 @@ Run the local static validator:
node --check scripts/deploy-desired-state-plan.mjs
node --check scripts/src/deploy-desired-state-plan.mjs
node --test scripts/deploy-desired-state-plan.test.mjs
node scripts/deploy-desired-state-plan.mjs --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
node --check scripts/validate-artifact-catalog.mjs
node --check scripts/refresh-artifact-catalog.mjs
node --check scripts/preflight-dev-base-image.mjs
+1 -1
View File
@@ -38,7 +38,7 @@ node --check scripts/dev-deploy-apply.mjs
node --check scripts/src/dev-deploy-apply.mjs
node --check scripts/dev-runtime-migration.mjs
node --check scripts/src/dev-runtime-migration.mjs
node scripts/deploy-desired-state-plan.mjs --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
node scripts/dev-runtime-migration.mjs --check
node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --no-report --expect-blocked
node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report
+5 -4
View File
@@ -21,7 +21,7 @@ node --check scripts/src/deploy-desired-state-plan.mjs
node --check scripts/dev-runtime-migration.mjs
node --check scripts/src/dev-runtime-migration.mjs
node --check scripts/refresh-artifact-catalog.mjs
node scripts/deploy-desired-state-plan.mjs --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
node scripts/dev-runtime-migration.mjs --check
node scripts/dev-gate-preflight.mjs
```
@@ -47,9 +47,10 @@ The preflight checks:
- `deploy/deploy.json` and `deploy/artifact-catalog.dev.json` are internally
consistent and DEV-only.
- `scripts/deploy-desired-state-plan.mjs --check` can be run as a read-only
source support check for deploy commit/image/tag/workload/env mirror
convergence. It does not contact a registry or prove DEV runtime state.
- `scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check` can
be run as a read-only source support check for deploy commit/image/tag/workload
and env mirror convergence. It does not contact a registry or prove DEV
runtime state.
- The deploy manifest, artifact catalog, catalog service commits, and image tags
are covered by the selected `origin/main` target. If `origin/main` is a
report-only evidence commit whose changes do not touch artifact build inputs,
+5 -4
View File
@@ -64,14 +64,14 @@ publish report:
```sh
node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json
node scripts/deploy-desired-state-plan.mjs --promotion-commit $(git rev-parse --short=7 origin/main) --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
```
If publish is blocked, keep the report blocked rather than inventing digests:
```sh
node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked
node scripts/deploy-desired-state-plan.mjs --promotion-commit $(git rev-parse --short=7 origin/main) --check
node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check
```
The desired-state files are a single deployment truth: `deploy/deploy.json`,
@@ -79,8 +79,9 @@ The desired-state files are a single deployment truth: `deploy/deploy.json`,
carry the same promotion commit and immutable image tag before they are used as
apply input. A `--target-ref` desired-state plan may report a uniform older
state as `promotion_pending`; PR review and CI/CD promotion gates must use
`--promotion-commit <sha> --check` so stale or partially refreshed commit,
image, workload, and mirror-env fields are blocked before any DEV apply.
`--target-ref origin/main --check` or `--promotion-commit <sha> --check` so
stale or partially refreshed commit, image, workload, and mirror-env fields are
blocked before any DEV apply.
Reports under `reports/` are evidence snapshots only and cannot override this
desired-state contract.
+3 -3
View File
File diff suppressed because one or more lines are too long
@@ -277,6 +277,18 @@ test("target tag review is read-only promotion_pending when current state is uni
assert.match(plan.services[0].promotion.deployImage, /:def5678$/u);
});
test("target check blocks when current desired-state is uniformly older", async () => {
const repoRoot = await makeFixture();
const plan = await buildDesiredStatePlan({ repoRoot, targetTag: "def5678", requireTargetConvergence: true });
assert.equal(plan.status, "blocked");
assert.equal(plan.target.convergence.state, "target_mismatch");
assert.equal(plan.target.convergenceRequirement, "target-check");
assert.ok(plan.diagnostics.some((diagnostic) =>
diagnostic.code === "target_desired_state_mismatch" &&
diagnostic.message.includes("stale desired-state is not pinned")
));
});
test("promotion commit check blocks when current desired-state is uniformly older", async () => {
const repoRoot = await makeFixture();
const plan = await buildDesiredStatePlan({ repoRoot, promotionCommit: "def5678" });
+27 -10
View File
@@ -87,7 +87,7 @@ function usage() {
"",
"Options:",
" --plan explicitly request the default read-only JSON plan",
" --check exit non-zero when desired-state sources are invalid, drifted, or do not match --promotion-commit",
" --check exit non-zero when desired-state sources are invalid, drifted, or do not match an explicit target",
" --pretty print indented JSON",
" --target-ref REF resolve REF with git and review promotion to its short commit tag",
" --target-tag TAG review promotion to an explicit image tag without proving registry existence",
@@ -467,7 +467,7 @@ function assertTargetTag(ctx, tag, pathName) {
}
}
async function resolveTarget(repoRoot, { targetRef, targetTag, promotionCommit }, ctx) {
async function resolveTarget(repoRoot, { targetRef, targetTag, promotionCommit, requireTargetConvergence = false }, ctx) {
if (!targetRef && !targetTag && !promotionCommit) return null;
let commitId = null;
let shortCommitId = null;
@@ -503,6 +503,7 @@ async function resolveTarget(repoRoot, { targetRef, targetTag, promotionCommit }
if (promotionCommit && commitPattern.test(String(promotionCommit)) && targetTag && targetTag !== promotionCommit.slice(0, 7)) {
addMismatch(ctx, "promotion_tag_mismatch", "--target-tag", "promotion target tag must be the short promotion commit", promotionCommit.slice(0, 7), targetTag);
}
const requireConvergence = Boolean(promotionCommit || requireTargetConvergence);
return {
targetRef,
promotionCommit,
@@ -510,10 +511,18 @@ async function resolveTarget(repoRoot, { targetRef, targetTag, promotionCommit }
shortCommitId,
tag,
tagSource: targetTag ? "target-tag" : promotionCommit ? "promotion-commit-short" : "target-ref-short-commit",
requireConvergence: Boolean(promotionCommit)
requireConvergence,
convergenceRequirement: promotionCommit ? "promotion-commit" : requireTargetConvergence ? "target-check" : "plan-only"
};
}
function targetLabel(target) {
if (target.targetRef && target.tag) return `${target.targetRef} (${target.tag})`;
if (target.promotionCommit) return target.promotionCommit;
if (target.tag) return `tag ${target.tag}`;
return "target";
}
function observationMatchesTarget(observation, target) {
if (!target) return null;
if (observation.kind === "commit") {
@@ -563,16 +572,20 @@ function buildTargetConvergence(ctx, target) {
}
});
} else if (promotionRequired && pending.length > 0) {
const promotionCommitRequired = Boolean(target.promotionCommit);
addDiagnostic(ctx, {
code: "promotion_commit_mismatch",
code: promotionCommitRequired ? "promotion_commit_mismatch" : "target_desired_state_mismatch",
path: "desired-state",
message: "desired-state fields do not match the required promotion commit/tag",
message: promotionCommitRequired
? "desired-state fields do not match the required promotion commit/tag"
: `stale desired-state is not pinned to ${targetLabel(target)}; explicit target checks require current-main/commit-pinned deploy, catalog, workload, and runtime mirror fields`,
expected: {
commitId: target.commitId,
imageTag: target.tag
},
actual: {
pending: pending.length
pending: pending.length,
samplePendingPaths: pending.slice(0, 8).map((item) => item.path)
}
});
}
@@ -582,7 +595,9 @@ function buildTargetConvergence(ctx, target) {
: pending.length === 0
? "already_promoted"
: promotionRequired
? "promotion_mismatch"
? target.promotionCommit
? "promotion_mismatch"
: "target_mismatch"
: "promotion_pending";
return {
state,
@@ -668,7 +683,8 @@ export async function buildDesiredStatePlan(options = {}) {
const target = await resolveTarget(repoRoot, {
targetRef: options.targetRef ?? null,
targetTag: options.targetTag ?? null,
promotionCommit: options.promotionCommit ?? null
promotionCommit: options.promotionCommit ?? null,
requireTargetConvergence: options.requireTargetConvergence === true
}, ctx);
const deployServices = deploy.services ?? [];
@@ -910,7 +926,7 @@ export async function buildDesiredStatePlan(options = {}) {
prod: false,
devLiveClaim: false
},
checkSemantics: "--check exits non-zero for missing/invalid desired-state sources, internal commit/image mirror drift, invalid target tags, partial target drift, or any mismatch with --promotion-commit. A uniform older desired-state under --target-ref alone is a read-only promotion plan, not a check failure.",
checkSemantics: "--check exits non-zero for missing/invalid desired-state sources, internal commit/image mirror drift, invalid target tags, partial target drift, or any mismatch with an explicit --target-ref, --target-tag, or --promotion-commit. Without --check, a uniform older desired-state under --target-ref/--target-tag is a read-only promotion plan.",
summary: {
desiredCommitId,
desiredImageTag,
@@ -966,7 +982,8 @@ export async function runDeployDesiredStatePlanCli(argv = process.argv.slice(2),
repoRoot: options.repoRoot,
targetRef: args.targetRef,
targetTag: args.targetTag,
promotionCommit: args.promotionCommit
promotionCommit: args.promotionCommit,
requireTargetConvergence: args.check && Boolean(args.targetRef || args.targetTag)
});
process.stdout.write(`${JSON.stringify(plan, null, args.pretty ? 2 : 0)}\n`);
if (args.check && plan.status === "blocked") {