feat: add dev artifact publish v2 plan
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"endpoint": "http://74.48.78.17:6667",
|
||||
"commitId": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"artifactState": "contract-skeleton",
|
||||
"publish": {
|
||||
"ciPublished": false,
|
||||
@@ -66,159 +66,378 @@
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-api:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-web:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-mgr:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-worker:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway-simu:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-box-simu:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-patch-panel:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-router:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-tunnel-client:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-edge-proxy:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-cli:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"commitId": "1a2efd4",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-skills:1a2efd4",
|
||||
"imageTag": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"sourceState": "source-present"
|
||||
"sourceState": "source-present",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "publish_not_run"
|
||||
}
|
||||
]
|
||||
],
|
||||
"serviceInventory": {
|
||||
"version": "v2",
|
||||
"serviceCount": 13,
|
||||
"requiredServiceCount": 13,
|
||||
"disabledServiceCount": 0,
|
||||
"requiredServiceIds": [
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-gateway",
|
||||
"hwlab-gateway-simu",
|
||||
"hwlab-box-simu",
|
||||
"hwlab-patch-panel",
|
||||
"hwlab-router",
|
||||
"hwlab-tunnel-client",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills"
|
||||
],
|
||||
"disabledServiceIds": [],
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-cloud-api/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "cloud-web",
|
||||
"implementationState": "static-web-wrapper",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "web/hwlab-cloud-web/index.html",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-mgr/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-worker/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway-simu/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-box-simu/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-patch-panel/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-router/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-tunnel-client/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-edge-proxy/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "cli",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "tools/hwlab-cli/bin/hwlab-cli.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "skills-bundle",
|
||||
"implementationState": "repo-bundle",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "skills/hwlab-agent-runtime/SKILL.md",
|
||||
"disabledReason": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+18
-18
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifestVersion": "v1",
|
||||
"environment": "dev",
|
||||
"commitId": "1a2efd4",
|
||||
"commitId": "35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"endpoint": "http://74.48.78.17:6667",
|
||||
"profiles": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-api:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -30,9 +30,9 @@
|
||||
"env": {
|
||||
"HWLAB_ENVIRONMENT": "dev",
|
||||
"HWLAB_PUBLIC_ENDPOINT": "http://74.48.78.17:6667",
|
||||
"HWLAB_COMMIT_ID": "1a2efd4",
|
||||
"HWLAB_IMAGE": "ghcr.io/pikastech/hwlab-cloud-api:1a2efd4",
|
||||
"HWLAB_IMAGE_TAG": "1a2efd4",
|
||||
"HWLAB_COMMIT_ID": "35ecb5e",
|
||||
"HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"HWLAB_IMAGE_TAG": "35ecb5e",
|
||||
"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": "require",
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-web:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-mgr:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -65,7 +65,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-worker:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -89,7 +89,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway-simu:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -101,7 +101,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"image": "ghcr.io/pikastech/hwlab-box-simu:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"image": "ghcr.io/pikastech/hwlab-patch-panel:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -125,7 +125,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"image": "ghcr.io/pikastech/hwlab-router:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -137,7 +137,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"image": "ghcr.io/pikastech/hwlab-tunnel-client:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -150,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"image": "ghcr.io/pikastech/hwlab-edge-proxy:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -162,7 +162,7 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"image": "ghcr.io/pikastech/hwlab-cli:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
@@ -173,13 +173,13 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-skills:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:35ecb5e",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
"replicas": 1,
|
||||
"env": {
|
||||
"HWLAB_SKILLS_COMMIT_ID": "1a2efd4"
|
||||
"HWLAB_SKILLS_COMMIT_ID": "35ecb5e"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -138,6 +138,19 @@
|
||||
"const": "dev-redacted-presence-only"
|
||||
}
|
||||
}
|
||||
},
|
||||
"publishEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"artifactRequired": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"artifactScope": {
|
||||
"type": "string",
|
||||
"enum": ["required", "disabled"]
|
||||
},
|
||||
"notPublishedReason": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-cloud-api",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-api:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -49,15 +49,15 @@
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_COMMIT_ID",
|
||||
"value": "1a2efd4"
|
||||
"value": "35ecb5e"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_IMAGE",
|
||||
"value": "ghcr.io/pikastech/hwlab-cloud-api:1a2efd4"
|
||||
"value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_IMAGE_TAG",
|
||||
"value": "1a2efd4"
|
||||
"value": "35ecb5e"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN",
|
||||
@@ -129,7 +129,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-cloud-web",
|
||||
"image": "ghcr.io/pikastech/hwlab-cloud-web:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -189,7 +189,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-agent-mgr",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-mgr:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -245,7 +245,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-agent-worker",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-worker:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:35ecb5e",
|
||||
"env": [
|
||||
{
|
||||
"name": "HWLAB_AGENT_SESSION_MODE",
|
||||
@@ -288,7 +288,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-gateway",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -348,7 +348,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-gateway-simu",
|
||||
"image": "ghcr.io/pikastech/hwlab-gateway-simu:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -408,7 +408,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-box-simu",
|
||||
"image": "ghcr.io/pikastech/hwlab-box-simu:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -468,7 +468,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-patch-panel",
|
||||
"image": "ghcr.io/pikastech/hwlab-patch-panel:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -522,7 +522,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-router",
|
||||
"image": "ghcr.io/pikastech/hwlab-router:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -582,7 +582,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-tunnel-client",
|
||||
"image": "ghcr.io/pikastech/hwlab-tunnel-client:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -650,7 +650,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-edge-proxy",
|
||||
"image": "ghcr.io/pikastech/hwlab-edge-proxy:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -711,7 +711,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-cli",
|
||||
"image": "ghcr.io/pikastech/hwlab-cli:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:35ecb5e",
|
||||
"env": [
|
||||
{
|
||||
"name": "HWLAB_CLI_ENDPOINT",
|
||||
@@ -753,7 +753,7 @@
|
||||
"containers": [
|
||||
{
|
||||
"name": "hwlab-agent-skills",
|
||||
"image": "ghcr.io/pikastech/hwlab-agent-skills:1a2efd4",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:35ecb5e",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
@@ -763,7 +763,7 @@
|
||||
"env": [
|
||||
{
|
||||
"name": "HWLAB_SKILLS_COMMIT_ID",
|
||||
"value": "1a2efd4"
|
||||
"value": "35ecb5e"
|
||||
}
|
||||
],
|
||||
"readinessProbe": {
|
||||
|
||||
@@ -11,8 +11,9 @@ declares:
|
||||
tags are aligned, but `ciPublished=false`, `registryVerified=false`, and every
|
||||
digest must stay `not_published`.
|
||||
- `artifactState: "published"` is allowed only after a DEV publish report proves
|
||||
every frozen service was published for the same source commit with a real
|
||||
`sha256:<64 hex>` registry digest.
|
||||
every required enabled service was published for the same source commit with a
|
||||
real `sha256:<64 hex>` registry digest. Services explicitly disabled in the
|
||||
service inventory must stay `not_published` and carry a disabled reason.
|
||||
|
||||
Do not infer registry existence, digest values, or deployment from commit tags.
|
||||
Real publish evidence must come from the publishing workflow and registry
|
||||
@@ -50,13 +51,15 @@ rules:
|
||||
| --- | --- |
|
||||
| `serviceId` | Must be one of the frozen HWLAB service IDs and must appear exactly once. |
|
||||
| `commitId` | Must match the catalog and `deploy/deploy.json` commit ID. Short or full lowercase Git SHA is allowed. |
|
||||
| `image` | Must match `ghcr.io/pikastech/<serviceId>:<imageTag>` and the matching `deploy/deploy.json` service image. |
|
||||
| `image` | Must match the DEV registry target `<registry>/<namespace>/<serviceId>:<imageTag>` and the matching `deploy/deploy.json` service image. |
|
||||
| `imageTag` | Must be the first seven characters of `commitId`. Mutable tags such as `latest`, `dev`, or `main` are forbidden. |
|
||||
| `profile` | Must be `dev`. |
|
||||
| `namespace` | Must be `hwlab-dev`. |
|
||||
| `healthPath` | Must be `/health/live` and match the DEV health contract. |
|
||||
| `publishState` | Must be `skeleton-only` in a blocked skeleton catalog, or `published` only when the catalog was refreshed from a successful publish report. |
|
||||
| `sourceState` | Must be `source-present` for MVP services with repository source, or `intentionally-disabled` for zero-replica services excluded from MVP publication. |
|
||||
| `artifactRequired` | Must match `serviceInventory.requiredServiceIds`; only required services need publish digests. |
|
||||
| `artifactScope` | `required` for services that must be published, or `disabled` with a `notPublishedReason`. |
|
||||
| `digest` | Must be `not_published` in the skeleton catalog; it may be a registry `sha256:<64 hex>` digest only after publish evidence exists. |
|
||||
|
||||
The catalog `commitId` is the artifact source commit, not proof that a registry
|
||||
@@ -86,8 +89,10 @@ node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-rep
|
||||
```
|
||||
|
||||
The publish-report mode refuses to update the catalog unless the report says
|
||||
`artifactPublish.status="published"`, covers every frozen service, matches the
|
||||
target source commit, and contains a valid `sha256` digest for each service.
|
||||
`artifactPublish.status="published"`, covers every frozen service in the v2
|
||||
publish plan, matches the target source commit, and contains a valid `sha256`
|
||||
digest for each required service. Disabled services remain `skeleton-only` with
|
||||
`digest: "not_published"`.
|
||||
|
||||
## Relationship To `deploy/deploy.json`
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ Static check:
|
||||
```sh
|
||||
node --check scripts/preflight-dev-base-image.mjs
|
||||
node --check scripts/src/dev-base-image-preflight.mjs
|
||||
node --check scripts/src/dev-artifact-services.mjs
|
||||
node --check scripts/dev-artifact-publish.mjs
|
||||
node --check scripts/refresh-artifact-catalog.mjs
|
||||
```
|
||||
@@ -75,11 +76,21 @@ Each service record contains:
|
||||
- `image`
|
||||
- `imageTag`
|
||||
- `digest`
|
||||
- `publishEnabled`
|
||||
- `artifactRequired`
|
||||
- `artifactScope`
|
||||
- `notPublishedReason`
|
||||
- `runtimeKind`
|
||||
- `implementationState`
|
||||
- `sourceState`
|
||||
- `entrypoint`
|
||||
|
||||
The report also carries `artifactPublish.serviceInventory` and
|
||||
`artifactPublish.publishPlan`. `serviceInventory` is the resolved frozen service
|
||||
list with enabled/disabled state. `publishPlan` is the machine-readable v2 plan:
|
||||
source commit, image tag, per-service registry target, image reference, digest
|
||||
placeholder or real digest, and the reason a service was not published.
|
||||
|
||||
`digest` is only set to a registry digest after `docker push` succeeds and the
|
||||
push output contains a `sha256:<64 hex>` digest. If the push succeeds but no
|
||||
digest is observable, the service remains `published_unverified_digest`,
|
||||
@@ -108,6 +119,10 @@ recommended `HWLAB_DEV_BASE_IMAGE` value, and preload commands. When that
|
||||
preflight is blocked, artifact publish is blocked and no build or push is
|
||||
attempted.
|
||||
|
||||
If the D601 builder has no approved Node 20 base image, or the selected DEV
|
||||
registry is not reachable, the report must remain `status: "blocked"`. Do not
|
||||
turn a base-image or registry blocker into a fake digest or published state.
|
||||
|
||||
## Known Implementation States
|
||||
|
||||
- `repo-entrypoint`: the repository has `cmd/<serviceId>/main.mjs`.
|
||||
|
||||
@@ -11,7 +11,8 @@ PROD, read secrets, or use UniDesk runtime services as substitutes.
|
||||
|
||||
- No repository Dockerfile is present in the DEV skeleton.
|
||||
- `deploy/deploy.json` and `deploy/artifact-catalog.dev.json` pin service image
|
||||
names to `ghcr.io/pikastech/<hwlab-service-id>:<commit>`.
|
||||
names to the DEV registry target, by default
|
||||
`127.0.0.1:5000/hwlab/<hwlab-service-id>:<commit>`.
|
||||
- The artifact catalog is `contract-skeleton` only. Service entries must keep
|
||||
`publishState: "skeleton-only"` and `digest: "not_published"` until real
|
||||
publish evidence exists.
|
||||
@@ -54,8 +55,8 @@ Explicit base image values must match local `node:20-*`, `hwlab-dev-base:*`, or
|
||||
`hwlab-node20-base:*` references. They must not reference UniDesk, Code Queue,
|
||||
`backend-core`, `provider-gateway`, `microservice-proxy`, or an HWLAB runtime
|
||||
service image. A service image such as
|
||||
`ghcr.io/pikastech/hwlab-cloud-api:<sha>` is an artifact output, not a builder
|
||||
base image.
|
||||
`127.0.0.1:5000/hwlab/hwlab-cloud-api:<sha>` is an artifact output, not a
|
||||
builder base image.
|
||||
|
||||
The recommended D601 preload source is `node:20-bookworm-slim`. The recommended
|
||||
HWLAB-specific local tag is:
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"validate": "node scripts/validate-contract.mjs",
|
||||
"check": "node --check internal/protocol/index.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-agent-mgr/main.mjs && node --check cmd/hwlab-agent-worker/main.mjs && node --check cmd/hwlab-box-simu/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/dev-edge-health-smoke.mjs && node --check scripts/src/dev-edge-health-smoke-lib.mjs && node --check scripts/validate-contract.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-artifact-catalog.mjs && node --check scripts/refresh-artifact-catalog.mjs && node --check scripts/dev-artifact-publish.mjs && node --check scripts/preflight-dev-base-image.mjs && node --check scripts/src/dev-base-image-preflight.mjs && node --check scripts/dev-evidence-blocker-aggregator.mjs && node --check scripts/src/dev-evidence-blocker-aggregator.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/gate-summary.mjs && node --check web/hwlab-cloud-web/scripts/check.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/dev-evidence-blocker-aggregator.mjs --check && node scripts/l2-runtime-contract-smoke.mjs && node scripts/l6-cli-web-smoke.mjs && node web/hwlab-cloud-web/scripts/check.mjs && node --test internal/agent/index.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/server.test.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh",
|
||||
"check": "node --check internal/protocol/index.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-agent-mgr/main.mjs && node --check cmd/hwlab-agent-worker/main.mjs && node --check cmd/hwlab-box-simu/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/dev-edge-health-smoke.mjs && node --check scripts/src/dev-edge-health-smoke-lib.mjs && node --check scripts/validate-contract.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-artifact-catalog.mjs && node --check scripts/refresh-artifact-catalog.mjs && node --check scripts/dev-artifact-publish.mjs && node --check scripts/src/dev-artifact-services.mjs && node --check scripts/preflight-dev-base-image.mjs && node --check scripts/src/dev-base-image-preflight.mjs && node --check scripts/dev-evidence-blocker-aggregator.mjs && node --check scripts/src/dev-evidence-blocker-aggregator.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/gate-summary.mjs && node --check web/hwlab-cloud-web/scripts/check.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/dev-evidence-blocker-aggregator.mjs --check && node scripts/l2-runtime-contract-smoke.mjs && node scripts/l6-cli-web-smoke.mjs && node web/hwlab-cloud-web/scripts/check.mjs && node --test internal/agent/index.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/server.test.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh",
|
||||
"dev-base-image:preflight": "node scripts/preflight-dev-base-image.mjs",
|
||||
"m1:smoke": "node scripts/m1-contract-smoke.mjs",
|
||||
"l2:smoke": "node scripts/l2-runtime-contract-smoke.mjs",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"reportVersion": "v1",
|
||||
"issue": "pikasTech/HWLAB#35",
|
||||
"taskId": "dev-artifact-publish",
|
||||
"commitId": "7e0ccb0",
|
||||
"commitId": "35ecb5e",
|
||||
"acceptanceLevel": "dev_artifact_publish",
|
||||
"devOnly": true,
|
||||
"prodDisabled": true,
|
||||
@@ -21,6 +21,7 @@
|
||||
},
|
||||
"validationCommands": [
|
||||
"node --check scripts/dev-artifact-publish.mjs",
|
||||
"node --check scripts/src/dev-artifact-services.mjs",
|
||||
"node --check scripts/preflight-dev-base-image.mjs",
|
||||
"node --check scripts/src/dev-base-image-preflight.mjs",
|
||||
"node scripts/preflight-dev-base-image.mjs",
|
||||
@@ -72,7 +73,7 @@
|
||||
"status": "blocked",
|
||||
"mode": "preflight",
|
||||
"repo": "pikasTech/HWLAB",
|
||||
"sourceCommitId": "7e0ccb037f5680a21ee454276182da2012693e61",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryPrefix": "127.0.0.1:5000/hwlab",
|
||||
"baseImage": null,
|
||||
"baseImagePreflight": {
|
||||
@@ -176,166 +177,592 @@
|
||||
}
|
||||
},
|
||||
"environment": "dev",
|
||||
"serviceInventory": {
|
||||
"version": "v2",
|
||||
"serviceCount": 13,
|
||||
"requiredServiceCount": 13,
|
||||
"disabledServiceCount": 0,
|
||||
"requiredServiceIds": [
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-gateway",
|
||||
"hwlab-gateway-simu",
|
||||
"hwlab-box-simu",
|
||||
"hwlab-patch-panel",
|
||||
"hwlab-router",
|
||||
"hwlab-tunnel-client",
|
||||
"hwlab-edge-proxy",
|
||||
"hwlab-cli",
|
||||
"hwlab-agent-skills"
|
||||
],
|
||||
"disabledServiceIds": [],
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-cloud-api/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "cloud-web",
|
||||
"implementationState": "static-web-wrapper",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "web/hwlab-cloud-web/index.html",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-mgr/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-worker/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway-simu/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-box-simu/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-patch-panel/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-router/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-tunnel-client/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-edge-proxy/main.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "cli",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "tools/hwlab-cli/bin/hwlab-cli.mjs",
|
||||
"disabledReason": null
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"runtimeKind": "skills-bundle",
|
||||
"implementationState": "repo-bundle",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "skills/hwlab-agent-runtime/SKILL.md",
|
||||
"disabledReason": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"publishPlan": {
|
||||
"version": "v2",
|
||||
"mode": "preflight",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"imageTag": "35ecb5e",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab",
|
||||
"digestPlaceholder": "not_published",
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-api",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-web",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-mgr",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-worker",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway-simu",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-box-simu",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-patch-panel",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-router",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-tunnel-client",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-edge-proxy",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-cli",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"enabled": true,
|
||||
"required": true,
|
||||
"artifactScope": "required",
|
||||
"sourceCommitId": "35ecb5e038b0b06962151f3f1751b72e1d9f6c73",
|
||||
"registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-skills",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"digestStatus": "placeholder",
|
||||
"status": "preflight_only",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
}
|
||||
]
|
||||
},
|
||||
"serviceCount": 13,
|
||||
"requiredServiceCount": 13,
|
||||
"disabledServiceCount": 0,
|
||||
"builtCount": 0,
|
||||
"publishedCount": 0,
|
||||
"generatedAt": "2026-05-21T18:14:22.229Z",
|
||||
"generatedAt": "2026-05-21T18:29:58.488Z",
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-cloud-api/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-cloud-api/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "cloud-web",
|
||||
"implementationState": "static-web-wrapper",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "web/hwlab-cloud-web/index.html"
|
||||
"entrypoint": "web/hwlab-cloud-web/index.html",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-mgr/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-agent-mgr/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-agent-worker/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-agent-worker/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-gateway/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-gateway-simu/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-gateway-simu/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-box-simu/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-box-simu/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-patch-panel/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-patch-panel/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-router/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-router/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-tunnel-client/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-tunnel-client/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "cmd/hwlab-edge-proxy/main.mjs"
|
||||
"entrypoint": "cmd/hwlab-edge-proxy/main.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "cli",
|
||||
"implementationState": "repo-entrypoint",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "tools/hwlab-cli/bin/hwlab-cli.mjs"
|
||||
"entrypoint": "tools/hwlab-cli/bin/hwlab-cli.mjs",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:7e0ccb0",
|
||||
"imageTag": "7e0ccb0",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:35ecb5e",
|
||||
"imageTag": "35ecb5e",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "skills-bundle",
|
||||
"implementationState": "repo-bundle",
|
||||
"sourceState": "source-present",
|
||||
"entrypoint": "skills/hwlab-agent-runtime/SKILL.md"
|
||||
"entrypoint": "skills/hwlab-agent-runtime/SKILL.md",
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": "blocked_by_preflight"
|
||||
}
|
||||
],
|
||||
"blockers": [
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { constants as fsConstants } from "node:fs";
|
||||
import {
|
||||
access,
|
||||
mkdir,
|
||||
readFile,
|
||||
writeFile
|
||||
@@ -12,6 +10,11 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
|
||||
import {
|
||||
BUILDABLE_IMPLEMENTATION_STATES,
|
||||
resolveDevArtifactServices,
|
||||
serviceInventoryFromServices
|
||||
} from "./src/dev-artifact-services.mjs";
|
||||
import { runDevBaseImagePreflight } from "./src/dev-base-image-preflight.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -29,11 +32,7 @@ const fatalBlockerTypes = new Set([
|
||||
"network_blocker",
|
||||
"safety_blocker"
|
||||
]);
|
||||
const buildableImplementationStates = new Set([
|
||||
"repo-entrypoint",
|
||||
"static-web-wrapper",
|
||||
"repo-bundle"
|
||||
]);
|
||||
const buildableImplementationStates = new Set(BUILDABLE_IMPLEMENTATION_STATES);
|
||||
const sourceStates = new Set([
|
||||
"source-present",
|
||||
"intentionally-disabled"
|
||||
@@ -109,15 +108,6 @@ async function readJson(relativePath) {
|
||||
return JSON.parse(await readFile(path.join(repoRoot, relativePath), "utf8"));
|
||||
}
|
||||
|
||||
async function pathExists(relativePath) {
|
||||
try {
|
||||
await access(path.join(repoRoot, relativePath), fsConstants.R_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function emit(event, payload = {}) {
|
||||
process.stderr.write(`${JSON.stringify({ event, ...payload })}\n`);
|
||||
}
|
||||
@@ -289,71 +279,8 @@ function repoLabelFromRemote(remoteUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function serviceSourceState(catalog, serviceId) {
|
||||
return catalog?.services?.find((service) => service.serviceId === serviceId)?.sourceState ?? "source-present";
|
||||
}
|
||||
|
||||
async function resolveService(serviceId, catalog) {
|
||||
const sourceState = serviceSourceState(catalog, serviceId);
|
||||
const cmdPath = `cmd/${serviceId}/main.mjs`;
|
||||
if (await pathExists(cmdPath)) {
|
||||
return {
|
||||
serviceId,
|
||||
runtimeKind: "node-command",
|
||||
entrypoint: cmdPath,
|
||||
implementationState: "repo-entrypoint",
|
||||
sourceState
|
||||
};
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-cloud-web") {
|
||||
return {
|
||||
serviceId,
|
||||
runtimeKind: "cloud-web",
|
||||
entrypoint: "web/hwlab-cloud-web/index.html",
|
||||
implementationState: "static-web-wrapper",
|
||||
sourceState
|
||||
};
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-cli") {
|
||||
const hasBin = await pathExists("tools/hwlab-cli/bin/hwlab-cli.mjs");
|
||||
return {
|
||||
serviceId,
|
||||
runtimeKind: "cli",
|
||||
entrypoint: hasBin ? "tools/hwlab-cli/bin/hwlab-cli.mjs" : "tools/hwlab-cli/lib/cli.mjs",
|
||||
implementationState: hasBin ? "repo-entrypoint" : "library-only",
|
||||
sourceState
|
||||
};
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-agent-skills") {
|
||||
return {
|
||||
serviceId,
|
||||
runtimeKind: "skills-bundle",
|
||||
entrypoint: "skills/hwlab-agent-runtime/SKILL.md",
|
||||
implementationState: "repo-bundle",
|
||||
sourceState
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
serviceId,
|
||||
runtimeKind: "health-placeholder",
|
||||
entrypoint: null,
|
||||
implementationState: "missing-runtime-entrypoint",
|
||||
sourceState
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveServices(serviceIds, catalog) {
|
||||
const unknown = serviceIds.filter((serviceId) => !SERVICE_IDS.includes(serviceId));
|
||||
assert.deepEqual(unknown, [], `unknown service IDs: ${unknown.join(", ")}`);
|
||||
const services = [];
|
||||
for (const serviceId of serviceIds) {
|
||||
services.push(await resolveService(serviceId, catalog));
|
||||
}
|
||||
return services;
|
||||
return resolveDevArtifactServices(repoRoot, serviceIds, SERVICE_IDS, catalog);
|
||||
}
|
||||
|
||||
function runtimeScriptBase64() {
|
||||
@@ -605,7 +532,7 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre
|
||||
next: `Keep ${service.serviceId} out of the MVP artifact catalog, or add a source entrypoint before publishing it.`
|
||||
})
|
||||
);
|
||||
} else if (service.implementationState === "missing-runtime-entrypoint") {
|
||||
} else if (service.artifactRequired && service.implementationState === "missing-runtime-entrypoint") {
|
||||
blockers.push(
|
||||
blocker({
|
||||
type: "runtime_blocker",
|
||||
@@ -808,11 +735,12 @@ function repositoryFromImageRef(image) {
|
||||
}
|
||||
|
||||
function reportStatus(mode, artifacts, blockers) {
|
||||
const requiredArtifacts = artifacts.filter((artifact) => artifact.artifactRequired);
|
||||
if (mode === "preflight") return blockers.length ? "blocked" : "pass";
|
||||
if (artifacts.some((artifact) => artifact.status === "publish_failed" || artifact.status === "build_failed")) return "failed";
|
||||
if (artifacts.some((artifact) => artifact.status.startsWith("blocked_"))) return "blocked";
|
||||
if (mode === "publish" && artifacts.every((artifact) => artifact.status === "published")) return "published";
|
||||
if (mode === "build" && artifacts.every((artifact) => artifact.status === "built")) return "built";
|
||||
if (requiredArtifacts.some((artifact) => artifact.status === "publish_failed" || artifact.status === "build_failed")) return "failed";
|
||||
if (requiredArtifacts.some((artifact) => artifact.status.startsWith("blocked_"))) return "blocked";
|
||||
if (mode === "publish" && requiredArtifacts.length > 0 && requiredArtifacts.every((artifact) => artifact.status === "published")) return "published";
|
||||
if (mode === "build" && requiredArtifacts.length > 0 && requiredArtifacts.every((artifact) => artifact.status === "built")) return "built";
|
||||
return blockers.length ? "blocked" : "pass";
|
||||
}
|
||||
|
||||
@@ -833,11 +761,66 @@ function devGateBlockers(blockers) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function createReport({ args, repo, commitId, shortCommit, mode, artifacts, blockers, baseImagePreflight }) {
|
||||
function artifactNotPublishedReason(artifact, mode, fatalBlocked) {
|
||||
if (/^sha256:[a-f0-9]{64}$/u.test(artifact.digest ?? "")) return null;
|
||||
if (!artifact.artifactRequired) return artifact.notPublishedReason;
|
||||
if (artifact.status === "preflight_only") return fatalBlocked ? "blocked_by_preflight" : "preflight_only";
|
||||
if (artifact.status === "built") return "build_only_not_published";
|
||||
if (artifact.status === "build_failed") return "build_failed";
|
||||
if (artifact.status === "publish_failed") return "publish_failed";
|
||||
if (artifact.status === "published_unverified_digest") return "digest_unverified";
|
||||
return artifact.notPublishedReason ?? "publish_not_run";
|
||||
}
|
||||
|
||||
function artifactRecord({ args, service, shortCommit, status, digest = "not_published" }) {
|
||||
return {
|
||||
...service,
|
||||
status,
|
||||
image: imageRef(args.registryPrefix, service.serviceId, shortCommit),
|
||||
imageTag: shortCommit,
|
||||
digest,
|
||||
repositoryDigest: null
|
||||
};
|
||||
}
|
||||
|
||||
function publishPlanFor({ args, commitId, shortCommit, mode, artifacts, fatalBlocked }) {
|
||||
return {
|
||||
version: "v2",
|
||||
mode,
|
||||
sourceCommitId: commitId,
|
||||
imageTag: shortCommit,
|
||||
registryTarget: args.registryPrefix,
|
||||
digestPlaceholder: "not_published",
|
||||
services: artifacts.map((artifact) => {
|
||||
const image = artifact.image ?? imageRef(args.registryPrefix, artifact.serviceId, shortCommit);
|
||||
const digest = artifact.digest ?? "not_published";
|
||||
return {
|
||||
serviceId: artifact.serviceId,
|
||||
enabled: artifact.publishEnabled,
|
||||
required: artifact.artifactRequired,
|
||||
artifactScope: artifact.artifactScope,
|
||||
sourceCommitId: commitId,
|
||||
registryTarget: imageRepository(args.registryPrefix, artifact.serviceId),
|
||||
image,
|
||||
imageTag: artifact.imageTag ?? shortCommit,
|
||||
digest,
|
||||
repositoryDigest: artifact.repositoryDigest ?? null,
|
||||
digestStatus: /^sha256:[a-f0-9]{64}$/u.test(digest) ? "real" : "placeholder",
|
||||
status: artifact.status,
|
||||
notPublishedReason: artifactNotPublishedReason(artifact, mode, fatalBlocked)
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function createReport({ args, repo, commitId, shortCommit, mode, services, artifacts, blockers, baseImagePreflight }) {
|
||||
const status = reportStatus(mode, artifacts, blockers);
|
||||
const fatalBlocked = hasFatalBlocker(blockers);
|
||||
const publishedCount = artifacts.filter((artifact) => artifact.status === "published").length;
|
||||
const builtCount = artifacts.filter((artifact) => ["built", "published", "published_unverified_digest"].includes(artifact.status)).length;
|
||||
const requiredArtifacts = artifacts.filter((artifact) => artifact.artifactRequired);
|
||||
const serviceInventory = serviceInventoryFromServices(services);
|
||||
const publishPlan = publishPlanFor({ args, commitId, shortCommit, mode, artifacts, fatalBlocked });
|
||||
const publishedCount = requiredArtifacts.filter((artifact) => artifact.status === "published").length;
|
||||
const builtCount = requiredArtifacts.filter((artifact) => ["built", "published", "published_unverified_digest"].includes(artifact.status)).length;
|
||||
|
||||
return {
|
||||
$schema: "https://hwlab.pikastech.local/schemas/dev-artifact-publish.schema.json",
|
||||
@@ -862,6 +845,7 @@ function createReport({ args, repo, commitId, shortCommit, mode, artifacts, bloc
|
||||
},
|
||||
validationCommands: [
|
||||
"node --check scripts/dev-artifact-publish.mjs",
|
||||
"node --check scripts/src/dev-artifact-services.mjs",
|
||||
"node --check scripts/preflight-dev-base-image.mjs",
|
||||
"node --check scripts/src/dev-base-image-preflight.mjs",
|
||||
"node scripts/preflight-dev-base-image.mjs",
|
||||
@@ -913,7 +897,11 @@ function createReport({ args, repo, commitId, shortCommit, mode, artifacts, bloc
|
||||
baseImage: args.baseImage ?? null,
|
||||
baseImagePreflight: summarizeBaseImagePreflight(baseImagePreflight),
|
||||
environment: ENVIRONMENT_DEV,
|
||||
serviceInventory,
|
||||
publishPlan,
|
||||
serviceCount: artifacts.length,
|
||||
requiredServiceCount: serviceInventory.requiredServiceCount,
|
||||
disabledServiceCount: serviceInventory.disabledServiceCount,
|
||||
builtCount,
|
||||
publishedCount,
|
||||
generatedAt: new Date().toISOString(),
|
||||
@@ -928,6 +916,10 @@ function createReport({ args, repo, commitId, shortCommit, mode, artifacts, bloc
|
||||
implementationState: artifact.implementationState,
|
||||
sourceState: artifact.sourceState,
|
||||
entrypoint: artifact.entrypoint,
|
||||
publishEnabled: artifact.publishEnabled,
|
||||
artifactRequired: artifact.artifactRequired,
|
||||
artifactScope: artifact.artifactScope,
|
||||
notPublishedReason: artifactNotPublishedReason(artifact, mode, fatalBlocked),
|
||||
localImageId: artifact.localImageId
|
||||
})),
|
||||
blockers
|
||||
@@ -967,12 +959,11 @@ async function main() {
|
||||
const services = await resolveServices(args.services, catalog);
|
||||
|
||||
let blockers = await preflight({ args, services, catalog, deployManifest, baseImagePreflight });
|
||||
let artifacts = services.map((service) => ({
|
||||
...service,
|
||||
status: "preflight_only",
|
||||
image: imageRef(args.registryPrefix, service.serviceId, shortCommit),
|
||||
imageTag: shortCommit,
|
||||
digest: "not_published"
|
||||
let artifacts = services.map((service) => artifactRecord({
|
||||
args,
|
||||
service,
|
||||
shortCommit,
|
||||
status: service.artifactRequired ? "preflight_only" : "not_required"
|
||||
}));
|
||||
|
||||
if ((args.mode === "build" || args.mode === "publish") && hasFatalBlocker(blockers)) {
|
||||
@@ -980,18 +971,18 @@ async function main() {
|
||||
fatalBlockers: blockers.filter((item) => fatalBlockerTypes.has(item.type)).map((item) => item.scope)
|
||||
});
|
||||
} else if (args.mode === "build" || args.mode === "publish") {
|
||||
artifacts = [];
|
||||
for (const service of services) {
|
||||
const artifactByServiceId = new Map(artifacts.map((artifact) => [artifact.serviceId, artifact]));
|
||||
for (const service of services.filter((item) => item.artifactRequired)) {
|
||||
emit("build_start", { serviceId: service.serviceId, image: imageRef(args.registryPrefix, service.serviceId, shortCommit) });
|
||||
const artifact = await buildService({ args, repo, commitId, shortCommit, service });
|
||||
artifacts.push(artifact);
|
||||
artifactByServiceId.set(service.serviceId, artifact);
|
||||
if (artifact.blocker) blockers.push(artifact.blocker);
|
||||
emit("build_done", { serviceId: service.serviceId, status: artifact.status, image: artifact.image });
|
||||
|
||||
if (args.mode === "publish" && artifact.status === "built") {
|
||||
emit("publish_start", { serviceId: service.serviceId, image: artifact.image });
|
||||
const published = await publishService(artifact);
|
||||
artifacts[artifacts.length - 1] = published;
|
||||
artifactByServiceId.set(service.serviceId, published);
|
||||
if (published.blocker) blockers.push(published.blocker);
|
||||
emit("publish_done", {
|
||||
serviceId: service.serviceId,
|
||||
@@ -1001,9 +992,10 @@ async function main() {
|
||||
});
|
||||
}
|
||||
}
|
||||
artifacts = services.map((service) => artifactByServiceId.get(service.serviceId));
|
||||
}
|
||||
|
||||
const report = createReport({ args, repo, commitId, shortCommit, mode: args.mode, artifacts, blockers, baseImagePreflight });
|
||||
const report = createReport({ args, repo, commitId, shortCommit, mode: args.mode, services, artifacts, blockers, baseImagePreflight });
|
||||
if (args.emitReport) {
|
||||
await writeReport(args.reportPath, report);
|
||||
}
|
||||
@@ -1015,11 +1007,13 @@ async function main() {
|
||||
sourceCommitId: commitId,
|
||||
registryPrefix: args.registryPrefix,
|
||||
baseImagePreflight: summarizeBaseImagePreflight(baseImagePreflight),
|
||||
publishPlan: report.artifactPublish.publishPlan,
|
||||
services: report.artifactPublish.services.map((service) => ({
|
||||
serviceId: service.serviceId,
|
||||
status: service.status,
|
||||
image: service.image,
|
||||
digest: service.digest
|
||||
digest: service.digest,
|
||||
notPublishedReason: service.notPublishedReason
|
||||
})),
|
||||
blockers
|
||||
}, null, 2));
|
||||
|
||||
@@ -7,6 +7,10 @@ import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
|
||||
import {
|
||||
resolveDevArtifactServices,
|
||||
serviceInventoryFromServices
|
||||
} from "./src/dev-artifact-services.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -14,6 +18,7 @@ const catalogPath = "deploy/artifact-catalog.dev.json";
|
||||
const deployPath = "deploy/deploy.json";
|
||||
const workloadsPath = "deploy/k8s/base/workloads.yaml";
|
||||
const defaultPublishReportPath = "reports/dev-gate/dev-artifacts.json";
|
||||
const defaultRegistryPrefix = process.env.HWLAB_DEV_REGISTRY_PREFIX || process.env.HWLAB_DEV_REGISTRY || "127.0.0.1:5000/hwlab";
|
||||
const digestPattern = /^sha256:[a-f0-9]{64}$/;
|
||||
const commitPattern = /^[a-f0-9]{7,40}$/;
|
||||
|
||||
@@ -107,8 +112,8 @@ function parseTaggedImage(image, context) {
|
||||
};
|
||||
}
|
||||
|
||||
function targetImage(serviceId, shortCommitId) {
|
||||
return `ghcr.io/pikastech/${serviceId}:${shortCommitId}`;
|
||||
function targetImage(serviceId, shortCommitId, registryPrefix = defaultRegistryPrefix) {
|
||||
return `${registryPrefix.replace(/\/+$/u, "")}/${serviceId}:${shortCommitId}`;
|
||||
}
|
||||
|
||||
function commitMatchesTarget(value, target) {
|
||||
@@ -137,16 +142,42 @@ function publishRecordsFromReport(report, target) {
|
||||
assert.ok(artifactPublish && typeof artifactPublish === "object", "publish report missing artifactPublish");
|
||||
assert.equal(artifactPublish.status, "published", "publish report artifactPublish.status must be published");
|
||||
assert.ok(commitMatchesTarget(artifactPublish.sourceCommitId, target), "publish report sourceCommitId must match target ref");
|
||||
assert.equal(artifactPublish.publishedCount, SERVICE_IDS.length, "publish report must publish every frozen service");
|
||||
assert.equal(artifactPublish.serviceCount, SERVICE_IDS.length, "publish report must cover every frozen service");
|
||||
assert.ok(artifactPublish.publishPlan && typeof artifactPublish.publishPlan === "object", "publish report missing artifactPublish.publishPlan");
|
||||
assert.equal(artifactPublish.publishPlan.version, "v2", "publish report plan version must be v2");
|
||||
|
||||
const requiredServiceIds = artifactPublish.publishPlan.services
|
||||
.filter((service) => service.required)
|
||||
.map((service) => service.serviceId);
|
||||
const disabledServiceIds = artifactPublish.publishPlan.services
|
||||
.filter((service) => !service.required)
|
||||
.map((service) => service.serviceId);
|
||||
assert.deepEqual(
|
||||
[...requiredServiceIds, ...disabledServiceIds].sort(),
|
||||
[...SERVICE_IDS].sort(),
|
||||
"publish plan must cover every frozen service"
|
||||
);
|
||||
assert.equal(
|
||||
artifactPublish.publishedCount,
|
||||
requiredServiceIds.length,
|
||||
"publish report must publish every required enabled service"
|
||||
);
|
||||
|
||||
const records = new Map();
|
||||
for (const service of artifactPublish.services ?? []) {
|
||||
assert.ok(SERVICE_IDS.includes(service.serviceId), `unknown service ${service.serviceId} in publish report`);
|
||||
assert.equal(service.status, "published", `${service.serviceId} status must be published`);
|
||||
assert.match(service.digest, digestPattern, `${service.serviceId} digest must be a sha256 registry digest`);
|
||||
const image = parseTaggedImage(service.image, service.serviceId);
|
||||
assert.equal(image.tag, target.shortCommitId, `${service.serviceId} image tag must match target short commit`);
|
||||
if (!requiredServiceIds.includes(service.serviceId)) {
|
||||
assert.equal(service.artifactRequired, false, `${service.serviceId} disabled service must not be required`);
|
||||
assert.equal(service.digest, "not_published", `${service.serviceId} disabled digest must stay not_published`);
|
||||
assert.equal(service.notPublishedReason?.startsWith("disabled_"), true, `${service.serviceId} disabled service must state a disabled reason`);
|
||||
continue;
|
||||
}
|
||||
|
||||
assert.equal(service.status, "published", `${service.serviceId} status must be published`);
|
||||
assert.equal(service.artifactRequired, true, `${service.serviceId} required service must state artifactRequired=true`);
|
||||
assert.match(service.digest, digestPattern, `${service.serviceId} digest must be a sha256 registry digest`);
|
||||
records.set(service.serviceId, {
|
||||
serviceId: service.serviceId,
|
||||
image: service.image,
|
||||
@@ -156,8 +187,8 @@ function publishRecordsFromReport(report, target) {
|
||||
});
|
||||
}
|
||||
|
||||
assert.deepEqual([...records.keys()], SERVICE_IDS, "publish report services must cover frozen service IDs in order");
|
||||
return records;
|
||||
assert.deepEqual([...records.keys()], requiredServiceIds, "publish report services must publish required service IDs in order");
|
||||
return { records, requiredServiceIds, disabledServiceIds };
|
||||
}
|
||||
|
||||
function updateEnvObject(env, service, target, publishRecord) {
|
||||
@@ -192,15 +223,19 @@ function workloadContainers(item) {
|
||||
return item?.spec?.template?.spec?.containers ?? [];
|
||||
}
|
||||
|
||||
function refreshDocuments({ deploy, catalog, workloads, target, publishRecords, provenancePath }) {
|
||||
function refreshDocuments({ deploy, catalog, workloads, target, publishRecords, serviceInventory, provenancePath, registryPrefix }) {
|
||||
const published = Boolean(publishRecords);
|
||||
const records = publishRecords?.records ?? null;
|
||||
const requiredIds = new Set(publishRecords?.requiredServiceIds ?? serviceInventory.requiredServiceIds);
|
||||
const catalogByService = new Map(catalog.services.map((service) => [service.serviceId, service]));
|
||||
const deployByService = new Map(deploy.services.map((service) => [service.serviceId, service]));
|
||||
const inventoryByService = new Map(serviceInventory.services.map((service) => [service.serviceId, service]));
|
||||
const refreshedServices = [];
|
||||
|
||||
deploy.commitId = target.shortCommitId;
|
||||
catalog.commitId = target.shortCommitId;
|
||||
catalog.artifactState = published ? "published" : "contract-skeleton";
|
||||
catalog.serviceInventory = serviceInventory;
|
||||
catalog.publish = {
|
||||
...catalog.publish,
|
||||
ciPublished: published,
|
||||
@@ -217,8 +252,10 @@ function refreshDocuments({ deploy, catalog, workloads, target, publishRecords,
|
||||
assert.ok(deployService, `${serviceId} missing from deploy manifest`);
|
||||
assert.ok(catalogService, `${serviceId} missing from catalog`);
|
||||
|
||||
const publishRecord = publishRecords?.get(serviceId) ?? null;
|
||||
const image = publishRecord?.image ?? targetImage(serviceId, target.shortCommitId);
|
||||
const inventory = inventoryByService.get(serviceId);
|
||||
const required = requiredIds.has(serviceId);
|
||||
const publishRecord = records?.get(serviceId) ?? null;
|
||||
const image = publishRecord?.image ?? targetImage(serviceId, target.shortCommitId, registryPrefix);
|
||||
const imageTag = publishRecord?.imageTag ?? target.shortCommitId;
|
||||
|
||||
deployService.image = image;
|
||||
@@ -228,13 +265,21 @@ function refreshDocuments({ deploy, catalog, workloads, target, publishRecords,
|
||||
catalogService.image = image;
|
||||
catalogService.imageTag = imageTag;
|
||||
catalogService.digest = publishRecord?.digest ?? "not_published";
|
||||
catalogService.publishState = published ? "published" : "skeleton-only";
|
||||
catalogService.publishState = publishRecord ? "published" : "skeleton-only";
|
||||
catalogService.publishEnabled = inventory?.publishEnabled ?? required;
|
||||
catalogService.artifactRequired = required;
|
||||
catalogService.artifactScope = required ? "required" : "disabled";
|
||||
catalogService.notPublishedReason = publishRecord
|
||||
? null
|
||||
: inventory?.disabledReason ?? (published ? "not_required" : "publish_not_run");
|
||||
refreshedServices.push({
|
||||
serviceId,
|
||||
image,
|
||||
imageTag,
|
||||
digest: catalogService.digest,
|
||||
publishState: catalogService.publishState
|
||||
publishState: catalogService.publishState,
|
||||
artifactRequired: catalogService.artifactRequired,
|
||||
notPublishedReason: catalogService.notPublishedReason
|
||||
});
|
||||
}
|
||||
|
||||
@@ -268,13 +313,18 @@ async function main() {
|
||||
assertDevOnlyDeployAndCatalog(deploy, catalog);
|
||||
|
||||
const publishRecords = publishReport ? publishRecordsFromReport(publishReport, target) : null;
|
||||
const serviceInventory = publishReport?.artifactPublish?.serviceInventory ?? serviceInventoryFromServices(
|
||||
await resolveDevArtifactServices(repoRoot, SERVICE_IDS, SERVICE_IDS)
|
||||
);
|
||||
const services = refreshDocuments({
|
||||
deploy,
|
||||
catalog,
|
||||
workloads,
|
||||
target,
|
||||
publishRecords,
|
||||
provenancePath: args.publishReportPath ?? defaultPublishReportPath
|
||||
serviceInventory,
|
||||
provenancePath: args.publishReportPath ?? defaultPublishReportPath,
|
||||
registryPrefix: publishReport?.artifactPublish?.registryPrefix ?? defaultRegistryPrefix
|
||||
});
|
||||
|
||||
if (args.write) {
|
||||
@@ -293,10 +343,12 @@ async function main() {
|
||||
wrote: args.write ? [deployPath, catalogPath, workloadsPath] : [],
|
||||
ciPublished: Boolean(publishRecords),
|
||||
registryVerified: Boolean(publishRecords),
|
||||
publishedCount: publishRecords?.size ?? 0,
|
||||
notPublishedCount: publishRecords ? 0 : services.length,
|
||||
publishedCount: publishRecords?.records.size ?? 0,
|
||||
requiredServiceCount: serviceInventory.requiredServiceCount,
|
||||
disabledServiceCount: serviceInventory.disabledServiceCount,
|
||||
notPublishedCount: services.filter((service) => service.digest === "not_published").length,
|
||||
digestPolicy: publishRecords
|
||||
? "catalog digests copied only from a published DEV artifact report"
|
||||
? "catalog digests copied only for required services from a published DEV artifact report; disabled services stay not_published"
|
||||
: "all catalog digests remain not_published",
|
||||
services
|
||||
}, null, 2));
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import { constants as fsConstants } from "node:fs";
|
||||
import { access } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
export const BUILDABLE_IMPLEMENTATION_STATES = Object.freeze([
|
||||
"repo-entrypoint",
|
||||
"static-web-wrapper",
|
||||
"repo-bundle"
|
||||
]);
|
||||
|
||||
const buildableImplementationStates = new Set(BUILDABLE_IMPLEMENTATION_STATES);
|
||||
|
||||
async function pathExists(repoRoot, relativePath) {
|
||||
try {
|
||||
await access(path.join(repoRoot, relativePath), fsConstants.R_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function publishEnabledForService(service) {
|
||||
return service.sourceState !== "intentionally-disabled" &&
|
||||
buildableImplementationStates.has(service.implementationState);
|
||||
}
|
||||
|
||||
export function notPublishedReasonForService(service) {
|
||||
if (publishEnabledForService(service)) {
|
||||
return "publish_not_run";
|
||||
}
|
||||
if (service.sourceState === "intentionally-disabled") {
|
||||
return "disabled_intentionally";
|
||||
}
|
||||
if (service.implementationState === "missing-runtime-entrypoint") {
|
||||
return "disabled_missing_runtime_entrypoint";
|
||||
}
|
||||
if (service.implementationState === "library-only") {
|
||||
return "disabled_missing_executable_entrypoint";
|
||||
}
|
||||
return `disabled_${service.implementationState}`;
|
||||
}
|
||||
|
||||
export function servicePublishPolicy(service) {
|
||||
const publishEnabled = publishEnabledForService(service);
|
||||
return {
|
||||
publishEnabled,
|
||||
artifactRequired: publishEnabled,
|
||||
artifactScope: publishEnabled ? "required" : "disabled",
|
||||
notPublishedReason: publishEnabled ? "publish_not_run" : notPublishedReasonForService(service)
|
||||
};
|
||||
}
|
||||
|
||||
export function withServicePublishPolicy(service) {
|
||||
return {
|
||||
...service,
|
||||
...servicePublishPolicy(service)
|
||||
};
|
||||
}
|
||||
|
||||
function serviceSourceState(catalog, serviceId) {
|
||||
return catalog?.services?.find((service) => service.serviceId === serviceId)?.sourceState ?? "source-present";
|
||||
}
|
||||
|
||||
export async function resolveDevArtifactService(repoRoot, serviceId, catalog = null) {
|
||||
const sourceState = serviceSourceState(catalog, serviceId);
|
||||
const cmdPath = `cmd/${serviceId}/main.mjs`;
|
||||
if (await pathExists(repoRoot, cmdPath)) {
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "node-command",
|
||||
entrypoint: cmdPath,
|
||||
implementationState: "repo-entrypoint",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-cloud-web") {
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "cloud-web",
|
||||
entrypoint: "web/hwlab-cloud-web/index.html",
|
||||
implementationState: "static-web-wrapper",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-cli") {
|
||||
const hasBin = await pathExists(repoRoot, "tools/hwlab-cli/bin/hwlab-cli.mjs");
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "cli",
|
||||
entrypoint: hasBin ? "tools/hwlab-cli/bin/hwlab-cli.mjs" : "tools/hwlab-cli/lib/cli.mjs",
|
||||
implementationState: hasBin ? "repo-entrypoint" : "library-only",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
if (serviceId === "hwlab-agent-skills") {
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "skills-bundle",
|
||||
entrypoint: "skills/hwlab-agent-runtime/SKILL.md",
|
||||
implementationState: "repo-bundle",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "health-placeholder",
|
||||
entrypoint: null,
|
||||
implementationState: "missing-runtime-entrypoint",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
export async function resolveDevArtifactServices(repoRoot, serviceIds, frozenServiceIds, catalog = null) {
|
||||
const unknown = serviceIds.filter((serviceId) => !frozenServiceIds.includes(serviceId));
|
||||
if (unknown.length > 0) {
|
||||
throw new Error(`unknown service IDs: ${unknown.join(", ")}`);
|
||||
}
|
||||
|
||||
const services = [];
|
||||
for (const serviceId of serviceIds) {
|
||||
services.push(await resolveDevArtifactService(repoRoot, serviceId, catalog));
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
export function serviceInventoryFromServices(services) {
|
||||
const requiredServiceIds = services
|
||||
.filter((service) => service.artifactRequired)
|
||||
.map((service) => service.serviceId);
|
||||
const disabledServiceIds = services
|
||||
.filter((service) => !service.artifactRequired)
|
||||
.map((service) => service.serviceId);
|
||||
|
||||
return {
|
||||
version: "v2",
|
||||
serviceCount: services.length,
|
||||
requiredServiceCount: requiredServiceIds.length,
|
||||
disabledServiceCount: disabledServiceIds.length,
|
||||
requiredServiceIds,
|
||||
disabledServiceIds,
|
||||
services: services.map((service) => ({
|
||||
serviceId: service.serviceId,
|
||||
publishEnabled: service.publishEnabled,
|
||||
artifactRequired: service.artifactRequired,
|
||||
artifactScope: service.artifactScope,
|
||||
runtimeKind: service.runtimeKind,
|
||||
implementationState: service.implementationState,
|
||||
sourceState: service.sourceState,
|
||||
entrypoint: service.entrypoint,
|
||||
disabledReason: service.artifactRequired ? null : service.notPublishedReason
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -170,6 +170,7 @@ function buildServicePlan(services) {
|
||||
}
|
||||
|
||||
function buildArtifactPlan(deploy, catalog, sourceCommitId, artifactEvidence) {
|
||||
const requiredServices = (catalog?.services ?? []).filter((service) => service.artifactRequired !== false);
|
||||
const serviceCommitIds = uniqueStrings([
|
||||
...(catalog?.services ?? []).map((service) => service.commitId),
|
||||
...(artifactEvidence ?? []).map((service) => service.catalogCommitId)
|
||||
@@ -188,8 +189,10 @@ function buildArtifactPlan(deploy, catalog, sourceCommitId, artifactEvidence) {
|
||||
published: catalog?.publish?.ciPublished === true,
|
||||
registryVerified: catalog?.publish?.registryVerified === true,
|
||||
imageCount: artifactEvidence.length,
|
||||
unpublishedServices: (catalog?.services ?? [])
|
||||
.filter((service) => service.publishState === "skeleton-only" || !service.digest?.startsWith("sha256:"))
|
||||
requiredServiceCount: requiredServices.length,
|
||||
disabledServiceCount: (catalog?.services ?? []).filter((service) => service.artifactRequired === false).length,
|
||||
unpublishedServices: requiredServices
|
||||
.filter((service) => service.publishState !== "published" || !service.digest?.startsWith("sha256:"))
|
||||
.map((service) => service.serviceId)
|
||||
};
|
||||
}
|
||||
@@ -359,18 +362,19 @@ function validateDeployAndCatalog(deploy, catalog, sourceCommitId, blockers) {
|
||||
});
|
||||
}
|
||||
|
||||
const requiredServices = (catalog.services ?? []).filter((service) => service.artifactRequired !== false);
|
||||
const disabledServices = (catalog.services ?? []).filter((service) => service.artifactRequired === false);
|
||||
const hasPublishedCatalog =
|
||||
catalog.publish?.ciPublished === true &&
|
||||
catalog.publish?.registryVerified === true &&
|
||||
catalog.services?.every(
|
||||
(service) => service.digest?.startsWith("sha256:") && service.publishState !== "skeleton-only"
|
||||
);
|
||||
requiredServices.every((service) => service.digest?.startsWith("sha256:") && service.publishState === "published") &&
|
||||
disabledServices.every((service) => service.digest === "not_published" && service.publishState === "skeleton-only");
|
||||
if (!hasPublishedCatalog) {
|
||||
addBlocker(
|
||||
blockers,
|
||||
"observability_blocker",
|
||||
"artifact-publish",
|
||||
"DEV artifact catalog has no CI publish, registry verification, or registry digests"
|
||||
"DEV artifact catalog has no CI publish, registry verification, or required-service registry digests"
|
||||
);
|
||||
}
|
||||
return artifactEvidence;
|
||||
|
||||
@@ -198,7 +198,11 @@ function addBlocker(blockers, blocker) {
|
||||
|
||||
function imageToManifestUrl(image) {
|
||||
const match = image.match(/^ghcr\.io\/pikastech\/([^:@]+):([^:@]+)$/);
|
||||
return match ? `https://ghcr.io/v2/pikastech/${match[1]}/manifests/${match[2]}` : null;
|
||||
if (match) return `https://ghcr.io/v2/pikastech/${match[1]}/manifests/${match[2]}`;
|
||||
|
||||
const localMatch = image.match(/^(127\.0\.0\.1:5000|localhost:5000)\/hwlab\/([^:@]+):([^:@]+)$/);
|
||||
if (localMatch) return `http://${localMatch[1]}/v2/hwlab/${localMatch[2]}/manifests/${localMatch[3]}`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function matchesTargetCommit(value, targetCommit, targetShortCommit) {
|
||||
@@ -225,21 +229,31 @@ function catalogDigestCounts(catalog) {
|
||||
return counts;
|
||||
}
|
||||
|
||||
function requiredCatalogServices(catalog) {
|
||||
return (catalog.services ?? []).filter((service) => service.artifactRequired !== false);
|
||||
}
|
||||
|
||||
function disabledCatalogServices(catalog) {
|
||||
return (catalog.services ?? []).filter((service) => service.artifactRequired === false);
|
||||
}
|
||||
|
||||
function artifactIdentityFor({ deploy, catalog, targetCommit, targetShortCommit, targetRef }) {
|
||||
const serviceCommitIds = uniqueSorted((catalog.services ?? []).map((service) => service.commitId));
|
||||
const digestCounts = catalogDigestCounts(catalog);
|
||||
const requiredServices = requiredCatalogServices(catalog);
|
||||
const disabledServices = disabledCatalogServices(catalog);
|
||||
const catalogMatchesSource = matchesTargetCommit(catalog.commitId, targetCommit, targetShortCommit);
|
||||
const deployMatchesSource = matchesTargetCommit(deploy.commitId, targetCommit, targetShortCommit);
|
||||
const servicesMatchSource = serviceCommitIds.length === 1 &&
|
||||
matchesTargetCommit(serviceCommitIds[0], targetCommit, targetShortCommit);
|
||||
const allDigestsPublished = digestCounts.sha256 === SERVICE_IDS.length &&
|
||||
digestCounts.notPublished === 0 &&
|
||||
const allRequiredDigestsPublished = requiredServices.length > 0 &&
|
||||
requiredServices.every((service) => digestPattern.test(service.digest) && service.publishState === "published") &&
|
||||
disabledServices.every((service) => service.digest === "not_published" && service.publishState === "skeleton-only") &&
|
||||
digestCounts.invalid === 0;
|
||||
const publishVerified = catalog.publish?.ciPublished === true &&
|
||||
catalog.publish?.registryVerified === true &&
|
||||
catalog.artifactState === "published" &&
|
||||
allDigestsPublished &&
|
||||
(catalog.services ?? []).every((service) => service.publishState === "published");
|
||||
allRequiredDigestsPublished;
|
||||
|
||||
return {
|
||||
source: {
|
||||
@@ -260,6 +274,8 @@ function artifactIdentityFor({ deploy, catalog, targetCommit, targetShortCommit,
|
||||
registryVerified: catalog.publish?.registryVerified === true,
|
||||
provenance: catalog.publish?.provenance ?? "unknown",
|
||||
digestCounts,
|
||||
requiredServiceCount: requiredServices.length,
|
||||
disabledServiceCount: disabledServices.length,
|
||||
matchesSource: catalogMatchesSource
|
||||
},
|
||||
services: (catalog.services ?? []).map((service) => ({
|
||||
@@ -269,7 +285,9 @@ function artifactIdentityFor({ deploy, catalog, targetCommit, targetShortCommit,
|
||||
image: service.image,
|
||||
imageTag: service.imageTag,
|
||||
digest: service.digest,
|
||||
publishState: service.publishState
|
||||
publishState: service.publishState,
|
||||
artifactRequired: service.artifactRequired !== false,
|
||||
notPublishedReason: service.notPublishedReason ?? null
|
||||
})),
|
||||
serviceCommitIds,
|
||||
matchesSource: deployMatchesSource && catalogMatchesSource && servicesMatchSource,
|
||||
@@ -478,17 +496,26 @@ function validateArtifactPublishReport(reporter, artifactReport, targetShortComm
|
||||
const sourcePresenceResolved =
|
||||
services.length === SERVICE_IDS.length &&
|
||||
services.every((service) => service.sourceState === "source-present" || service.sourceState === "intentionally-disabled");
|
||||
const allServicesPublished = services.length === SERVICE_IDS.length &&
|
||||
services.every((service) => service.status === "published" && /^sha256:[a-f0-9]{64}$/.test(service.digest));
|
||||
const requiredServices = services.filter((service) => service.artifactRequired !== false);
|
||||
const disabledServices = services.filter((service) => service.artifactRequired === false);
|
||||
const allServicesAccountedFor = services.length === SERVICE_IDS.length &&
|
||||
[...services].map((service) => service.serviceId).sort().join("\n") === [...SERVICE_IDS].sort().join("\n");
|
||||
const allRequiredServicesPublished = requiredServices.length > 0 &&
|
||||
requiredServices.every((service) => service.status === "published" && /^sha256:[a-f0-9]{64}$/.test(service.digest));
|
||||
const disabledServicesNotPublished = disabledServices.every(
|
||||
(service) => service.digest === "not_published" && service.notPublishedReason?.startsWith("disabled_")
|
||||
);
|
||||
const sourceMatchesTarget = [targetCommit, targetShortCommit].includes(artifactPublish?.sourceCommitId) ||
|
||||
[targetCommit, targetShortCommit].includes(artifactReport.commitId);
|
||||
const publishReady = artifactPublish?.status === "published" &&
|
||||
artifactPublish.publishedCount === SERVICE_IDS.length &&
|
||||
allServicesPublished &&
|
||||
artifactPublish.publishedCount === requiredServices.length &&
|
||||
allServicesAccountedFor &&
|
||||
allRequiredServicesPublished &&
|
||||
disabledServicesNotPublished &&
|
||||
sourceMatchesTarget;
|
||||
|
||||
if (publishReady) {
|
||||
reporter.check("dev-artifact-publish-report", "registry", "pass", "DEV artifact publish report covers all frozen service IDs with immutable digests for the target commit.");
|
||||
reporter.check("dev-artifact-publish-report", "registry", "pass", "DEV artifact publish report covers all required enabled service IDs with immutable digests for the target commit, and records disabled service reasons.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -501,13 +528,13 @@ function validateArtifactPublishReport(reporter, artifactReport, targetShortComm
|
||||
"dev-artifact-publish-report",
|
||||
"registry",
|
||||
"blocked",
|
||||
`DEV artifact publish report status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${serviceCount} published for source ${artifactPublish?.sourceCommitId ?? artifactReport.commitId ?? "unknown"}; ${sourceSummary}.`
|
||||
`DEV artifact publish report status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published for source ${artifactPublish?.sourceCommitId ?? artifactReport.commitId ?? "unknown"}; ${sourceSummary}.`
|
||||
);
|
||||
reporter.block({
|
||||
type: "runtime_blocker",
|
||||
scope: "dev-artifact-publish",
|
||||
summary: `reports/dev-gate/dev-artifacts.json does not prove all HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${serviceCount} published and ${sourceSummary}.`,
|
||||
nextTask: "Complete DEV artifact publishing for every frozen HWLAB service at the current origin/main commit and record immutable registry digests."
|
||||
summary: `reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published and ${sourceSummary}.`,
|
||||
nextTask: "Complete DEV artifact publishing for every enabled required HWLAB service at the current origin/main commit and record immutable registry digests; keep disabled services marked not_published with reasons."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -762,7 +789,8 @@ async function validateLiveProbes(reporter, catalog, timeoutMs) {
|
||||
});
|
||||
}
|
||||
|
||||
const registryEvidence = await Promise.all(catalog.services.map(async (service) => {
|
||||
const registryServices = requiredCatalogServices(catalog);
|
||||
const registryEvidence = await Promise.all(registryServices.map(async (service) => {
|
||||
const url = imageToManifestUrl(service.image);
|
||||
if (!url) {
|
||||
return { serviceId: service.serviceId, ok: false, image: service.image, error: "unsupported image format" };
|
||||
@@ -778,18 +806,18 @@ async function validateLiveProbes(reporter, catalog, timeoutMs) {
|
||||
}));
|
||||
const registryOk = registryEvidence.every((probe) => probe.ok);
|
||||
reporter.check(
|
||||
"ghcr-anonymous-manifest-read",
|
||||
"registry-manifest-read",
|
||||
"registry",
|
||||
registryOk ? "pass" : "blocked",
|
||||
registryOk ? "All catalog images were visible through anonymous GHCR manifest HEAD probes." : "One or more catalog images could not be verified through anonymous GHCR manifest HEAD probes.",
|
||||
registryOk ? "All required catalog images were visible through registry manifest HEAD probes." : "One or more required catalog images could not be verified through registry manifest HEAD probes.",
|
||||
registryEvidence
|
||||
);
|
||||
if (!registryOk) {
|
||||
reporter.block({
|
||||
type: "runtime_blocker",
|
||||
scope: "ghcr",
|
||||
summary: "This preflight could not verify GHCR manifests for the DEV catalog images without reading credentials.",
|
||||
nextTask: "Publish public DEV images or provide a non-secret registry evidence artifact with immutable digests for each HWLAB service."
|
||||
scope: "registry-manifest-read",
|
||||
summary: "This preflight could not verify registry manifests for the required DEV catalog images without reading credentials.",
|
||||
nextTask: "Publish required DEV images or provide a non-secret registry evidence artifact with immutable digests for each required HWLAB service."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const mutableTags = new Set(["latest", "dev", "main", "master", "prod", "product
|
||||
const catalogModes = new Set(["contract-skeleton", "published"]);
|
||||
const skeletonProvenances = new Set(["not_available_in_mvp_skeleton", "not_available_until_publish"]);
|
||||
const sourceStates = new Set(["source-present", "intentionally-disabled"]);
|
||||
const artifactScopes = new Set(["required", "disabled"]);
|
||||
const requiredForbiddenItems = [
|
||||
"prod-deploy",
|
||||
"prod-profile-enabled",
|
||||
@@ -111,6 +112,16 @@ function assertDevOnlyCatalog(catalog) {
|
||||
return catalog.artifactState;
|
||||
}
|
||||
|
||||
function assertServiceInventory(catalog) {
|
||||
assert.ok(catalog.serviceInventory && typeof catalog.serviceInventory === "object", "catalog serviceInventory must be an object");
|
||||
assert.equal(catalog.serviceInventory.version, "v2", "catalog serviceInventory.version");
|
||||
assert.deepEqual(catalog.serviceInventory.requiredServiceIds, SERVICE_IDS, "catalog required service IDs");
|
||||
assert.deepEqual(catalog.serviceInventory.disabledServiceIds, [], "catalog disabled service IDs");
|
||||
assert.equal(catalog.serviceInventory.serviceCount, SERVICE_IDS.length, "catalog serviceInventory.serviceCount");
|
||||
assert.equal(catalog.serviceInventory.requiredServiceCount, catalog.serviceInventory.requiredServiceIds.length, "catalog requiredServiceCount");
|
||||
assert.equal(catalog.serviceInventory.disabledServiceCount, catalog.serviceInventory.disabledServiceIds.length, "catalog disabledServiceCount");
|
||||
}
|
||||
|
||||
function assertDeployManifest(deployManifest, catalog) {
|
||||
assert.equal(deployManifest.manifestVersion, "v1", "deploy manifestVersion");
|
||||
assert.equal(deployManifest.environment, ENVIRONMENT_DEV, "deploy environment");
|
||||
@@ -136,6 +147,8 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
|
||||
assertUnique("deploy service ids", deployServiceIds);
|
||||
|
||||
const deployByServiceId = new Map(deployManifest.services.map((service) => [service.serviceId, service]));
|
||||
const requiredServiceIds = new Set(catalog.serviceInventory.requiredServiceIds);
|
||||
const disabledServiceIds = new Set(catalog.serviceInventory.disabledServiceIds);
|
||||
for (const service of catalog.services) {
|
||||
const context = `service ${service.serviceId}`;
|
||||
const deployService = deployByServiceId.get(service.serviceId);
|
||||
@@ -161,12 +174,31 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
|
||||
if (service.sourceState === "intentionally-disabled") {
|
||||
assert.equal(deployService.replicas, 0, `${context} intentionally-disabled services must have zero deploy replicas`);
|
||||
}
|
||||
|
||||
assert.ok(artifactScopes.has(service.artifactScope), `${context} artifactScope`);
|
||||
assert.equal(typeof service.publishEnabled, "boolean", `${context} publishEnabled`);
|
||||
assert.equal(typeof service.artifactRequired, "boolean", `${context} artifactRequired`);
|
||||
assert.equal(service.artifactRequired, requiredServiceIds.has(service.serviceId), `${context} artifactRequired must match serviceInventory`);
|
||||
if (requiredServiceIds.has(service.serviceId)) {
|
||||
assert.equal(service.artifactScope, "required", `${context} artifactScope`);
|
||||
} else {
|
||||
assert.ok(disabledServiceIds.has(service.serviceId), `${context} disabled service must be in serviceInventory`);
|
||||
assert.equal(service.artifactScope, "disabled", `${context} artifactScope`);
|
||||
assert.equal(service.publishEnabled, false, `${context} publishEnabled`);
|
||||
assert.equal(service.notPublishedReason.startsWith("disabled_"), true, `${context} notPublishedReason must explain disabled service`);
|
||||
}
|
||||
|
||||
if (catalogMode === "contract-skeleton") {
|
||||
assert.equal(service.publishState, "skeleton-only", `${context} publishState`);
|
||||
assert.equal(service.digest, "not_published", `${context} digest`);
|
||||
} else {
|
||||
assert.equal(service.publishState, "published", `${context} publishState`);
|
||||
assert.match(service.digest, digestPattern, `${context} digest`);
|
||||
if (service.artifactRequired) {
|
||||
assert.equal(service.publishState, "published", `${context} publishState`);
|
||||
assert.match(service.digest, digestPattern, `${context} digest`);
|
||||
} else {
|
||||
assert.equal(service.publishState, "skeleton-only", `${context} publishState`);
|
||||
assert.equal(service.digest, "not_published", `${context} digest`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +231,7 @@ const ci = await readJSON(ciPath);
|
||||
const healthContract = await readJSON(healthContractPath);
|
||||
|
||||
const catalogMode = assertDevOnlyCatalog(catalog);
|
||||
assertServiceInventory(catalog);
|
||||
assertDeployManifest(deployManifest, catalog);
|
||||
assertCatalogServices(catalog, deployManifest, catalogMode);
|
||||
assertCIForbidden(ci, catalog);
|
||||
|
||||
@@ -145,7 +145,7 @@ function assertHwlabImage(image, serviceId, label) {
|
||||
);
|
||||
assert.match(
|
||||
image,
|
||||
new RegExp(`^ghcr\\.io/pikastech/${serviceId}:[a-f0-9]{7,40}$`),
|
||||
new RegExp(`^(ghcr\\.io/pikastech|127\\.0\\.0\\.1:5000/hwlab|localhost:5000/hwlab)/${serviceId}:[a-f0-9]{7,40}$`),
|
||||
`${label}.image must be the HWLAB image for ${serviceId}`
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user