docs: refresh DEV gate evidence layering

This commit is contained in:
HWLAB Code Queue
2026-05-22 07:26:40 +00:00
parent 8e89409dda
commit e7cc38147e
10 changed files with 1116 additions and 399 deletions
+21 -5
View File
@@ -38,16 +38,29 @@ promoted to live DEV evidence:
| `SOURCE` | Repository contracts, schemas, docs, manifests, or examples. |
| `LOCAL` | Localhost or in-process smoke results only. |
| `DRY-RUN` | Fixture-backed or no-mutation dry-run readiness evidence. |
| `DEV-LIVE` | Read-only evidence observed from the real DEV route. |
| `DEV-LIVE` | Read-only evidence observed from the real DEV route; this may prove entrypoint reachability without proving downstream DB, M3, M4, or M5 acceptance. |
| `BLOCKED` | Missing issue reads, missing DEV evidence, missing artifacts, or other open blockers. |
## Current DEV Layering
The v2 report exposes `currentDevLayering` so the public route can be green
without promoting the full gate:
| Layer | Meaning |
| --- | --- |
| `EDGE/ROUTE live` | `16666` browser and `16667` health route evidence from read-only probes. |
| `DB live/degraded` | Cloud API DB readiness from redacted health evidence; route success does not imply DB live. |
| `M3 hardware trusted loop` | Real DEV `res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1` plus operation, trace, audit, and evidence identifiers. |
| `M4 agent loop` | Agent loop live preflight and scheduling/evidence closure, gated by DB and runtime readiness. |
| `artifact/desired-state source` | Artifact source coverage for the current target ref plus desired-state dry-run/apply status. |
## Current Contract Map
| Milestone | Current visible level | Contract entry |
| --- | --- | --- |
| M0 | `SOURCE` | `docs/m0-contract-audit.md`, protocol schemas, evidence chain examples, M0 validator. |
| M1 | `LOCAL` | `docs/m1-local-smoke.md`, `fixtures/mvp/runtime.json`, `scripts/m1-contract-smoke.mjs`. |
| M2 | `DRY-RUN` | DEV deploy smoke fixture, artifact catalog, runtime boundary, and M2 dry-run validator. |
| M2 | `DEV-LIVE` for route only | DEV deploy smoke fixture plus active read-only `16666`/`16667` endpoint smoke. |
| M3 | `LOCAL` | Hardware trusted loop topology fixture and local patch-panel smoke. |
| M4 | `LOCAL` | Agent automation loop fixture, local runtime skeleton, skills commit, evidence, and cleanup smoke. |
| M5 | `DRY-RUN` | MVP e2e dry-run plan and orchestration validator. |
@@ -60,14 +73,17 @@ The v2 report reads committed report fixtures only:
- `reports/dev-gate/dev-deploy-report.json`
- `reports/dev-gate/dev-artifacts.json`
- `reports/dev-gate/dev-edge-health.json`
- `reports/dev-gate/dev-m2-deploy-smoke-active.json`
- `reports/dev-gate/dev-m3-hardware-loop.json`
- `reports/dev-gate/dev-m4-agent-loop.json`
- `reports/dev-gate/dev-mvp-gate-report.json`
- `reports/d601-k3s-readonly-observability.json`
The report is blocked until real DEV-LIVE evidence is attached for the DEV
route and downstream HWLAB runtime services. Current source, local, and dry-run
contracts are useful readiness signals, but they are not live DEV proof.
The report remains blocked until real DEV-LIVE evidence is attached for the
required downstream HWLAB runtime services. Public route evidence on
`16666/16667` is necessary but not sufficient: DB live readiness, M3 trusted
loop evidence, M4 agent-loop evidence, and artifact/desired-state source
coverage stay separate.
The legacy base-image blocker is not current when the committed reports show an
approved Node 20 builder base. Artifact/current remains stricter: a target ref
+5
View File
@@ -29,6 +29,11 @@ Each committed DEV gate report must carry `reportLifecycle`:
`res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1` in real DEV
plus operation, audit, and evidence identifiers. SOURCE, LOCAL, DRY-RUN,
fixture, and historical report evidence are support/diagnostics only.
- M5 DEV-LIVE cannot be inferred from public entrypoint reachability. Reports
must keep EDGE/ROUTE live, DB live/degraded, M3 hardware trusted loop,
M4 agent loop, and artifact/desired-state source status as separate layers.
If the only read-only evidence is `16666/16667` reachability, M5 must remain
blocked/non-green.
## Files
+477 -36
View File
@@ -12,7 +12,7 @@
"pikasTech/HWLAB#39",
"pikasTech/HWLAB#42"
],
"generatedAt": "2026-05-21T17:34:15.046Z",
"generatedAt": "2026-05-22T07:21:35.915Z",
"mode": "read-only",
"devOnly": true,
"prodDisabled": true,
@@ -48,18 +48,28 @@
"node --check scripts/src/d601-k3s-readonly-observability.mjs",
"node scripts/d601-k3s-readonly-observability.mjs"
],
"conclusion": "blocked",
"conclusion": "ready",
"environment": {
"binaries": {
"kubectl": {
"name": "kubectl",
"available": false,
"path": null
"available": true,
"path": "/usr/local/bin/kubectl",
"version": {
"ok": true,
"command": "kubectl version --client=true",
"output": "Client Version: v1.36.1 Kustomize Version: v5.8.1"
}
},
"k3s": {
"name": "k3s",
"available": false,
"path": null
"available": true,
"path": "/usr/local/bin/k3s",
"version": {
"ok": true,
"command": "k3s --version",
"output": "k3s version v1.34.1+k3s1 (24fc436e) go version go1.24.6"
}
},
"k3sctl": {
"name": "k3sctl",
@@ -73,11 +83,19 @@
"version": {
"ok": true,
"command": "ssh -V",
"output": "OpenSSH_10.0p2 Debian-7+deb13u2, OpenSSL 3.5.5 27 Jan 2026"
"output": "OpenSSH_10.0p2 Debian-7+deb13u4, OpenSSL 3.5.6 7 Apr 2026"
}
}
},
"kubeconfig": [
{
"source": "KUBECONFIG",
"path": "/var/lib/unidesk/code-queue/kubeconfig",
"exists": true,
"readable": true,
"type": "file",
"sizeBytes": 410
},
{
"source": "default",
"path": "~/.kube/config",
@@ -147,7 +165,7 @@
"exists": true,
"readable": true,
"type": "file",
"sizeBytes": 4208
"sizeBytes": 5186
}
],
"configured": false,
@@ -156,36 +174,459 @@
},
"cluster": {
"namespace": "hwlab-dev",
"attemptedExecutors": [],
"readable": false,
"executors": []
"attemptedExecutors": [
"kubectl",
"k3s-kubectl"
],
"readable": true,
"executors": [
{
"executor": "kubectl",
"ready": true,
"probes": {
"clientVersion": {
"ok": true,
"command": "kubectl version --client=true",
"exitCode": 0,
"stdout": "Client Version: v1.36.1\nKustomize Version: v5.8.1"
},
"currentContext": {
"ok": true,
"command": "kubectl config current-context",
"exitCode": 0,
"stdout": "in-cluster"
},
"namespace": {
"ok": true,
"command": "kubectl get namespace hwlab-dev -o json",
"exitCode": 0
},
"auth": {
"pods": {
"allowed": true,
"ok": true,
"command": "kubectl auth can-i get pods -n hwlab-dev",
"exitCode": 0,
"stdout": "yes"
},
"services": {
"allowed": true,
"ok": true,
"command": "kubectl auth can-i get services -n hwlab-dev",
"exitCode": 0,
"stdout": "yes"
},
"configmaps": {
"allowed": true,
"ok": true,
"command": "kubectl auth can-i get configmaps -n hwlab-dev",
"exitCode": 0,
"stdout": "yes"
}
},
"pods": {
"ok": true,
"command": "kubectl -n hwlab-dev get pods -o json",
"exitCode": 0
},
"services": {
"ok": true,
"command": "kubectl -n hwlab-dev get services -o json",
"exitCode": 0
},
"configMaps": {
"ok": true,
"command": "kubectl -n hwlab-dev get configmaps -o json",
"exitCode": 0
}
},
"summaries": {
"pods": {
"count": 13,
"items": [
{
"name": "hwlab-agent-mgr-75544c7747-5dzt9",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-agent-skills-849d9976f4-2nsmw",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-box-simu-55688cbcc7-pftxc",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-box-simu-55688cbcc7-sq5fx",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-cloud-api-f48dcdfb-9n6zh",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-cloud-web-cd9655b98-gxtmc",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-edge-proxy-567f978cdd-8fwc8",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-frpc-59f57d479c-wm5l2",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-gateway-simu-699fd486b-j9pk7",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-gateway-simu-699fd486b-phcgp",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-patch-panel-757f9f44d5-7np8r",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-router-5d9cbdf89b-p7r94",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
},
{
"name": "hwlab-tunnel-client-65c7858c8-wjqsh",
"phase": "Running",
"readyContainers": 1,
"totalContainers": 1,
"restartCount": 0,
"nodeName": "d601"
}
]
},
"services": {
"count": 12,
"items": [
{
"name": "hwlab-agent-mgr",
"type": "ClusterIP",
"clusterIP": "10.43.175.43",
"ports": [
{
"name": "http",
"port": 7410,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-agent-skills",
"type": "ClusterIP",
"clusterIP": "10.43.218.243",
"ports": [
{
"name": "http",
"port": 7430,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-box-simu",
"type": "ClusterIP",
"clusterIP": "10.43.192.161",
"ports": [
{
"name": "http",
"port": 7201,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-cli",
"type": "ClusterIP",
"clusterIP": "10.43.94.68",
"ports": [
{
"name": "http",
"port": 7501,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-cloud-api",
"type": "ClusterIP",
"clusterIP": "10.43.88.144",
"ports": [
{
"name": "http",
"port": 6667,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-cloud-web",
"type": "ClusterIP",
"clusterIP": "10.43.112.75",
"ports": [
{
"name": "http",
"port": 8080,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-edge-proxy",
"type": "ClusterIP",
"clusterIP": "10.43.104.107",
"ports": [
{
"name": "http",
"port": 6667,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-gateway",
"type": "ClusterIP",
"clusterIP": "10.43.70.176",
"ports": [
{
"name": "http",
"port": 7001,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-gateway-simu",
"type": "ClusterIP",
"clusterIP": "10.43.70.14",
"ports": [
{
"name": "http",
"port": 7101,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-patch-panel",
"type": "ClusterIP",
"clusterIP": "10.43.7.35",
"ports": [
{
"name": "http",
"port": 7301,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-router",
"type": "ClusterIP",
"clusterIP": "10.43.6.240",
"ports": [
{
"name": "http",
"port": 7401,
"targetPort": "http",
"protocol": "TCP"
}
]
},
{
"name": "hwlab-tunnel-client",
"type": "ClusterIP",
"clusterIP": "10.43.129.225",
"ports": [
{
"name": "http",
"port": 7402,
"targetPort": "http",
"protocol": "TCP"
}
]
}
]
},
"configMaps": {
"count": 3,
"items": [
{
"name": "hwlab-dev-health-contract",
"keys": [
"agent",
"cloud-api",
"cloud-api-db",
"cloud-web",
"edge-proxy",
"endpoint",
"patch-panel",
"router",
"runtime-substitute-policy",
"sim",
"tunnel-client"
]
},
{
"name": "hwlab-frpc-config",
"keys": [
"frpc.toml"
]
},
{
"name": "kube-root-ca.crt",
"keys": [
"ca.crt"
]
}
]
}
}
},
{
"executor": "k3s-kubectl",
"ready": false,
"probes": {
"clientVersion": {
"ok": false,
"command": "k3s kubectl version --client=true",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl version --client=true error: unknown command \"kubectl\" for \"kubectl\""
},
"currentContext": {
"ok": false,
"command": "k3s kubectl config current-context",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl config current-context error: unknown command \"kubectl\" for \"kubectl\""
},
"namespace": {
"ok": false,
"command": "k3s kubectl get namespace hwlab-dev -o json",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl get namespace hwlab-dev -o json error: unknown command \"kubectl\" for \"kubectl\""
},
"auth": {
"pods": {
"allowed": false,
"ok": false,
"command": "k3s kubectl auth can-i get pods -n hwlab-dev",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl auth can-i get pods -n hwlab-dev error: unknown command \"kubectl\" for \"kubectl\""
},
"services": {
"allowed": false,
"ok": false,
"command": "k3s kubectl auth can-i get services -n hwlab-dev",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl auth can-i get services -n hwlab-dev error: unknown command \"kubectl\" for \"kubectl\""
},
"configmaps": {
"allowed": false,
"ok": false,
"command": "k3s kubectl auth can-i get configmaps -n hwlab-dev",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl auth can-i get configmaps -n hwlab-dev error: unknown command \"kubectl\" for \"kubectl\""
}
},
"pods": {
"ok": false,
"command": "k3s kubectl -n hwlab-dev get pods -o json",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl -n hwlab-dev get pods -o json error: unknown command \"kubectl\" for \"kubectl\""
},
"services": {
"ok": false,
"command": "k3s kubectl -n hwlab-dev get services -o json",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl -n hwlab-dev get services -o json error: unknown command \"kubectl\" for \"kubectl\""
},
"configMaps": {
"ok": false,
"command": "k3s kubectl -n hwlab-dev get configmaps -o json",
"exitCode": 1,
"stderr": "error: unknown command \"kubectl\" for \"kubectl\"",
"error": "Command failed: k3s kubectl -n hwlab-dev get configmaps -o json error: unknown command \"kubectl\" for \"kubectl\""
}
},
"summaries": null
}
]
},
"blockers": [
{
"status": "open",
"type": "environment_blocker",
"scope": "d601-k3s-client-binary",
"summary": "Neither kubectl nor k3s is installed in this runner PATH.",
"nextTask": "Install kubectl in the D601 Code Queue runner image, or mount an approved k3s kubectl client path for read-only hwlab-dev observation."
},
{
"status": "open",
"type": "environment_blocker",
"scope": "d601-kubeconfig-path",
"summary": "No readable KUBECONFIG/default k3s kubeconfig path was found by metadata checks.",
"nextTask": "Mount a read-only kubeconfig for hwlab-dev, or document the approved k3s local kubeconfig path without exposing token material."
},
{
"status": "open",
"type": "environment_blocker",
"scope": "d601-maintenance-ssh-bridge",
"summary": "No SSH maintenance bridge was detected from ssh binary plus known environment variables or ~/.ssh/config metadata.",
"nextTask": "Provide a documented read-only maintenance bridge variable or config path if kubectl cannot be mounted directly in the runner."
}
],
"blockers": [],
"runnerImageFollowUp": {
"needed": true,
"suggestedUniDeskIssue": "Code Queue runner image should include kubectl and a documented read-only hwlab-dev kubeconfig mount/permission path; keep Secret resources and token material inaccessible to reports."
"needed": false,
"suggestedUniDeskIssue": null
},
"notes": "This report uses command availability checks, file metadata checks, kubectl/k3s kubectl read-only get probes, and auth can-i checks only. It does not print kubeconfig contents, token material, Secret resources, ConfigMap values, SSH config contents, or private keys."
}
@@ -4,12 +4,20 @@
"reportVersion": "v1",
"issue": "pikasTech/HWLAB#23",
"taskId": "m2-dev-deploy-smoke",
"commitId": "54f927b02bc1",
"commitId": "8e89409dda5d",
"acceptanceLevel": "dev_m2_deploy_smoke",
"devOnly": true,
"prodDisabled": true,
"reportLifecycle": {
"version": "v1",
"state": "active",
"activeEndpoint": "http://74.48.78.17:16667",
"activeBrowserEndpoint": "http://74.48.78.17:16666",
"deprecatedEndpoint": null,
"summary": "Active M2 read-only public endpoint smoke; route reachability is not M5 DEV-LIVE acceptance."
},
"status": "pass",
"generatedAt": "2026-05-22T04:53:00.375Z",
"generatedAt": "2026-05-22T07:18:45.192Z",
"endpoint": "http://74.48.78.17:16667",
"frontendEndpoint": "http://74.48.78.17:16666",
"sourceContract": {
@@ -76,7 +84,7 @@
"runtimeSmoke": {
"mode": "live-read-only",
"status": "pass",
"generatedAt": "2026-05-22T04:53:00.375Z",
"generatedAt": "2026-05-22T07:18:45.192Z",
"apiEndpoint": "http://74.48.78.17:16667",
"frontendEndpoint": "http://74.48.78.17:16666",
"safety": {
@@ -95,12 +103,12 @@
"ok": true,
"status": 200,
"statusText": "OK",
"durationMs": 939,
"durationMs": 3916,
"contentType": "application/json; charset=utf-8",
"headers": {
"content-type": "application/json; charset=utf-8",
"content-length": "661",
"date": "Fri, 22 May 2026 04:53:00 GMT"
"date": "Fri, 22 May 2026 07:18:43 GMT"
},
"json": {
"serviceId": "hwlab-edge-proxy",
@@ -122,7 +130,7 @@
"digest": "unknown"
},
"endpoint": "http://74.48.78.17:16667",
"observedAt": "2026-05-22T04:53:00.143Z",
"observedAt": "2026-05-22T07:18:43.527Z",
"details": {
"upstream": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667",
"mode": "dev-edge-proxy"
@@ -137,12 +145,12 @@
"ok": true,
"status": 200,
"statusText": "OK",
"durationMs": 936,
"durationMs": 3510,
"contentType": "application/json; charset=utf-8",
"headers": {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
"date": "Fri, 22 May 2026 04:53:00 GMT"
"date": "Fri, 22 May 2026 07:18:44 GMT"
},
"json": {
"serviceId": "hwlab-cloud-api",
@@ -164,7 +172,7 @@
"digest": "unknown"
},
"endpoint": "http://74.48.78.17:16667",
"observedAt": "2026-05-22T04:53:00.145Z",
"observedAt": "2026-05-22T07:18:44.622Z",
"db": {
"contractVersion": "v1",
"environment": "dev",
@@ -225,7 +233,7 @@
}
},
"title": null,
"bodyPreview": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"cb35ada\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:cb35ada\",\"tag\":\"cb35ada\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T04:53:00.145Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"connectionChecked\":false,\"configReady\":"
"bodyPreview": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"cb35ada\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:cb35ada\",\"tag\":\"cb35ada\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T07:18:44.622Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"connectionChecked\":false,\"configReady\":"
},
{
"id": "frontend-root",
@@ -233,12 +241,12 @@
"ok": true,
"status": 200,
"statusText": "OK",
"durationMs": 923,
"durationMs": 1027,
"contentType": "text/html; charset=utf-8",
"headers": {
"content-type": "text/html; charset=utf-8",
"content-length": "4230",
"date": "Fri, 22 May 2026 04:53:00 GMT"
"date": "Fri, 22 May 2026 07:18:42 GMT"
},
"json": null,
"title": "HWLAB DEV MVP Gate",
@@ -252,13 +260,5 @@
"summary": "Legacy public :6667 observations are retained only as historical blockers and are not accepted as active green evidence."
}
},
"notes": "M2 active report uses read-only GET probes only; PROD, service restarts, deployments, and script-driven apply flows were not touched.",
"reportLifecycle": {
"version": "v1",
"state": "active",
"activeEndpoint": "http://74.48.78.17:16667",
"activeBrowserEndpoint": "http://74.48.78.17:16666",
"deprecatedEndpoint": null,
"summary": "Current M2 DEV deploy smoke report; legacy public endpoints are deprecated and not active green evidence."
}
"notes": "M2 active report uses read-only GET probes only; PROD, service restarts, deployments, and script-driven apply flows were not touched."
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+41 -33
View File
@@ -24,13 +24,21 @@
],
"target": {
"ref": "origin/main",
"commitId": "82ad96c5c67c3906732a057d52395ec72851e4a9",
"shortCommitId": "82ad96c"
"commitId": "8e89409dda5d0b028de7f7b7b0e006a60bdd6d44",
"shortCommitId": "8e89409"
},
"generatedAt": "2026-05-22T05:35:39.805Z",
"generatedAt": "2026-05-22T07:12:46.951Z",
"mode": "read-only",
"devOnly": true,
"prodDisabled": true,
"reportLifecycle": {
"version": "v1",
"state": "active",
"activeEndpoint": "http://74.48.78.17:16667",
"activeBrowserEndpoint": "http://74.48.78.17:16666",
"deprecatedEndpoint": null,
"summary": "Current read-only DEV preflight report; source, local, and dry-run checks are not DEV-LIVE acceptance."
},
"forbiddenActions": [
"prod-deploy",
"secret-material-read",
@@ -52,8 +60,8 @@
"artifactIdentity": {
"source": {
"ref": "origin/main",
"commitId": "82ad96c5c67c3906732a057d52395ec72851e4a9",
"shortCommitId": "82ad96c"
"commitId": "8e89409dda5d0b028de7f7b7b0e006a60bdd6d44",
"shortCommitId": "8e89409"
},
"artifactSource": {
"source": "artifact-publish-report",
@@ -67,17 +75,21 @@
"sourceCommitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33",
"sourceShortCommitId": "7e29522",
"targetRef": "origin/main",
"targetCommitId": "82ad96c5c67c3906732a057d52395ec72851e4a9",
"targetShortCommitId": "82ad96c",
"targetCommitId": "8e89409dda5d0b028de7f7b7b0e006a60bdd6d44",
"targetShortCommitId": "8e89409",
"mode": "artifact-input-drift",
"covered": false,
"artifactBuildInputChanged": true,
"changedPathCount": 22,
"changedPathCount": 48,
"changedArtifactInputPaths": [
"internal/dev-report-lifecycle.mjs",
"internal/mvp-gate/summary.mjs",
"internal/protocol/index.mjs",
"package.json",
"scripts/dev-artifact-publish.mjs",
"web/hwlab-cloud-web/gate-summary.mjs"
],
"changedNonArtifactPathCount": 20,
"changedNonArtifactPathCount": 42,
"reason": "target changes since the artifact source touch artifact build inputs"
},
"deployManifest": {
@@ -274,7 +286,7 @@
"registryCapabilities": {
"version": "v1",
"registryPrefix": "127.0.0.1:5000/hwlab",
"generatedAt": "2026-05-22T05:35:38.863Z",
"generatedAt": "2026-05-22T07:12:45.056Z",
"classification": "degraded",
"dimensions": {
"processHttpAccess": {
@@ -322,7 +334,7 @@
"names": "unidesk-artifact-registry",
"ports": "127.0.0.1:5000->5000/tcp",
"state": "running",
"status": "Up 28 hours"
"status": "Up 30 hours"
}
],
"pushAttempted": false,
@@ -461,13 +473,13 @@
"id": "target-commit-pinning",
"category": "contract",
"status": "blocked",
"summary": "Source origin/main 82ad96c is not covered by artifact source 7e29522: target changes since the artifact source touch artifact build inputs.",
"summary": "Source origin/main 8e89409 is not covered by artifact source 7e29522: target changes since the artifact source touch artifact build inputs.",
"evidence": [
{
"source": {
"ref": "origin/main",
"commitId": "82ad96c5c67c3906732a057d52395ec72851e4a9",
"shortCommitId": "82ad96c"
"commitId": "8e89409dda5d0b028de7f7b7b0e006a60bdd6d44",
"shortCommitId": "8e89409"
},
"artifactSource": {
"source": "artifact-publish-report",
@@ -481,17 +493,21 @@
"sourceCommitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33",
"sourceShortCommitId": "7e29522",
"targetRef": "origin/main",
"targetCommitId": "82ad96c5c67c3906732a057d52395ec72851e4a9",
"targetShortCommitId": "82ad96c",
"targetCommitId": "8e89409dda5d0b028de7f7b7b0e006a60bdd6d44",
"targetShortCommitId": "8e89409",
"mode": "artifact-input-drift",
"covered": false,
"artifactBuildInputChanged": true,
"changedPathCount": 22,
"changedPathCount": 48,
"changedArtifactInputPaths": [
"internal/dev-report-lifecycle.mjs",
"internal/mvp-gate/summary.mjs",
"internal/protocol/index.mjs",
"package.json",
"scripts/dev-artifact-publish.mjs",
"web/hwlab-cloud-web/gate-summary.mjs"
],
"changedNonArtifactPathCount": 20,
"changedNonArtifactPathCount": 42,
"reason": "target changes since the artifact source touch artifact build inputs"
},
"deployManifest": {
@@ -778,7 +794,7 @@
"names": "unidesk-artifact-registry",
"ports": "127.0.0.1:5000->5000/tcp",
"state": "running",
"status": "Up 28 hours"
"status": "Up 30 hours"
}
],
"pushAttempted": false,
@@ -916,7 +932,7 @@
{
"version": "v1",
"registryPrefix": "127.0.0.1:5000/hwlab",
"generatedAt": "2026-05-22T05:35:38.863Z",
"generatedAt": "2026-05-22T07:12:45.056Z",
"classification": "degraded",
"dimensions": {
"processHttpAccess": {
@@ -964,7 +980,7 @@
"names": "unidesk-artifact-registry",
"ports": "127.0.0.1:5000->5000/tcp",
"state": "running",
"status": "Up 28 hours"
"status": "Up 30 hours"
}
],
"pushAttempted": false,
@@ -1406,7 +1422,7 @@
"method": "GET",
"status": 200,
"statusText": "OK",
"body": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"cb35ada\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:cb35ada\",\"tag\":\"cb35ada\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T05:35:39.607Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"connectionChecked\":false,\"c"
"body": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"cb35ada\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:cb35ada\",\"tag\":\"cb35ada\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T07:12:46.583Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"connectionChecked\":false,\"c"
}
]
},
@@ -1528,7 +1544,7 @@
"status": "open",
"type": "contract_blocker",
"scope": "artifact-source-commit",
"summary": "source commit origin/main 82ad96c is not covered by artifact source 7e29522; target changes since the artifact source touch artifact build inputs.",
"summary": "source commit origin/main 8e89409 is not covered by artifact source 7e29522; target changes since the artifact source touch artifact build inputs.",
"nextTask": "Refresh without fake digests using `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`, or after a successful publish run `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`."
},
{
@@ -1542,7 +1558,7 @@
"status": "open",
"type": "runtime_blocker",
"scope": "dev-artifact-publish",
"summary": "reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for origin/main 82ad96c; current status is published with 13/13 required services published and source states resolved: 13 source-present, 0 intentionally-disabled.",
"summary": "reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for origin/main 8e89409; current status is published with 13/13 required services published and source states resolved: 13 source-present, 0 intentionally-disabled.",
"nextTask": "Complete DEV artifact publishing for every enabled required HWLAB service at the artifact source commit, or prove the target commit has no artifact build input changes; keep disabled services marked not_published with reasons."
},
{
@@ -1553,13 +1569,5 @@
"nextTask": "Deploy cloud-api DB runtime readiness probe and/or repair DEV DB connectivity, then rerun the read-only health smoke without reading or printing the DB secret value."
}
],
"notes": "No PROD action, secret read, UniDesk runtime substitution, heavy e2e, browser e2e, runtime restart, or force push was performed.",
"reportLifecycle": {
"version": "v1",
"state": "active",
"activeEndpoint": "http://74.48.78.17:16667",
"activeBrowserEndpoint": "http://74.48.78.17:16666",
"deprecatedEndpoint": null,
"summary": "Current read-only DEV preflight report; source, local, and dry-run checks are not DEV-LIVE acceptance."
}
"notes": "No PROD action, secret read, UniDesk runtime substitution, heavy e2e, browser e2e, runtime restart, or force push was performed."
}
+4
View File
@@ -6,6 +6,7 @@ import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs";
import {
DEV_ENDPOINT,
DEV_FRONTEND_ENDPOINT,
@@ -299,6 +300,9 @@ function buildActiveReport({ fixture, probes, generatedAt, commitId }) {
acceptanceLevel: "dev_m2_deploy_smoke",
devOnly: true,
prodDisabled: true,
reportLifecycle: activeReportLifecycle(
"Active M2 read-only public endpoint smoke; route reachability is not M5 DEV-LIVE acceptance."
),
status,
generatedAt,
endpoint: DEV_ENDPOINT,
@@ -113,15 +113,20 @@ async function run(command, args = [], options = {}) {
timeout: options.timeoutMs ?? 5000,
maxBuffer: 1024 * 1024
});
return {
const probe = {
ok: true,
command: commandText(command, args),
exitCode: 0,
stdout: redacted(result.stdout.trim()),
stderr: redacted(result.stderr.trim())
};
Object.defineProperty(probe, "rawStdout", {
value: result.stdout.trim(),
enumerable: false
});
return probe;
} catch (error) {
return {
const probe = {
ok: false,
command: commandText(command, args),
exitCode: typeof error.code === "number" ? error.code : 1,
@@ -129,6 +134,11 @@ async function run(command, args = [], options = {}) {
stderr: redacted(String(error.stderr ?? "").trim()),
error: oneLine(error.message)
};
Object.defineProperty(probe, "rawStdout", {
value: String(error.stdout ?? "").trim(),
enumerable: false
});
return probe;
}
}
@@ -239,9 +249,10 @@ async function collectSshBridge(binaries) {
}
function parseJsonProbe(probe) {
if (!probe.ok || !probe.stdout) return null;
const raw = probe.rawStdout ?? probe.stdout;
if (!probe.ok || !raw) return null;
try {
return JSON.parse(probe.stdout);
return JSON.parse(raw);
} catch {
return null;
}
@@ -407,7 +418,7 @@ function buildBlockers({ binaries, kubeconfig, sshBridge, cluster }) {
nextTask: "Mount a read-only kubeconfig for hwlab-dev, or document the approved k3s local kubeconfig path without exposing token material."
});
}
if (!sshBridge.configured) {
if (!sshBridge.configured && !cluster.readable) {
addBlocker(blockers, {
type: "environment_blocker",
scope: "d601-maintenance-ssh-bridge",
+239 -20
View File
@@ -5,7 +5,7 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { activeReportLifecycle, reportIsHistorical, reportLifecycleState } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
@@ -42,6 +42,7 @@ const reportPaths = Object.freeze({
devDeploy: "reports/dev-gate/dev-deploy-report.json",
devArtifacts: "reports/dev-gate/dev-artifacts.json",
devEdgeHealth: "reports/dev-gate/dev-edge-health.json",
devM2Smoke: "reports/dev-gate/dev-m2-deploy-smoke-active.json",
devM3Hardware: "reports/dev-gate/dev-m3-hardware-loop.json",
devM4Agent: "reports/dev-gate/dev-m4-agent-loop.json",
devM5Gate: "reports/dev-gate/dev-mvp-gate-report.json",
@@ -189,6 +190,19 @@ function hasBlocker(blockers, predicate) {
return blockers.some(predicate);
}
function isRouteTransportBlocker(blocker) {
const scope = blocker.scope ?? "";
return scope === "m2-public-endpoints" ||
scope === "dev-edge" ||
scope === "dev-edge-health" ||
scope === "hwlab-router" ||
scope === "hwlab-tunnel-client" ||
scope === "hwlab-edge-proxy" ||
scope.includes("edge-frp") ||
scope.includes("ingress") ||
scope.includes("frp");
}
function applyPriority(blocker) {
if (blocker.scope === "base-image") {
return {
@@ -234,6 +248,16 @@ function applyPriority(blocker) {
};
}
if (blocker.scope === "hwlab-dev-readonly-rbac") {
return {
...blocker,
priority: "P1",
unblockOrder: 4,
unblocks: [issue(34), issue(33), issue(36), issue(38), issue(39)],
rationale: "D601 has a client path, but read-only hwlab-dev pods/services/configmaps must be observable before live acceptance evidence can be trusted."
};
}
if (blocker.scope.includes("cloud-api-db")) {
return {
...blocker,
@@ -244,6 +268,16 @@ function applyPriority(blocker) {
};
}
if (blocker.scope === "db-live") {
return {
...blocker,
priority: "P1",
unblockOrder: 5,
unblocks: [issue(37), issue(39)],
rationale: "M4 and M5 cannot claim live agent or MVP evidence until cloud-api /health/live proves DB readiness with redacted live evidence."
};
}
if (isEdgeOrFrpBlocker(blocker)) {
return {
...blocker,
@@ -254,6 +288,16 @@ function applyPriority(blocker) {
};
}
if (blocker.scope === "m3-hardware-loop-runtime") {
return {
...blocker,
priority: "P0",
unblockOrder: 6,
unblocks: [issue(38), issue(39), issue(64)],
rationale: "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers."
};
}
return {
...blocker,
priority: blocker.priority ?? "P3",
@@ -264,11 +308,7 @@ function applyPriority(blocker) {
}
function isEdgeOrFrpBlocker(blocker) {
return blocker.scope.includes("edge") ||
blocker.scope.includes("ingress") ||
blocker.scope.includes("frp") ||
blocker.summary.includes(":16667") ||
blocker.summary.includes("16667");
return isRouteTransportBlocker(blocker);
}
function sourceEntry(entry) {
@@ -309,9 +349,11 @@ function collectM2Evidence(reports) {
const deploy = reports.devDeploy;
const artifacts = reports.devArtifacts;
const edge = reports.devEdgeHealth;
const m2Smoke = reports.devM2Smoke;
const d601 = reports.d601Observability;
const artifactIdentity = preflight.artifactIdentity;
const edgeLive = hasCurrentLiveEdgeEvidence(edge);
const m2EndpointLive = hasCurrentM2EndpointEvidence(m2Smoke);
return [
reportEvidence({
@@ -352,6 +394,18 @@ function collectM2Evidence(reports) {
commands: artifacts.validationCommands,
summary: "Artifact publish preflight exists but is blocked before any real image publish."
}),
reportEvidence({
milestone: "M2",
level: m2EndpointLive ? "DEV-LIVE" : "BLOCKED",
report: m2Smoke,
status: m2Smoke.runtimeSmoke?.status ?? primaryStatus(m2Smoke),
category: "public-entrypoints-read-only",
evidence: publicEndpointEvidence(m2Smoke),
commands: m2Smoke.devPreconditions?.commands ?? [],
summary: m2EndpointLive
? "Read-only probes prove the frozen public DEV entrypoints on :16666/:16667 are reachable; this is EDGE/ROUTE evidence only."
: "The active M2 public endpoint smoke does not prove the frozen :16666/:16667 entrypoints."
}),
reportEvidence({
milestone: "M2",
level: edgeLive ? "DEV-LIVE" : "SOURCE",
@@ -388,6 +442,7 @@ function collectM2Evidence(reports) {
function collectM3Evidence(reports) {
const m3 = reports.devM3Hardware;
const m5Milestone = reports.devM5Gate.milestones?.find((item) => item.id === "M3");
const liveSummary = m3TrustedLoopSummary(m3);
return [
reportEvidence({
milestone: "M3",
@@ -422,11 +477,20 @@ function collectM3Evidence(reports) {
`evidenceId=${m3.liveOperation?.evidenceId ?? "not_observed"}`
],
commands: ["node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"],
summary: m3.liveOperation?.summary ?? "No live M3 hardware operation was observed."
summary: liveSummary
})
];
}
function m3TrustedLoopSummary(m3Report) {
const operationSummary = m3Report.liveOperation?.summary ?? "No live M3 hardware operation was observed.";
const blockerSummary = m3Report.blockers?.find((blocker) => blocker.scope === "m3-hardware-loop-runtime")?.summary;
if (statusIsPass(m3Report.liveOperation?.status) || !blockerSummary) {
return operationSummary;
}
return `${operationSummary} Blocker: ${blockerSummary}.`;
}
function collectM4Evidence(reports) {
const m4 = reports.devM4Agent;
return [
@@ -537,6 +601,25 @@ function hasCurrentLiveEdgeEvidence(edgeReport) {
publicHttp.some((probe) => probe.url === `${DEV_ENDPOINT}/health/live` && probe.ok === true && probe.status === 200);
}
function hasCurrentM2EndpointEvidence(m2Report) {
const probes = m2Report?.runtimeSmoke?.probes ?? [];
return m2Report?.endpoint === DEV_ENDPOINT &&
m2Report?.frontendEndpoint === DEV_FRONTEND_ENDPOINT &&
m2Report?.runtimeSmoke?.mode === "live-read-only" &&
m2Report?.runtimeSmoke?.status === "pass" &&
probes.some((probe) => probe.url === `${DEV_ENDPOINT}/health` && probe.ok === true && probe.status === 200) &&
probes.some((probe) => probe.url === `${DEV_ENDPOINT}/health/live` && probe.ok === true && probe.status === 200) &&
probes.some((probe) => probe.url === `${DEV_FRONTEND_ENDPOINT}/` && probe.ok === true && probe.status === 200);
}
function publicEndpointEvidence(m2Report) {
return (m2Report?.runtimeSmoke?.probes ?? []).map((probe) => {
const identity = probe.json?.serviceId ?? probe.title ?? "unknown";
const status = probe.json?.status ?? (probe.ok ? "ok" : "failed");
return `${probe.url} -> HTTP ${probe.status ?? "none"} identity=${identity} status=${status}`;
});
}
function isStaleLegacyIngressBlocker(blocker, sourceReport, reports) {
const text = `${blocker.scope ?? ""} ${blocker.summary ?? ""}`;
const staleLegacyPort = text.includes(deprecatedLegacyPublicEndpoint);
@@ -571,6 +654,7 @@ function deriveMilestones(evidence, blockers) {
const liveItems = items.filter((item) => item.level === "DEV-LIVE");
const highestVisibleLevel = levels.findLast((level) => items.some((item) => item.level === level && level !== "BLOCKED" && statusHasEvidence(item.status))) ?? "BLOCKED";
const hasPassingLive = liveItems.some((item) => statusIsPass(item.status));
const hasBlockedEvidence = items.some((item) => item.level === "BLOCKED" && ["blocked", "failed", "degraded"].includes(item.status));
const milestoneBlockers = blockers.filter((blocker) => {
if (milestone === "M2") return blocker.unblocks.includes(issue(33)) || blocker.unblocks.includes(issue(35)) || blocker.unblocks.includes(issue(36));
if (milestone === "M3") return blocker.unblocks.includes(issue(38));
@@ -578,7 +662,7 @@ function deriveMilestones(evidence, blockers) {
if (milestone === "M5") return blocker.unblocks.includes(issue(39));
return false;
});
const status = milestoneBlockers.length > 0 || (liveItems.length > 0 && !hasPassingLive)
const status = milestoneBlockers.length > 0 || hasBlockedEvidence || (liveItems.length > 0 && !hasPassingLive)
? "blocked"
: worstStatus(items.map((item) => item.status));
@@ -642,7 +726,16 @@ function buildDoD(reports, milestones, blockers) {
const preflight = reports.devPreflight;
const artifactIdentity = preflight.artifactIdentity;
const d601 = reports.d601Observability;
const d601HasClient = d601.environment?.binaries?.kubectl?.available === true ||
d601.environment?.binaries?.k3s?.available === true;
const edgeLive = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth);
const artifactCurrent = artifactIdentity.publishVerified === true &&
artifactIdentity.targetCoverage?.covered !== false &&
artifactIdentity.artifactCatalog?.matchesTarget !== false;
const cloudApiDb = cloudApiDbStatus(reports);
const dbReady = cloudApiDb.ready === true && cloudApiDb.connected === true && cloudApiDb.liveDbEvidence === true;
const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status);
const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status);
return {
status: blockers.length === 0 && milestones.every((item) => item.status === "pass") ? "green" : "blocked",
@@ -662,9 +755,9 @@ function buildDoD(reports, milestones, blockers) {
},
{
id: "artifact-publish-digests",
status: artifactIdentity.publishVerified ? "pass" : "blocked",
evidenceLevel: artifactIdentity.publishVerified ? "DEV-LIVE" : "BLOCKED",
summary: `artifactState=${artifactIdentity.artifactCatalog.artifactState}, ciPublished=${artifactIdentity.artifactCatalog.ciPublished}, registryVerified=${artifactIdentity.artifactCatalog.registryVerified}, sha256=${artifactIdentity.artifactCatalog.digestCounts.sha256}, not_published=${artifactIdentity.artifactCatalog.digestCounts.notPublished}`
status: artifactCurrent ? "pass" : "blocked",
evidenceLevel: artifactCurrent ? "DEV-LIVE" : "BLOCKED",
summary: `artifactState=${artifactIdentity.artifactCatalog.artifactState}, ciPublished=${artifactIdentity.artifactCatalog.ciPublished}, registryVerified=${artifactIdentity.artifactCatalog.registryVerified}, sha256=${artifactIdentity.artifactCatalog.digestCounts.sha256}, not_published=${artifactIdentity.artifactCatalog.digestCounts.notPublished}, targetCovered=${artifactIdentity.targetCoverage?.covered ?? "unknown"}`
},
{
id: "d601-k3s-observability",
@@ -672,21 +765,39 @@ function buildDoD(reports, milestones, blockers) {
evidenceLevel: d601.cluster?.readable ? "DEV-LIVE" : "BLOCKED",
summary: d601.cluster?.readable
? "D601 hwlab-dev cluster was readable."
: "D601 runner lacks kubectl/k3s/kubeconfig observability for hwlab-dev."
: d601HasClient
? "D601 kubectl/k3s clients are present, but read-only hwlab-dev pods/services/configmaps probes are still blocked."
: "D601 runner lacks kubectl/k3s/kubeconfig observability for hwlab-dev."
},
{
id: "dev-edge-frp-16667",
status: edgeLive && !hasBlocker(blockers, isEdgeOrFrpBlocker) ? "pass" : "blocked",
evidenceLevel: edgeLive && !hasBlocker(blockers, isEdgeOrFrpBlocker) ? "DEV-LIVE" : "BLOCKED",
status: edgeLive && !hasBlocker(blockers, isRouteTransportBlocker) ? "pass" : "blocked",
evidenceLevel: edgeLive && !hasBlocker(blockers, isRouteTransportBlocker) ? "DEV-LIVE" : "BLOCKED",
summary: edgeLive
? "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; old preflight network probe blockers are stale."
? "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; this is route evidence, not DB/M3/M4/M5 acceptance."
: "No committed report proves live HTTP 200/JSON on http://74.48.78.17:16667."
},
{
id: "cloud-api-db-ready",
status: hasBlocker(blockers, (blocker) => blocker.scope.includes("cloud-api-db")) ? "blocked" : "pass",
evidenceLevel: "BLOCKED",
summary: "Manifest-level DB env exists, but live DB health readiness is still blocked/missing."
status: dbReady ? "pass" : "blocked",
evidenceLevel: dbReady ? "DEV-LIVE" : "BLOCKED",
summary: `cloud-api DB status=${cloudApiDb.status}; ready=${cloudApiDb.ready}; connected=${cloudApiDb.connected}; liveDbEvidence=${cloudApiDb.liveDbEvidence}.`
},
{
id: "m3-hardware-trusted-loop",
status: m3Live ? "pass" : "blocked",
evidenceLevel: m3Live ? "DEV-LIVE" : "BLOCKED",
summary: m3Live
? "M3 hardware trusted loop has operation, trace, audit, and evidence identifiers."
: "M3 trusted loop is blocked until res_boxsimu_1:DO1 -> patch-panel -> res_boxsimu_2:DI1 is proven with operation/trace/audit/evidence."
},
{
id: "m4-agent-loop-live",
status: m4Live ? "pass" : "blocked",
evidenceLevel: m4Live ? "DEV-LIVE" : "BLOCKED",
summary: m4Live
? "M4 agent loop live preflight passed."
: "M4 agent loop live path is blocked before accepted agent scheduling/evidence closure."
},
{
id: "m5-mvp-dev-live",
@@ -698,6 +809,99 @@ function buildDoD(reports, milestones, blockers) {
};
}
function cloudApiDbStatus(reports) {
const db = reports.devEdgeHealth.edgeHealth?.contracts?.deploy?.cloudApiDb ?? {};
return {
status: db.status ?? "unknown",
ready: db.ready === true,
connected: db.connected === true || db.liveConnected === true,
configReady: db.configReady === true,
connectionChecked: db.connectionChecked === true,
liveDbEvidence: db.liveDbEvidence === true
};
}
function buildCurrentDevLayering(reports, blockers) {
const m2EndpointLive = hasCurrentM2EndpointEvidence(reports.devM2Smoke);
const edgeLive = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth);
const cloudDb = cloudApiDbStatus(reports);
const dbReady = cloudDb.ready && cloudDb.connected && cloudDb.liveDbEvidence;
const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status);
const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status);
const artifactIdentity = reports.devPreflight.artifactIdentity;
const artifactCurrent = artifactIdentity.publishVerified === true &&
artifactIdentity.targetCoverage?.covered !== false &&
artifactIdentity.artifactCatalog?.matchesTarget !== false;
const desired = reports.devDeploy.devDeployApply ?? {};
const artifactSourceStates = artifactSourceStateCounts(reports.devArtifacts);
return {
edgeRoute: {
label: "EDGE/ROUTE live",
status: m2EndpointLive || edgeLive ? "pass" : "blocked",
evidenceLevel: m2EndpointLive || edgeLive ? "DEV-LIVE" : "BLOCKED",
summary: m2EndpointLive
? "16666 browser, 16667 /health, and 16667 /health/live returned accepted HWLAB DEV responses in the active M2 read-only smoke."
: "No active read-only public endpoint report proves both 16666 and 16667.",
evidence: publicEndpointEvidence(reports.devM2Smoke),
nextRequired: "Keep this separated from DB readiness, M3/M4 loop evidence, and M5 acceptance."
},
dbLive: {
label: "DB live/degraded",
status: dbReady ? "pass" : "blocked",
evidenceLevel: dbReady ? "DEV-LIVE" : "BLOCKED",
summary: `cloud-api DB status=${cloudDb.status}; configReady=${cloudDb.configReady}; ready=${cloudDb.ready}; connected=${cloudDb.connected}; liveDbEvidence=${cloudDb.liveDbEvidence}.`,
evidence: reports.devM5Gate.devPreconditions?.evidence?.filter((line) => line.includes("/health/live") || line.includes("DB")) ?? [],
nextRequired: "Provide live DB connection evidence through redacted health output; route reachability alone is insufficient."
},
m3HardwareTrustedLoop: {
label: "M3 hardware trusted loop",
status: m3Live ? "pass" : "blocked",
evidenceLevel: m3Live ? "DEV-LIVE" : "BLOCKED",
summary: m3TrustedLoopSummary(reports.devM3Hardware),
evidence: [
`operationId=${reports.devM3Hardware.liveOperation?.operationId ?? "not_observed"}`,
`traceId=${reports.devM3Hardware.liveOperation?.traceId ?? "not_observed"}`,
`auditId=${reports.devM3Hardware.liveOperation?.auditId ?? "not_observed"}`,
`evidenceId=${reports.devM3Hardware.liveOperation?.evidenceId ?? "not_observed"}`
],
nextRequired: "Only a real DEV res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 observation with operation/trace/audit/evidence can clear M3."
},
m4AgentLoop: {
label: "M4 agent loop",
status: m4Live ? "pass" : "blocked",
evidenceLevel: m4Live ? "DEV-LIVE" : "BLOCKED",
summary: reports.devM4Agent.livePreflight?.summary ?? reports.devM4Agent.devPreconditions?.summary ?? "No live M4 observation was recorded.",
evidence: reports.devM4Agent.livePreflight?.evidence ?? [],
nextRequired: "Do not schedule or claim the agent loop as live until DB live and required runtime/evidence preconditions pass."
},
artifactDesiredStateSource: {
label: "artifact/desired-state source",
status: artifactCurrent && desired.conclusion?.status === "ready" ? "pass" : "blocked",
evidenceLevel: artifactCurrent ? "SOURCE" : "BLOCKED",
summary: `artifact targetCovered=${artifactIdentity.targetCoverage?.covered ?? "unknown"}; artifactSource=${short(artifactIdentity.artifactSource?.commitId)}; target=${short(artifactIdentity.source?.commitId)}; desiredApplyMode=${desired.mode ?? "unknown"}; mutationAttempted=${desired.mutationAttempted === true}.`,
evidence: [
`artifactState=${artifactIdentity.artifactCatalog?.artifactState ?? "unknown"}`,
`sourceStates=${artifactSourceStates.sourcePresent} source-present, ${artifactSourceStates.intentionallyDisabled} intentionally-disabled`,
`desiredState=${desired.conclusion?.status ?? "not_reported"} dry-run-only`
],
nextRequired: "Refresh artifact/source coverage for current origin/main and keep desired-state apply separate from read-only route proof."
}
};
}
function artifactSourceStateCounts(artifactReport) {
const counts = { sourcePresent: 0, intentionallyDisabled: 0 };
for (const service of artifactReport.artifactPublish?.services ?? []) {
if (service.sourceState === "source-present") {
counts.sourcePresent += 1;
} else if (service.sourceState === "intentionally-disabled") {
counts.intentionallyDisabled += 1;
}
}
return counts;
}
function buildNextSteps(blockers) {
const byOrder = new Map();
for (const blocker of blockers) {
@@ -737,6 +941,8 @@ function fallbackAction(scope) {
if (scope.includes("artifact")) return "Publish DEV artifacts for every frozen HWLAB service and record immutable registry digests.";
if (scope.includes("edge") || scope.includes("ingress") || scope.includes("frp")) return "Repair frp/master-edge/D601 router path and rerun read-only DEV edge health.";
if (scope.includes("cloud-api-db")) return "Configure DEV cloud-api DB env readiness and rerun health/preflight without exposing secrets.";
if (scope === "db-live") return "Repair DEV cloud-api DB live readiness, then rerun the read-only health and M4 preflight reports without exposing secret values.";
if (scope === "m3-hardware-loop-runtime") return "Prove the real DEV M3 trusted loop res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 with operation, trace, audit, and evidence identifiers.";
if (scope.includes("kubectl") || scope.includes("k3s")) return "Provide read-only kubectl/kubeconfig observability for hwlab-dev.";
return "Resolve the blocker and attach source/local/dry-run/DEV-live evidence at the correct level.";
}
@@ -747,6 +953,8 @@ function evidenceRequiredFor(scope) {
if (scope.includes("artifact") || scope === "ghcr") return "Artifact publish report with ciPublished=true, registryVerified=true, and sha256 digest for each frozen service ID.";
if (scope.includes("kubectl") || scope.includes("k3s")) return "Read-only kubectl/k3s report proving pods/services/configmaps are observable in hwlab-dev without reading Secrets.";
if (scope.includes("cloud-api-db")) return "Cloud API health/live output showing DB env ready and redacted secret references, without secret material.";
if (scope === "db-live") return "Cloud API /health/live output with ready=true, connected=true, liveDbEvidence=true, and redacted secret references.";
if (scope === "m3-hardware-loop-runtime") return "Operation, trace, audit, and evidence IDs from the real DEV DO1 -> patch-panel -> DI1 trusted loop.";
if (scope.includes("edge") || scope.includes("ingress") || scope.includes("frp")) return "Read-only DEV route observation for :16667/frp/edge/router with HWLAB service identity and artifact identity.";
return "A committed report with the exact evidence level and command used.";
}
@@ -787,6 +995,7 @@ export async function buildReport() {
const blockers = collectBlockers(reports);
const milestones = deriveMilestones(evidence, blockers);
const dod = buildDoD(reports, milestones, blockers);
const currentDevLayering = buildCurrentDevLayering(reports, blockers);
const nextSteps = buildNextSteps(blockers);
const report = {
"$schema": "https://hwlab.pikastech.local/schemas/dev-m5-gate-aggregator-v2.schema.json",
@@ -794,7 +1003,7 @@ export async function buildReport() {
reportVersion: "v2",
reportKind: "dev-m5-gate-aggregator",
issue: issue(58),
supports: [issue(7), issue(9), issue(31), issue(33), issue(34), issue(35), issue(36), issue(37), issue(38), issue(39), issue(46)],
supports: [issue(7), issue(9), issue(23), issue(26), issue(31), issue(33), issue(34), issue(35), issue(36), issue(37), issue(38), issue(39), issue(46), issue(64)],
generatedAt: new Date().toISOString(),
generatedFromCommit: gitCommit(),
environment: ENVIRONMENT_DEV,
@@ -832,9 +1041,10 @@ export async function buildReport() {
green: dod.green,
reason: dod.green
? "All #9 DEV DoD checks are green."
: "SOURCE, LOCAL, DRY-RUN, and edge DEV-LIVE evidence exists, but acceptance is still blocked by artifact, observability, DB, and loop evidence gaps."
: "EDGE/ROUTE DEV-LIVE evidence exists on :16666/:16667, but M5 remains blocked by artifact source drift, DB live degradation, missing M3 trusted loop operation evidence, and blocked M4 agent-loop preflight."
},
dod,
currentDevLayering,
milestones,
evidence,
levels: groupByLevel(evidence, blockers),
@@ -862,6 +1072,9 @@ export function formatCheckSummary(report) {
}
export function renderMarkdown(report) {
const layeringLines = Object.entries(report.currentDevLayering)
.map(([, layer]) => `| ${layer.label} | ${layer.status} | ${layer.evidenceLevel} | ${layer.summary} | ${layer.nextRequired} |`)
.join("\n");
const milestoneLines = report.milestones
.map((milestone) => `| ${milestone.id} | ${milestone.status} | ${milestone.highestVisibleLevel} | ${milestone.liveEvidence} | ${milestone.summary} |`)
.join("\n");
@@ -885,6 +1098,12 @@ Scope: DEV only, report-only
${report.overall.reason}
## Current DEV Layering
| Layer | Status | Evidence level | Current conclusion | Required next proof |
| --- | --- | --- | --- | --- |
${layeringLines}
## Milestones
| Milestone | Status | Highest visible level | Live evidence | Summary |