From 4e302faee961b87b914aa0c11f53cb6379954faf Mon Sep 17 00:00:00 2001 From: Code Queue Review Date: Sun, 24 May 2026 02:23:27 +0000 Subject: [PATCH] Remove repo reports and add recurrence guard --- AGENTS.md | 1 + CI.json | 4 + README.md | 2 +- deploy/README.md | 2 +- deploy/artifact-catalog.dev.json | 4 +- docs/artifact-catalog.md | 2 +- docs/cloud-web-workbench.md | 2 +- docs/d601-k3s-readonly-observability.md | 4 +- docs/dev-artifact-publish.md | 8 +- docs/dev-base-image-preflight.md | 2 +- docs/dev-deploy-apply.md | 22 +- docs/dev-edge-health.md | 6 +- docs/dev-evidence-blocker-aggregator.md | 24 +- docs/dev-gate-preflight.md | 14 +- docs/dev-gate-report.md | 12 +- docs/m2-dev-deploy-smoke.md | 6 +- docs/m3-hardware-loop.md | 4 +- docs/m5-mvp-e2e.md | 2 +- docs/operator-runbook.md | 8 +- docs/reference/MVP-e2e-acceptance.md | 2 +- docs/reference/cloud-workbench.md | 2 +- docs/reference/deployment-publish.md | 42 +- docs/reference/dev-runtime-boundary.md | 22 +- internal/cloud/server.mjs | 9 +- internal/mvp-gate/summary.mjs | 174 +- package.json | 14 +- reports/d601-k3s-readonly-observability.json | 710 -- reports/dev-gate/dev-artifacts-hwlab-224.json | 829 -- reports/dev-gate/dev-artifacts-hwlab-277.json | 733 -- reports/dev-gate/dev-artifacts.json | 1309 --- reports/dev-gate/dev-cd-apply.json | 7378 ----------------- .../dev-cloud-workbench-dom-only.json | 551 -- .../dev-gate/dev-cloud-workbench-live.json | 316 - reports/dev-gate/dev-deploy-report.json | 1799 ---- reports/dev-gate/dev-edge-health.json | 978 --- reports/dev-gate/dev-gate-report.example.json | 81 - .../dev-gate/dev-m2-deploy-smoke-active.json | 264 - reports/dev-gate/dev-m3-hardware-loop.json | 728 -- reports/dev-gate/dev-m4-agent-loop.json | 593 -- .../dev-gate/dev-m5-gate-aggregator-v2.json | 1539 ---- reports/dev-gate/dev-m5-gate-aggregator-v2.md | 118 - reports/dev-gate/dev-mvp-gate-report.json | 283 - reports/dev-gate/dev-mvp-gate-report.md | 63 - reports/dev-gate/dev-preflight-report.json | 1704 ---- .../dev-runtime-postflight-report.json | 467 -- .../dev-m3-hardware-loop-legacy-6667.json | 37 - scripts/deploy-desired-state-plan.test.mjs | 1 - scripts/dev-artifact-publish.mjs | 12 +- scripts/dev-cloud-workbench-layout-smoke.mjs | 5 +- scripts/dev-cloud-workbench-smoke.mjs | 5 +- scripts/dev-cloud-workbench-smoke.test.mjs | 24 +- scripts/dev-evidence-blocker-aggregator.mjs | 18 +- scripts/dev-m3-hardware-loop-smoke.mjs | 14 +- scripts/dev-m4-agent-loop-smoke.mjs | 7 +- scripts/export-web-gate-summary.mjs | 4 +- scripts/m2-dev-deploy-smoke.mjs | 18 +- scripts/refresh-artifact-catalog.mjs | 5 +- scripts/repo-reports-guard.mjs | 59 + .../src/artifact-runtime-readiness-guard.mjs | 23 +- .../src/d601-k3s-readonly-observability.mjs | 7 +- scripts/src/deploy-desired-state-plan.mjs | 5 +- scripts/src/dev-cd-apply.mjs | 63 +- scripts/src/dev-cd-apply.test.mjs | 58 +- scripts/src/dev-cd-transaction-guard.mjs | 4 +- scripts/src/dev-cloud-workbench-smoke-lib.mjs | 23 +- scripts/src/dev-deploy-apply.mjs | 43 +- scripts/src/dev-edge-health-smoke-lib.mjs | 17 +- .../src/dev-evidence-blocker-aggregator.mjs | 1735 +--- .../dev-evidence-blocker-aggregator.test.mjs | 25 +- scripts/src/dev-gate-preflight.mjs | 29 +- scripts/src/dev-runtime-migration.mjs | 8 +- scripts/src/dev-runtime-postflight.mjs | 8 +- scripts/src/dev-runtime-provisioning.mjs | 8 +- scripts/src/report-paths.mjs | 22 + scripts/src/rpt004-mvp-e2e-harness.mjs | 18 +- scripts/src/rpt004-mvp-e2e-harness.test.mjs | 2 +- .../validate-dev-gate-report-guard.test.mjs | 946 --- scripts/validate-dev-gate-report.mjs | 3901 +-------- scripts/validate-m2-deploy-smoke-active.mjs | 3 +- scripts/validate-runtime-boundary.mjs | 12 +- tools/hwlab-cli/lib/cli.mjs | 2 +- web/hwlab-cloud-web/gate-summary.mjs | 105 +- web/hwlab-cloud-web/package.json | 6 +- 83 files changed, 1042 insertions(+), 27077 deletions(-) delete mode 100644 reports/d601-k3s-readonly-observability.json delete mode 100644 reports/dev-gate/dev-artifacts-hwlab-224.json delete mode 100644 reports/dev-gate/dev-artifacts-hwlab-277.json delete mode 100644 reports/dev-gate/dev-artifacts.json delete mode 100644 reports/dev-gate/dev-cd-apply.json delete mode 100644 reports/dev-gate/dev-cloud-workbench-dom-only.json delete mode 100644 reports/dev-gate/dev-cloud-workbench-live.json delete mode 100644 reports/dev-gate/dev-deploy-report.json delete mode 100644 reports/dev-gate/dev-edge-health.json delete mode 100644 reports/dev-gate/dev-gate-report.example.json delete mode 100644 reports/dev-gate/dev-m2-deploy-smoke-active.json delete mode 100644 reports/dev-gate/dev-m3-hardware-loop.json delete mode 100644 reports/dev-gate/dev-m4-agent-loop.json delete mode 100644 reports/dev-gate/dev-m5-gate-aggregator-v2.json delete mode 100644 reports/dev-gate/dev-m5-gate-aggregator-v2.md delete mode 100644 reports/dev-gate/dev-mvp-gate-report.json delete mode 100644 reports/dev-gate/dev-mvp-gate-report.md delete mode 100644 reports/dev-gate/dev-preflight-report.json delete mode 100644 reports/dev-gate/dev-runtime-postflight-report.json delete mode 100644 reports/dev-gate/historical/dev-m3-hardware-loop-legacy-6667.json create mode 100644 scripts/repo-reports-guard.mjs create mode 100644 scripts/src/report-paths.mjs delete mode 100644 scripts/validate-dev-gate-report-guard.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 5e2c6435..941632e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - D601 发布/构建工作区是 `/home/ubuntu/workspace/hwlab`;不要把 runner 临时目录当作发布真相,详见 [docs/reference/deployment-publish.md](docs/reference/deployment-publish.md)。 - 当前一律走 PR 工作流;不要直推 `main`、不要合并自己的 PR、不要改 PROD、不要重启服务。 - `DC-DCSN-P0-2026-003` / [pikasTech/HWLAB#78](https://github.com/pikasTech/HWLAB/issues/78) 是当前 M3 虚拟硬件可信闭环的上位约束;其他任务不得把 SOURCE、LOCAL、DRY-RUN、fixture 或前端状态误报为 M3 DEV-LIVE。 +- 仓库禁止创建或提交 repo report 目录;验收、进展和结论只承载在 #7、专题 issue、每日简报或 PR/issue 评论。临时 JSON 只能写入 `/tmp`、`.state` 或 CI artifact,不能进入源码仓库。 ## 固定入口 diff --git a/CI.json b/CI.json index 46a7b47f..a0b7a567 100644 --- a/CI.json +++ b/CI.json @@ -3,6 +3,10 @@ "name": "hwlab-deploy-skeleton-ci", "profile": "dev", "commands": [ + { + "name": "repo-reports-guard", + "run": "node scripts/repo-reports-guard.mjs" + }, { "name": "contract-check", "run": "npm run check" diff --git a/README.md b/README.md index c374f572..d1e6da23 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ browser entry remains on public `:16666`, API/edge diagnostics remain on public unless the real `DO1 -> hwlab-patch-panel -> DI1` DEV-LIVE evidence exists. The CLI command defaults to a JSON dry-run plan sourced from -`reports/dev-gate/dev-mvp-gate-report.json` and +`GitHub issue/PR comments` and `fixtures/mvp/m5-e2e/dry-run-plan.json`. A live DEV attempt requires `--live --confirm-dev --confirmed-non-production`; with open gate blockers it returns `BLOCKED` evidence rather than using fixtures or UniDesk runtime as a diff --git a/deploy/README.md b/deploy/README.md index c261dede..2309a36f 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -106,7 +106,7 @@ npm run check node -e "JSON.parse(require('node:fs').readFileSync('deploy/deploy.json','utf8'))" KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply --dry-run=server -k deploy/k8s/dev curl -fsS http://74.48.78.17:16667/health/live -node scripts/dev-edge-health-smoke.mjs --live --write-report +node scripts/dev-edge-health-smoke.mjs --live --report /tmp/hwlab-dev-gate/report.json ``` Do not run PROD deployment or substitute UniDesk backend, provider-gateway, or diff --git a/deploy/artifact-catalog.dev.json b/deploy/artifact-catalog.dev.json index ce1c8358..e4650ee2 100644 --- a/deploy/artifact-catalog.dev.json +++ b/deploy/artifact-catalog.dev.json @@ -10,8 +10,8 @@ "publish": { "ciPublished": true, "registryVerified": true, - "provenance": "reports/dev-gate/dev-artifacts.json", - "note": "Digest fields were copied from a successful DEV artifact publish report for this source commit." + "provenance": "ci-artifact:dev-artifacts.json", + "note": "Digest fields were copied from a successful DEV artifact publish artifact for this source commit; repository report files are not used." }, "healthContract": { "method": "GET", diff --git a/docs/artifact-catalog.md b/docs/artifact-catalog.md index f4c68e06..cd1c0f4c 100644 --- a/docs/artifact-catalog.md +++ b/docs/artifact-catalog.md @@ -128,7 +128,7 @@ After a successful DEV artifact publish, record digests only from the publish report: ```sh -node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json +node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report /tmp/hwlab-dev-gate/dev-artifacts.json node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check ``` diff --git a/docs/cloud-web-workbench.md b/docs/cloud-web-workbench.md index b3fdc632..a041662f 100644 --- a/docs/cloud-web-workbench.md +++ b/docs/cloud-web-workbench.md @@ -78,7 +78,7 @@ must keep these data sources separate: | Same-origin `/v1` | Cloud API summaries for status, agents, records, health, artifact identity, and read-only workbench aggregation. | `GET` or documented read-only RPC wrappers through the same origin as the web bundle. | `DEV-LIVE` only when observed from the real DEV route; otherwise `BLOCKED` or `SOURCE`. | | Read-only `/json-rpc` | Hardware/gateway/patch-panel status, capabilities, topology, and evidence lookups already defined as safe reads. | Only list/status/get/describe methods. No generic write, no direct box mutation, no bypass of patch-panel ownership. | `DEV-LIVE` for read-only real DEV observations; `DRY-RUN` for fixture-backed previews. | | Source report | Checked-in contracts, manifests, schemas, service IDs, and documentation. | Static fetch or bundled build asset only. | `SOURCE`. | -| Gate report | `reports/dev-gate` gate summaries, DEV layer status, artifact identity, and M3/M4/M5 readiness. | Read-only local report content or same-origin report endpoint. | `SOURCE`, `DRY-RUN`, `DEV-LIVE`, or `BLOCKED` exactly as reported. | +| Gate report | `/tmp/hwlab-dev-gate` gate summaries, DEV layer status, artifact identity, and M3/M4/M5 readiness. | Read-only local report content or same-origin report endpoint. | `SOURCE`, `DRY-RUN`, `DEV-LIVE`, or `BLOCKED` exactly as reported. | | Blocked report | Open blockers for DB, M3, M4, edge/route, artifacts, registry, and evidence gaps. | Read-only report content or same-origin summary endpoint. | `BLOCKED`. | The frontend must not infer `DEV-LIVE` from successful page load on `:16666`. diff --git a/docs/d601-k3s-readonly-observability.md b/docs/d601-k3s-readonly-observability.md index 4de653a0..dbabf5f7 100644 --- a/docs/d601-k3s-readonly-observability.md +++ b/docs/d601-k3s-readonly-observability.md @@ -18,7 +18,7 @@ node --check scripts/src/d601-k3s-readonly-observability.mjs node scripts/d601-k3s-readonly-observability.mjs ``` -The command writes `reports/d601-k3s-readonly-observability.json` and prints a +The command writes `/tmp/hwlab-dev-gate/d601-k3s-readonly-observability.json` and prints a short JSON summary. A `blocked` conclusion means the preflight ran and recorded the missing binary, kubeconfig path, SSH bridge, or read permission. Use `--fail-on-blocked` only in CI jobs that should fail when observability is not @@ -30,7 +30,7 @@ Useful options: node scripts/d601-k3s-readonly-observability.mjs --pretty node scripts/d601-k3s-readonly-observability.mjs --timeout-ms 5000 node scripts/d601-k3s-readonly-observability.mjs --no-write -node scripts/d601-k3s-readonly-observability.mjs --report reports/d601-k3s-readonly-observability.json +node scripts/d601-k3s-readonly-observability.mjs --report /tmp/hwlab-dev-gate/d601-k3s-readonly-observability.json ``` ## What It Checks diff --git a/docs/dev-artifact-publish.md b/docs/dev-artifact-publish.md index 86b3891c..aff1911c 100644 --- a/docs/dev-artifact-publish.md +++ b/docs/dev-artifact-publish.md @@ -65,7 +65,7 @@ Build and publish to the D601 local/internal registry inside the DEV CD transaction: ```sh -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` The underlying single-step command is kept only for transaction internals and @@ -89,7 +89,7 @@ before image build; publish still goes through the transaction command above: ```sh node web/hwlab-cloud-web/scripts/build.mjs -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` The runtime wrapper serves `/app/web/hwlab-cloud-web/dist` before the source @@ -109,7 +109,7 @@ an approved local image for that run. ## Report -The script writes `reports/dev-gate/dev-artifacts.json`. The file is kept in the +The script writes `/tmp/hwlab-dev-gate/dev-artifacts.json`. The file is kept in the existing DEV gate report envelope for compatibility with `scripts/validate-dev-gate-report.mjs`, while the `artifactPublish` object is the HWLAB#35-specific publish record. @@ -161,7 +161,7 @@ observation, base image, registry, contract, or safety checks fail. After a fully successful publish, update the catalog from the report: ```sh -node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json +node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report /tmp/hwlab-dev-gate/dev-artifacts.json ``` If publish remains blocked, refresh only commit/tag identity and keep digests diff --git a/docs/dev-base-image-preflight.md b/docs/dev-base-image-preflight.md index afb3b98a..71fe0cb9 100644 --- a/docs/dev-base-image-preflight.md +++ b/docs/dev-base-image-preflight.md @@ -14,7 +14,7 @@ PROD, read secrets, or use UniDesk runtime services as substitutes. names to the DEV registry target, by default `127.0.0.1:5000/hwlab/:`. - The artifact catalog may be either `contract-skeleton` or `published`. A - `published` catalog must be backed by `reports/dev-gate/dev-artifacts.json` + `published` catalog must be backed by `/tmp/hwlab-dev-gate/dev-artifacts.json` with real `sha256:<64 hex>` digests for every required service. - The catalog and runtime boundary forbid PROD deployment, secret reads, force push, real publish claims, and UniDesk runtime substitution. diff --git a/docs/dev-deploy-apply.md b/docs/dev-deploy-apply.md index f37d9fbc..43f9cd7c 100644 --- a/docs/dev-deploy-apply.md +++ b/docs/dev-deploy-apply.md @@ -47,7 +47,7 @@ node --check scripts/src/dev-runtime-migration.mjs node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check node scripts/dev-runtime-migration.mjs --check node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --no-report --expect-blocked -node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report +node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report /tmp/hwlab-dev-gate/report.json node scripts/validate-dev-gate-report.mjs ``` @@ -61,14 +61,14 @@ Only after CI publish evidence and executor prerequisites exist, a real DEV CD transaction is: ```sh -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` The underlying apply backend still requires explicit non-production confirmation and a transaction environment: ```sh -node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` Direct `--apply` without `HWLAB_CD_TRANSACTION_ID` is rejected with @@ -106,7 +106,7 @@ check is safe for normal PR validation: ```sh node scripts/dev-runtime-migration.mjs --check -node scripts/dev-runtime-migration.mjs --dry-run --write-report +node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/report.json ``` Read-only live DB verification requires explicit DEV confirmation and an @@ -114,14 +114,14 @@ already injected `HWLAB_CLOUD_DB_URL`; the report redacts the value and keeps auth, schema, migration ledger, and readiness blockers separate: ```sh -node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --write-report +node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/report.json ``` A real migration write is not part of default deploy validation. It requires a separately authorized DEV operation: ```sh -node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` That command may only apply @@ -137,7 +137,7 @@ cluster pullability, DEV apply, service restart, or M3 DEV-LIVE evidence. ## Report Contract The machine-readable result is written to -`reports/dev-gate/dev-deploy-report.json` when `--write-report` is used. The +`/tmp/hwlab-dev-gate/dev-deploy-report.json` when `--report /tmp/hwlab-dev-gate/report.json` is used. The report remains compatible with the #33 DEV gate validator and adds these operator fields: @@ -183,7 +183,7 @@ When `conclusion.status` is `blocked`, operators should clear the listed `remainingBlockers` and rerun: ```sh -node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report +node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report /tmp/hwlab-dev-gate/report.json node scripts/validate-dev-gate-report.mjs ``` @@ -191,7 +191,7 @@ When `conclusion.status` is `ready`, run the dry-run plan and validator immediately before human approval: ```sh -node scripts/dev-deploy-apply.mjs --dry-run --write-report +node scripts/dev-deploy-apply.mjs --dry-run --report /tmp/hwlab-dev-gate/report.json node scripts/validate-dev-gate-report.mjs ``` @@ -221,7 +221,7 @@ freshness gates. ```sh node web/hwlab-cloud-web/scripts/check.mjs node web/hwlab-cloud-web/scripts/build.mjs -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` Probe the image locally before touching k3s: @@ -314,7 +314,7 @@ On Code Queue runners, the approved mounted kubeconfig path is typically pass `--kubeconfig` explicitly when the default D601 path is not available: ```sh -HWLAB_DEV_KUBECONFIG=/var/lib/unidesk/code-queue/kubeconfig node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report +HWLAB_DEV_KUBECONFIG=/var/lib/unidesk/code-queue/kubeconfig node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report /tmp/hwlab-dev-gate/report.json ``` The D601 runner can read and apply the `hwlab-dev` namespace. A real DEV apply diff --git a/docs/dev-edge-health.md b/docs/dev-edge-health.md index ee1dece9..0957871d 100644 --- a/docs/dev-edge-health.md +++ b/docs/dev-edge-health.md @@ -3,7 +3,7 @@ This check is DEV-only and targets the frozen endpoint: ```sh -node scripts/dev-edge-health-smoke.mjs --live --write-report +node scripts/dev-edge-health-smoke.mjs --live --report /tmp/hwlab-dev-gate/report.json ``` The smoke is read-only. It does not touch PROD, restart services, read @@ -79,7 +79,7 @@ The smoke classifies failures into these blockers: ## Machine-Readable Diagnosis Blocked `frp_blocker` and `edge_proxy_blocker` reports now include a -`diagnosis` object in `reports/dev-gate/dev-edge-health.json` with: +`diagnosis` object in `/tmp/hwlab-dev-gate/dev-edge-health.json` with: - `likelyLayer`: the most likely broken layer, such as `master-edge/frps`; - `confidence`: coarse confidence for the diagnosis; @@ -90,7 +90,7 @@ Blocked `frp_blocker` and `edge_proxy_blocker` reports now include a ## Current Blocker -The committed report at `reports/dev-gate/dev-edge-health.json` records a +The committed report at `/tmp/hwlab-dev-gate/dev-edge-health.json` records a read-only live run where public `http://74.48.78.17:16667/health/live` returned HWLAB DEV health from `hwlab-cloud-api`, DB config was present, and runtime health attempted a redacted TCP DB probe. The probe was not connected: diff --git a/docs/dev-evidence-blocker-aggregator.md b/docs/dev-evidence-blocker-aggregator.md index bbdcc85c..7a386d1e 100644 --- a/docs/dev-evidence-blocker-aggregator.md +++ b/docs/dev-evidence-blocker-aggregator.md @@ -20,13 +20,13 @@ node scripts/dev-evidence-blocker-aggregator.mjs --check To refresh the committed v2 machine and human reports: ```sh -node scripts/dev-evidence-blocker-aggregator.mjs --write-report +node scripts/dev-evidence-blocker-aggregator.mjs --report /tmp/hwlab-dev-gate/report.json ``` The generated files are: -- `reports/dev-gate/dev-m5-gate-aggregator-v2.json` -- `reports/dev-gate/dev-m5-gate-aggregator-v2.md` +- `/tmp/hwlab-dev-gate/dev-m5-gate-aggregator-v2.json` +- `/tmp/hwlab-dev-gate/dev-m5-gate-aggregator-v2.md` ## Source Levels @@ -89,15 +89,15 @@ dependency, and required next proof. The v2 report reads committed report fixtures only: -- `reports/dev-gate/dev-preflight-report.json` -- `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` +- `/tmp/hwlab-dev-gate/dev-preflight-report.json` +- `/tmp/hwlab-dev-gate/dev-deploy-report.json` +- `/tmp/hwlab-dev-gate/dev-artifacts.json` +- `/tmp/hwlab-dev-gate/dev-edge-health.json` +- `/tmp/hwlab-dev-gate/dev-m2-deploy-smoke-active.json` +- `/tmp/hwlab-dev-gate/dev-m3-hardware-loop.json` +- `/tmp/hwlab-dev-gate/dev-m4-agent-loop.json` +- `GitHub issue/PR comments` +- `/tmp/hwlab-dev-gate/d601-k3s-readonly-observability.json` The report remains blocked until real DEV-LIVE evidence is attached for the required downstream HWLAB runtime services. Public route evidence on diff --git a/docs/dev-gate-preflight.md b/docs/dev-gate-preflight.md index c8637a29..bd4fb8ef 100644 --- a/docs/dev-gate-preflight.md +++ b/docs/dev-gate-preflight.md @@ -26,7 +26,7 @@ node scripts/dev-runtime-migration.mjs --check node scripts/dev-gate-preflight.mjs ``` -The command writes `reports/dev-gate/dev-preflight-report.json` and prints a +The command writes `/tmp/hwlab-dev-gate/dev-preflight-report.json` and prints a short JSON summary. A `blocked` conclusion means the preflight ran correctly but found blockers; the command exits zero by default so the report can be committed as evidence. Use `--fail-on-blocked` only in CI jobs that should fail on an open @@ -36,7 +36,7 @@ Useful options: ```sh node scripts/dev-gate-preflight.mjs --target-ref origin/main -node scripts/dev-gate-preflight.mjs --report reports/dev-gate/dev-preflight-report.json +node scripts/dev-gate-preflight.mjs --report /tmp/hwlab-dev-gate/dev-preflight-report.json node scripts/dev-gate-preflight.mjs --timeout-ms 5000 node scripts/dev-gate-preflight.mjs --no-write ``` @@ -55,12 +55,12 @@ The preflight checks: are covered by the selected `origin/main` target. If `origin/main` is a report-only evidence commit whose changes do not touch artifact build inputs, the preflight may accept the artifact source recorded in - `reports/dev-gate/dev-artifacts.json`. + `/tmp/hwlab-dev-gate/dev-artifacts.json`. - The artifact catalog clearly separates source commit, artifact commit, `ciPublished`, `registryVerified`, and per-service digest state. - The artifact catalog has real publish and registry digest evidence, or stays blocked with `not_published` digests. -- `reports/dev-gate/dev-artifacts.json`, when present, proves all frozen DEV +- `/tmp/hwlab-dev-gate/dev-artifacts.json`, when present, proves all frozen DEV service artifacts were published for the selected target or for an artifact source commit that still covers the target. - Registry capability evidence is split into `process-http-access`, @@ -88,7 +88,7 @@ The preflight checks: - The runner can perform read-only `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl` probes against `hwlab-dev` and verify node `d601`; default `docker-desktop` kubeconfig output is not DEV evidence. -- `reports/dev-gate/dev-edge-health.json`, when present, records read-only +- `/tmp/hwlab-dev-gate/dev-edge-health.json`, when present, records read-only public edge, frps, tunnel-health, and k3s observability evidence. - `http://74.48.78.17:16667/health/live` responds. - Catalog image manifests are visible without reading credentials. A failed @@ -139,12 +139,12 @@ node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked ``` -Only use the published refresh path after `reports/dev-gate/dev-artifacts.json` +Only use the published refresh path after `/tmp/hwlab-dev-gate/dev-artifacts.json` proves every frozen service has a registry digest for that same source commit: ```sh node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty -node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json +node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report /tmp/hwlab-dev-gate/dev-artifacts.json ``` Committed DEV artifact reports can prove required service digests only for the diff --git a/docs/dev-gate-report.md b/docs/dev-gate-report.md index f1da5215..92daa883 100644 --- a/docs/dev-gate-report.md +++ b/docs/dev-gate-report.md @@ -47,8 +47,8 @@ Each committed DEV gate report must carry `reportLifecycle`: ## Files -- `reports/dev-gate/dev-gate-report.example.json`: machine-readable example. -- `reports/dev-gate/dev-deploy-report.json`: DEV deploy apply/preflight result. +- `/tmp/hwlab-dev-gate/dev-gate-report.example.json`: machine-readable example. +- `/tmp/hwlab-dev-gate/dev-deploy-report.json`: DEV deploy apply/preflight result. - `scripts/validate-dev-gate-report.mjs`: local validator for the report shape. - `scripts/report-lifecycle.mjs`: lightweight lifecycle helper for validating one report or marking a replaced report historical/deprecated after endpoint @@ -115,7 +115,7 @@ node --check scripts/validate-dev-gate-report.mjs node scripts/validate-dev-gate-report.mjs ``` -The validator scans `reports/dev-gate/*.json` by default. It only checks the +The validator scans `/tmp/hwlab-dev-gate/*.json` by default. It only checks the contract shape and the frozen report metadata; it does not run any DEV or PROD deployment. @@ -137,11 +137,11 @@ represent the active gate as historical: ```sh node --check scripts/report-lifecycle.mjs -node scripts/report-lifecycle.mjs invalidate reports/dev-gate/.json \ +node scripts/report-lifecycle.mjs invalidate /tmp/hwlab-dev-gate/.json \ --deprecated-endpoint http://74.48.78.17:6667 \ --reason "Public DEV endpoint moved to frontend :16666 and API/edge :16667" \ - --out reports/dev-gate/historical/-legacy-6667.json -node scripts/validate-dev-gate-report.mjs reports/dev-gate/historical/-legacy-6667.json + --out /tmp/hwlab-dev-gate/historical/-legacy-6667.json +node scripts/validate-dev-gate-report.mjs /tmp/hwlab-dev-gate/historical/-legacy-6667.json ``` Then regenerate the active report through its owning script so the active file diff --git a/docs/m2-dev-deploy-smoke.md b/docs/m2-dev-deploy-smoke.md index 2a2c842b..88caf788 100644 --- a/docs/m2-dev-deploy-smoke.md +++ b/docs/m2-dev-deploy-smoke.md @@ -43,7 +43,7 @@ operator supplies the live flags. The public DEV endpoint split is: Required flags: ```sh -node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --write-report +node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` Before using `--live`, confirm all of the following: @@ -63,8 +63,8 @@ The script rejects live execution if any of these are missing: - `--read-secret` - `--force-push` -When `--write-report` is provided, the script writes -`reports/dev-gate/dev-m2-deploy-smoke-active.json`. The report contains only +When `--report /tmp/hwlab-dev-gate/report.json` is provided, the script writes +`/tmp/hwlab-dev-gate/dev-m2-deploy-smoke-active.json`. The report contains only read-only GET evidence and marks legacy `:6667` observations as ineligible for active green. diff --git a/docs/m3-hardware-loop.md b/docs/m3-hardware-loop.md index 2e462d3c..ab8c1b0d 100644 --- a/docs/m3-hardware-loop.md +++ b/docs/m3-hardware-loop.md @@ -78,7 +78,7 @@ Use the no-write plan first: node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run ``` -The dry-run plan writes `reports/dev-gate/dev-m3-hardware-loop.json` by +The dry-run plan writes `/tmp/hwlab-dev-gate/dev-m3-hardware-loop.json` by default, records `dryRunPlan.evidenceLevel: "DRY-RUN"`, lists the live write preconditions, enumerates the endpoints that a later live run would call, and keeps `liveOperation.status: "not_run"`. It does not call DEV endpoints, does @@ -96,7 +96,7 @@ new handoffs should use `--expect-non-prod`. Missing or incomplete live authorization stays on the source/read-only plan path, and `--dry-run` combined with `--live` remains no-write. -The live smoke writes `reports/dev-gate/dev-m3-hardware-loop.json`. It first +The live smoke writes `/tmp/hwlab-dev-gate/dev-m3-hardware-loop.json`. It first checks the frozen DEV endpoint, `http://74.48.78.17:16667`, and stops at the first critical blocker from `docs/dev-acceptance-matrix.md`. When DEV ingress is not observable, the report is `blocked` and the script does not run the live diff --git a/docs/m5-mvp-e2e.md b/docs/m5-mvp-e2e.md index caadbe9c..894abb6f 100644 --- a/docs/m5-mvp-e2e.md +++ b/docs/m5-mvp-e2e.md @@ -111,6 +111,6 @@ hwlab-cli test e2e --env dev --mvp --live --confirm-dev --confirmed-non-producti ``` That command is intentionally not executed by M5. While -`reports/dev-gate/dev-mvp-gate-report.json` contains open blockers, the CLI +`GitHub issue/PR comments` contains open blockers, the CLI must return `code: "BLOCKED"` with blocker evidence instead of substituting fixture output as live DEV evidence. diff --git a/docs/operator-runbook.md b/docs/operator-runbook.md index f939d5a9..ae0f68b1 100644 --- a/docs/operator-runbook.md +++ b/docs/operator-runbook.md @@ -101,13 +101,13 @@ but it must include: ## DEV Gate Aggregator v2 -Use `reports/dev-gate/dev-m5-gate-aggregator-v2.json` and -`reports/dev-gate/dev-m5-gate-aggregator-v2.md` as the current report-only M5 +Use `/tmp/hwlab-dev-gate/dev-m5-gate-aggregator-v2.json` and +`/tmp/hwlab-dev-gate/dev-m5-gate-aggregator-v2.md` as the current report-only M5 gate summary for `pikasTech/HWLAB#58`. Refresh them only from committed fixtures: ```sh -node scripts/dev-evidence-blocker-aggregator.mjs --write-report +node scripts/dev-evidence-blocker-aggregator.mjs --report /tmp/hwlab-dev-gate/report.json node scripts/validate-dev-gate-report.mjs ``` @@ -175,7 +175,7 @@ reason. ## L6 CLI/Web Acceptance Entry Use the L6 entry points for external manual acceptance triage. They read the -checked-in `reports/dev-gate/dev-mvp-gate-report.json` and +checked-in `GitHub issue/PR comments` and `fixtures/mvp/m5-e2e/dry-run-plan.json`; they do not call UniDesk runtime and do not claim live DEV evidence from fixtures. diff --git a/docs/reference/MVP-e2e-acceptance.md b/docs/reference/MVP-e2e-acceptance.md index a2382196..8a2aade2 100644 --- a/docs/reference/MVP-e2e-acceptance.md +++ b/docs/reference/MVP-e2e-acceptance.md @@ -1,6 +1,6 @@ # HWLAB MVP E2E 验收测试与报告 issue 规则 -本文定义 HWLAB MVP 的用户可读、Agent 可执行验收测试规格。它按 `cli-spec` 的 `TEST.md` 风格组织用例,但报告产物不写入 `docs/` 或 `reports/`;每轮验收必须创建一个带编号的 GitHub 测试报告 issue。 +本文定义 HWLAB MVP 的用户可读、Agent 可执行验收测试规格。它按 `cli-spec` 的 `TEST.md` 风格组织用例,但报告产物不写入 `docs/` 或 `repository report directory `;每轮验收必须创建一个带编号的 GitHub 测试报告 issue。 ## 适用范围 diff --git a/docs/reference/cloud-workbench.md b/docs/reference/cloud-workbench.md index 012ecdec..9b16f0e2 100644 --- a/docs/reference/cloud-workbench.md +++ b/docs/reference/cloud-workbench.md @@ -184,7 +184,7 @@ npm run web:layout:live 默认 `web:check` 失败时会把完整 JSON 报告写入 `tmp/dev-cloud-workbench-layout-web-check.json`,终端断言只输出 compact blockers/failures:`viewport`、`selector`、`failureType`、summary 和 artifact -路径。`reports/dev-gate/dev-cloud-workbench-layout*.json` 是显式 `web:layout*` +路径。`/tmp/hwlab-dev-gate/dev-cloud-workbench-layout*.json` 是显式 `web:layout*` 命令的报告路径;它们属于 #273 UI layout blocker 证据,不属于 M3 trusted green 证据。 diff --git a/docs/reference/deployment-publish.md b/docs/reference/deployment-publish.md index 94780aa8..9d9b613e 100644 --- a/docs/reference/deployment-publish.md +++ b/docs/reference/deployment-publish.md @@ -51,7 +51,7 @@ truth. 正式 DEV CD 写路径只有一个入口: ```sh -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` 该命令是唯一允许一次性发布 DEV artifact、从发布报告刷新 desired @@ -63,7 +63,7 @@ apply、rollout 类副作用前,脚本必须先完成 apply-only preflight: `docker-desktop`、`desktop-control-plane`、`127.0.0.1:11700`、裸 `kubectl` 指向第二套 `hwlab-dev` 控制面或其他不可信控制面信号,并且只验证 必要 SecretRef 的存在性和 key 名。通过后才获取 Lease、串行执行内部步骤、 -按需写入 `reports/dev-gate/dev-cd-apply.json`,最后释放 Lease。 +按需写入 `/tmp/hwlab-dev-gate/dev-cd-apply.json`,最后释放 Lease。 apply preflight 检查的 SecretRef 固定为: @@ -90,7 +90,7 @@ checkout promotion parent 或手工拼装发布环境。 `origin/main` 控制入口,`target.promotionCommit` 是从 `deploy/deploy.json` 解析出的真实 promotion commit,`target.artifactBoundary` 记录最近一次触碰 `deploy/deploy.json`、`deploy/artifact-catalog.dev.json`、 -`deploy/k8s/base/workloads.yaml`、`reports/dev-gate/dev-artifacts.json` 的 +`deploy/k8s/base/workloads.yaml`、`/tmp/hwlab-dev-gate/dev-artifacts.json` 的 first-parent artifact merge commit,以及该 merge commit 的 promotion parent 和 artifact/report parent。`artifactCatalog`、`artifactReport`、`deployJson` 分别记录 hash、commit、digest 计数和 publish source。若 deploy/catalog/report @@ -145,7 +145,7 @@ repo-owned 入口,不能绕过它们直接拼接 publish、apply、rollout 或 | 读取 DEV CD 状态 | `node scripts/dev-cd-apply.mjs --status` | 不写入、不获取 Lease、不读取 Secret value。 | | 事务 dry-run preflight | `node scripts/dev-cd-apply.mjs --dry-run` | 不 publish、不 apply、不 rollout。 | | 检查 desired-state 收敛 | `node scripts/deploy-desired-state-plan.mjs --promotion-commit --check` | 只读 source/report。 | -| 执行 DEV CD 事务 | `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report` | 仅 DEV,在 `Lease/hwlab-dev/hwlab-dev-cd-lock` 下 publish/apply/verify。 | +| 执行 DEV CD 事务 | `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` | 仅 DEV,在 `Lease/hwlab-dev/hwlab-dev-cd-lock` 下 publish/apply/verify。 | | 读取 D601 k3s 证据 | `node scripts/d601-k3s-readonly-observability.mjs` | 只读可见性报告;不 rollout、不输出 Secret value。 | 使用 master-side wrapper 时,稳定形态是: @@ -153,7 +153,7 @@ repo-owned 入口,不能绕过它们直接拼接 publish、apply、rollout 或 ```sh cd /root/unidesk bun scripts/cli.ts ssh D601 argv bash -lc 'cd && KUBECONFIG=/etc/rancher/k3s/k3s.yaml node scripts/dev-cd-apply.mjs --status' -bun scripts/cli.ts ssh D601 argv bash -lc 'cd && KUBECONFIG=/etc/rancher/k3s/k3s.yaml node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report' +bun scripts/cli.ts ssh D601 argv bash -lc 'cd && KUBECONFIG=/etc/rancher/k3s/k3s.yaml node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json' ``` `` 必须指向干净工作区。该工作区可以是固定 clean @@ -181,7 +181,7 @@ desired-state 来源关系固定如下: | `deploy/deploy.json` | DEV environment、namespace、endpoint、service set、promotion commit 和事务消费的 image tag。 | | `deploy/artifact-catalog.dev.json` | 同一 promotion commit 的不可变 artifact digest/tag 证据。 | | `deploy/k8s/base/workloads.yaml` | Kubernetes workload desired state,必须镜像 `deploy.json` 和 artifact catalog 后才能 apply。 | -| `reports/dev-gate/*.json` | 只作为证据快照;不能覆盖 repo desired state。 | +| `/tmp/hwlab-dev-gate/*.json` | 只作为证据快照;不能覆盖 repo desired state。 | | `Lease/hwlab-dev/hwlab-dev-cd-lock` | 只作为事务互斥锁和审计状态;永远不是 desired-state 来源。 | 只改文档的 commit 如果没有修改 `deploy/deploy.json`、 @@ -261,11 +261,11 @@ value。 - `16667/health/live` 的 Cloud API commit、DB readiness、runtime durability 均 ready; - `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deploy,statefulset` 显示 persistent workload image tag 收敛到同一 promotion commit; -- 输出保持 bounded summary,完整 report 写入 `reports/dev-gate/dev-cd-apply.json` +- 输出保持 bounded summary,完整 report 写入 `/tmp/hwlab-dev-gate/dev-cd-apply.json` 或 CLI dump 文件,避免把长 JSON 直接塞进指挥上下文。 正常 `--apply` stdout 默认也是紧凑摘要;完整事务记录继续通过 -`--write-report` 写入既有 report 路径,只有显式 `--full-output` 时才打印 +`--report /tmp/hwlab-dev-gate/report.json` 写入既有 report 路径,只有显式 `--full-output` 时才打印 完整 JSON。DEV apply、rollout 和 live verification 仍由 host commander 统一执行;runner 默认只使用 status/dry-run 观测面,不竞争 DEV CD lock。 @@ -291,14 +291,14 @@ node scripts/dev-artifact-publish.mjs --publish It only runs in side-effect mode when `HWLAB_CD_TRANSACTION_ID` is set by `scripts/dev-cd-apply.mjs`. Use `node scripts/dev-cd-apply.mjs --apply ---confirm-dev --confirmed-non-production --write-report` for normal DEV CD. +--confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` for normal DEV CD. For Cloud Web source freshness before the transaction: ```sh node web/hwlab-cloud-web/scripts/check.mjs node web/hwlab-cloud-web/scripts/build.mjs -node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` Cloud Web must build `web/hwlab-cloud-web/dist` before publish because the @@ -314,7 +314,7 @@ After a successful multi-service publish, refresh the artifact catalog from the publish report: ```sh -node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json +node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report /tmp/hwlab-dev-gate/dev-artifacts.json node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --check ``` @@ -333,7 +333,7 @@ state as `promotion_pending`; PR review and CI/CD promotion gates must use `--target-ref origin/main --check` or `--promotion-commit --check` so stale or partially refreshed commit, image, workload, and mirror-env fields are blocked before any DEV apply. -Reports under `reports/` are evidence snapshots only and cannot override this +Reports under `repository report directory ` are evidence snapshots only and cannot override this desired-state contract. ## Latest-Main Artifact/Runtime Guard @@ -351,7 +351,7 @@ When current readiness is expected to be blocked, use: node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --no-report --expect-blocked ``` -The guard compares target main, `reports/dev-gate/dev-artifacts.json`, +The guard compares target main, `/tmp/hwlab-dev-gate/dev-artifacts.json`, `deploy/artifact-catalog.dev.json`, the desired-state planner, Cloud API `/health/live`, and Cloud Web `:16666/health/live`. It is report-only and must not be used as an apply command. A local source check, static workbench smoke, @@ -390,9 +390,9 @@ The DEV CD transaction owns the durable runtime support sequence before final live postflight: ```sh -node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --write-report -node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --write-report -node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report +node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/report.json ``` These commands are called by `scripts/dev-cd-apply.mjs` under the transaction @@ -449,7 +449,7 @@ apply requires the transaction environment created by `scripts/dev-cd-apply.mjs` plus explicit non-production flags: ```sh -node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` Direct manual `--apply` calls without `HWLAB_CD_TRANSACTION_ID` return @@ -496,12 +496,12 @@ use the repo-owned scripts below instead of ad hoc shell fragments. | Plan desired image/workload state | `node scripts/deploy-desired-state-plan.mjs --plan --pretty` and `node scripts/deploy-desired-state-plan.mjs --promotion-commit --check` | Fold into `hwlab deploy plan --env dev --file deploy/deploy.json` with a promotion commit gate. | | Observe DEV CD status | `node scripts/dev-cd-apply.mjs`, `node scripts/dev-cd-apply.mjs --status`, or `node scripts/dev-cd-apply.mjs --dry-run` | Keep read-only for host and runners; no Lease mutation, artifact publish, apply, rollout, report write, or Secret read. | | Preflight artifact publish | `node scripts/dev-artifact-publish.mjs --preflight` | Keep as backend for `hwlab artifact publish --preflight`. | -| Publish/apply/verify DEV | `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report` | Keep as the single side-effect transaction and run from repo-owned DEV CI/CD. | +| Publish/apply/verify DEV | `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` | Keep as the single side-effect transaction and run from repo-owned DEV CI/CD. | | Publish DEV images | `node scripts/dev-artifact-publish.mjs --publish` inside `HWLAB_CD_TRANSACTION_ID` | Internal step only; direct side-effect calls are rejected. | -| Refresh catalog/desired state | `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json` | Internal transaction step after publish; read/write desired-state only from repo files. | -| Apply DEV rollout | `node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report` inside `HWLAB_CD_TRANSACTION_ID` | Internal transaction step; direct side-effect calls are rejected. | +| Refresh catalog/desired state | `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report /tmp/hwlab-dev-gate/dev-artifacts.json` | Internal transaction step after publish; read/write desired-state only from repo files. | +| Apply DEV rollout | `node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` inside `HWLAB_CD_TRANSACTION_ID` | Internal transaction step; direct side-effect calls are rejected. | | Roll back DEV Deployment | `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/` | Add `hwlab deploy rollback --env dev --service --to-revision `. | -| Verify status | `curl` checks plus `node scripts/d601-k3s-readonly-observability.mjs` | Add `hwlab deploy verify --env dev --write-report`. | +| Verify status | `curl` checks plus `node scripts/d601-k3s-readonly-observability.mjs` | Add `hwlab deploy verify --env dev --report /tmp/hwlab-dev-gate/report.json`. | ## Automation Direction diff --git a/docs/reference/dev-runtime-boundary.md b/docs/reference/dev-runtime-boundary.md index aefbe93a..135516ef 100644 --- a/docs/reference/dev-runtime-boundary.md +++ b/docs/reference/dev-runtime-boundary.md @@ -224,7 +224,7 @@ The repo-owned DEV DB provisioning entrypoint is: ```sh node scripts/dev-runtime-provisioning.mjs --check -node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --write-report +node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/report.json ``` The default `--check` path validates the redacted target DB URL contract only: @@ -237,7 +237,7 @@ kubeconfig material. An authorized DEV provisioning apply is: ```sh -node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` The apply path uses `HWLAB_CLOUD_DB_URL` from @@ -262,7 +262,7 @@ The repo-owned runtime migration entrypoint is: ```sh node scripts/dev-runtime-migration.mjs --check -node scripts/dev-runtime-migration.mjs --dry-run --write-report +node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/report.json ``` Those default paths are source-only. They validate @@ -275,14 +275,14 @@ An authorized operator may run a read-only live verification only after injecting the DEV DB URL through the existing Secret/env path: ```sh -node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --write-report +node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/report.json ``` A real DEV migration apply is intentionally separate and requires all explicit write flags: ```sh -node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --write-report +node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json ``` The apply command is scoped to DEV Postgres schema objects declared by @@ -306,7 +306,7 @@ The repo-owned postflight entrypoint is: ```sh node scripts/dev-runtime-postflight.mjs --check -node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report +node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/report.json ``` Default `--check` is source-only. Live mode first reads @@ -335,13 +335,13 @@ authorizes the DEV apply command and the preconditions below are already true. | Check | Safe for normal runners | Evidence allowed | Not allowed | | --- | --- | --- | --- | -| Source migration contract | Yes | `node scripts/dev-runtime-migration.mjs --check` and `node scripts/dev-runtime-migration.mjs --dry-run --write-report` | DB connection, Secret read, DEV/PROD write | +| Source migration contract | Yes | `node scripts/dev-runtime-migration.mjs --check` and `node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/report.json` | DB connection, Secret read, DEV/PROD write | | Static runtime boundary contract | Yes | `node scripts/validate-runtime-boundary.mjs`, source docs, manifests, and redacted Secret refs | Live mutation, service restart, Secret value output | | Public health observation | Yes, when command scope is read-only | `/health/live` fields such as `db.liveDbEvidence`, `runtime.adapter`, `runtime.durable`, `runtime.blocker`, `readiness.durability.blockedLayer`, `requiredEvidence`, and redaction/safety flags | Printing DB URLs, passwords, tokens, kubeconfig material, or Secret data | -| Live provisioning apply | No, unless explicitly authorized by commander/operator | `node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --write-report` with role/database created/existed booleans and no secret output | PROD apply, Secret value output, manual DB shell, schema migration, service restart, M3 acceptance promotion | -| Live DB read verification | Only with explicit DEV read authorization | `--dry-run --allow-live-db-read --confirm-dev --write-report` output with redacted endpoint and `secretValuesPrinted=false` | Kubernetes Secret reads, writes, migration apply, PROD target | -| Live migration/repair apply | No, unless explicitly authorized by commander/operator | `--apply --confirm-dev --confirmed-non-production --write-report` plus post-apply health evidence | PROD apply, Secret value output, service restart, Code Agent POST, hardware write, or acceptance promotion | -| Durable runtime postflight | Only after durable runtime readiness is already green | `node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report` with `/health/live`, `/v1`, and M3 true/false durable green evidence | Running M3 writes while runtime durable readiness is blocked, Secret output, PROD, or manual rollout | +| Live provisioning apply | No, unless explicitly authorized by commander/operator | `node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` with role/database created/existed booleans and no secret output | PROD apply, Secret value output, manual DB shell, schema migration, service restart, M3 acceptance promotion | +| Live DB read verification | Only with explicit DEV read authorization | `--dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/report.json` output with redacted endpoint and `secretValuesPrinted=false` | Kubernetes Secret reads, writes, migration apply, PROD target | +| Live migration/repair apply | No, unless explicitly authorized by commander/operator | `--apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json` plus post-apply health evidence | PROD apply, Secret value output, service restart, Code Agent POST, hardware write, or acceptance promotion | +| Durable runtime postflight | Only after durable runtime readiness is already green | `node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/report.json` with `/health/live`, `/v1`, and M3 true/false durable green evidence | Running M3 writes while runtime durable readiness is blocked, Secret output, PROD, or manual rollout | Secret handling rules are the same for every row: verify env names, `secretKeyRef` names/keys, configured provider/model/base URL, redacted endpoint diff --git a/internal/cloud/server.mjs b/internal/cloud/server.mjs index 7584df01..c6575651 100644 --- a/internal/cloud/server.mjs +++ b/internal/cloud/server.mjs @@ -49,8 +49,7 @@ const LIVE_BUILD_TIMEOUT_MS = 900; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const LIVE_BUILD_METADATA_PATHS = Object.freeze({ deployManifest: "deploy/deploy.json", - artifactCatalog: "deploy/artifact-catalog.dev.json", - artifactReport: "reports/dev-gate/dev-artifacts.json" + artifactCatalog: "deploy/artifact-catalog.dev.json" }); const LIVE_BUILD_SERVICE_DEFAULTS = Object.freeze({ "hwlab-cloud-api": Object.freeze({ urlEnv: null, defaultUrl: "http://127.0.0.1:6667" }), @@ -422,7 +421,7 @@ async function buildLiveBuildsPayload(options = {}) { metadataPaths: LIVE_BUILD_METADATA_PATHS, desiredStateSource: metadata.deployManifest.status === "ok" ? LIVE_BUILD_METADATA_PATHS.deployManifest : "unavailable", artifactCatalogSource: metadata.artifactCatalog.status === "ok" ? LIVE_BUILD_METADATA_PATHS.artifactCatalog : "unavailable", - artifactReportSource: metadata.artifactReport.status === "ok" ? LIVE_BUILD_METADATA_PATHS.artifactReport : "unavailable", + artifactReportSource: "disabled:repository-report-files-removed", note: "Each row prefers current live service health build metadata and falls back to repo-owned deploy/artifact metadata only when the live tag/revision matches; observedAt is not used as build time." }, latest, @@ -952,7 +951,7 @@ async function loadLiveBuildMetadata(options = {}) { const [deployManifest, artifactCatalog, artifactReport] = await Promise.all([ readJsonMetadata(root, LIVE_BUILD_METADATA_PATHS.deployManifest), readJsonMetadata(root, LIVE_BUILD_METADATA_PATHS.artifactCatalog), - readJsonMetadata(root, LIVE_BUILD_METADATA_PATHS.artifactReport) + Promise.resolve({ status: "disabled", payload: null, error: "repository report files removed" }) ]); return normalizeLiveBuildMetadata({ deployManifest, artifactCatalog, artifactReport }); } @@ -980,7 +979,7 @@ async function readJsonMetadata(root, relativePath) { } function normalizeMetadataFile(file) { - if (file?.status === "ok" || file?.status === "unavailable") { + if (file?.status === "ok" || file?.status === "unavailable" || file?.status === "disabled") { return { status: file.status, path: file.path ?? null, diff --git a/internal/mvp-gate/summary.mjs b/internal/mvp-gate/summary.mjs index cb4de7fd..3490e8c6 100644 --- a/internal/mvp-gate/summary.mjs +++ b/internal/mvp-gate/summary.mjs @@ -4,7 +4,7 @@ import path from "node:path"; import { DEV_ENDPOINT } from "../protocol/index.mjs"; export { DEV_ENDPOINT }; -export const DEFAULT_GATE_REPORT_PATH = "reports/dev-gate/dev-mvp-gate-report.json"; +export const DEFAULT_GATE_REPORT_SOURCE = "source:mvp-gate-summary"; export const DEFAULT_M5_PLAN_PATH = "fixtures/mvp/m5-e2e/dry-run-plan.json"; export const M3_WORKBENCH_TOPOLOGY = Object.freeze({ projectId: "prj_m3_hardware_loop", @@ -70,6 +70,170 @@ function readJsonFile(repoRoot, relativePath) { return JSON.parse(raw); } +function defaultMvpGateReport() { + return { + reportVersion: "v1", + issue: "pikasTech/HWLAB#39", + taskId: "dev-mvp-gate-source-summary", + commitId: "source-summary", + acceptanceLevel: "dev_mvp_gate", + devOnly: true, + prodDisabled: true, + gateStatus: "blocked", + sourceContract: { + status: "pass", + documents: [ + "docs/dev-gate-report.md", + "docs/dev-acceptance-matrix.md", + "docs/m0-contract-audit.md", + "docs/m1-local-smoke.md", + "docs/m2-dev-deploy-smoke.md", + "docs/m3-hardware-loop.md", + "docs/m4-agent-loop.md", + "docs/m5-mvp-e2e.md", + "docs/runtime-boundary-guard.md", + "docs/topology-constraints.md", + "docs/artifact-catalog.md", + "docs/schema-drift-map.md", + "protocol/README.md", + "protocol/audit.md", + "protocol/evidence-chain.md", + "protocol/mvp-e2e-contract.md" + ], + summary: "The DEV gate is anchored to the frozen matrix, gate contract, and M0-M5 milestone source docs; progress and acceptance evidence live in GitHub issue or PR comments, not repository report files." + }, + validationCommands: [ + "node scripts/repo-reports-guard.mjs", + "node --check scripts/validate-dev-gate-report.mjs", + "node scripts/validate-dev-gate-report.mjs" + ], + localSmoke: { + status: "pass", + commands: [ + "node scripts/validate-contract.mjs", + "node scripts/validate-artifact-catalog.mjs", + "node scripts/m1-contract-smoke.mjs", + "node scripts/m2-dev-deploy-smoke.mjs --dry-run" + ], + evidence: [ + "Source contracts remain parseable.", + "MVP dry-run fixtures remain local/source only.", + "Repository report directory is forbidden by repo guard." + ], + summary: "M0-M2 contract and smoke checks remain local/source scoped and do not create repository report files." + }, + dryRun: { + status: "pass", + commands: [ + "node tools/hwlab-cli/bin/hwlab-cli.mjs health", + "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" + ], + evidence: [ + "Dry-run uses fixtures and performs no DEV/PROD mutations.", + "Dry-run evidence is not live acceptance." + ], + summary: "The M5 orchestration dry-run is fixture-only and does not establish a live DEV gate." + }, + devPreconditions: { + status: "blocked", + classification: "runtime durable adapter; skills injection; hardware loop", + requirements: [ + "Current DEV route and health checks must be rerun when needed and summarized in GitHub.", + "Runtime durable adapter proves postgres schema, migration, and read-query readiness before M4/M5 can promote.", + "Agent manager, skills service, and worker jobs expose explicit skill commit/version before M4/M5 can promote.", + "M3 hardware loop proves box-simu-1 DO1 to box-simu-2 DI1 via patch-panel.", + "Keep the run DEV-only and avoid PROD, heavy e2e, secrets, restarts, or UniDesk substitution." + ], + commands: [ + "curl -fsS --max-time 8 http://74.48.78.17:16666/", + "curl -fsS --max-time 8 http://74.48.78.17:16667/health/live", + "curl -fsS --max-time 8 http://74.48.78.17:16667/health", + "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", + "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" + ], + evidence: [ + "Repository report files were removed and no longer qualify as current evidence.", + "Live DEV acceptance must be represented in #7, the owning issue, daily brief, or PR comments." + ], + summary: "Live MVP remains blocked until current DEV checks clear runtime durability, skills injection, and M3 hardware-loop blockers outside repository report files." + }, + blockers: [ + { + type: "runtime_blocker", + scope: "runtime-durable-adapter", + status: "open", + sourceIssue: "pikasTech/HWLAB#164", + classification: "runtime_durable_adapter_query_blocked", + summary: + "cloud-api durable runtime adapter readiness is blocked by runtime_durable_adapter_query_blocked; it must prove schema, migration, and read-query durability before M4/M5 promotion." + }, + { + type: "agent_blocker", + scope: "skills-commit-version-injection", + status: "open", + sourceIssue: "pikasTech/HWLAB#164", + classification: "skills injection", + summary: "DEV skills injection must expose explicit skills commit and version across manager, skills, and worker paths." + }, + { + type: "runtime_blocker", + scope: "m3-hardware-loop-runtime", + status: "open", + sourceIssue: "pikasTech/HWLAB#38", + classification: "hardware loop", + summary: "Live M3 must prove the patch-panel trusted loop with operation, trace, audit, and evidence identifiers." + } + ], + milestones: [ + { + id: "M0", + status: "pass", + commands: ["node scripts/validate-contract.mjs"], + evidence: ["Source contracts are parseable."], + summary: "Frozen contracts remain source-ready." + }, + { + id: "M1", + status: "pass", + commands: ["node scripts/m1-contract-smoke.mjs"], + evidence: ["Local skeleton smoke remains local-only."], + summary: "The local M1 contract smoke passes." + }, + { + id: "M2", + status: "pass", + commands: ["node scripts/m2-dev-deploy-smoke.mjs --dry-run"], + evidence: ["Deploy smoke remains fixture-only."], + summary: "The deploy smoke validates source contracts without live acceptance." + }, + { + id: "M3", + status: "blocked", + commands: ["node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"], + evidence: ["Repository report snapshots were removed; current live trusted-loop proof is required."], + summary: "Active DEV-LIVE M3 remains blocked by hardware loop topology until the trusted loop is proven.", + blocker: "hardware loop" + }, + { + id: "M4", + status: "blocked", + commands: ["node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"], + evidence: ["Runtime durable adapter readiness and skills injection must be current live facts."], + summary: "Active DEV-LIVE M4 remains blocked by runtime durable adapter readiness and skills commit/version injection.", + blocker: "runtime_durable_adapter_query_blocked; skills injection" + }, + { + id: "M5", + status: "blocked", + commands: ["node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run"], + evidence: ["M5 dry-run remains fixture-only; live acceptance must be posted in GitHub."], + summary: "The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance remains blocked.", + blocker: "runtime durable adapter; skills injection; hardware loop" + } + ] + }; +} + function countByServiceId(items) { return new Map(items.map((item) => [item.serviceId, item])); } @@ -275,7 +439,7 @@ export function buildMvpGateSummary({ report, plan }) { return { generatedFrom: { - gateReport: DEFAULT_GATE_REPORT_PATH, + gateReport: DEFAULT_GATE_REPORT_SOURCE, m5Plan: DEFAULT_M5_PLAN_PATH }, issue: "pikasTech/HWLAB#56", @@ -371,13 +535,13 @@ export function buildMvpGateSummary({ report, plan }) { } export function loadMvpGateSummary(repoRoot, options = {}) { - const reportPath = options.reportPath ?? DEFAULT_GATE_REPORT_PATH; + const reportPath = options.reportPath ?? null; const planPath = options.planPath ?? DEFAULT_M5_PLAN_PATH; - const report = readJsonFile(repoRoot, reportPath); + const report = reportPath ? readJsonFile(repoRoot, reportPath) : defaultMvpGateReport(); const plan = readJsonFile(repoRoot, planPath); const summary = buildMvpGateSummary({ report, plan }); summary.generatedFrom = { - gateReport: reportPath, + gateReport: reportPath ?? DEFAULT_GATE_REPORT_SOURCE, m5Plan: planPath }; return summary; diff --git a/package.json b/package.json index 4b21760a..0f755d8e 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "private": true, "type": "module", "scripts": { - "validate": "node scripts/validate-contract.mjs && node scripts/deploy-contract-plan.mjs --check && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-provisioning.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-runtime-postflight.mjs --check && node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --test scripts/artifact-runtime-readiness-guard.test.mjs", - "check": "node --check internal/protocol/index.mjs && node --check internal/build-metadata.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/audit/index.mjs && node --check internal/db/runtime-store.mjs && node --check internal/db/runtime-store.test.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/dev-entrypoint/http.mjs && node --check internal/dev-entrypoint/http.test.mjs && node --check internal/cloud/code-agent-contract.mjs && node --check internal/cloud/code-agent-session-registry.mjs && node --check internal/cloud/code-agent-session-registry.test.mjs && node --check internal/cloud/codex-stdio-session.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/code-agent-chat.mjs && node --check internal/cloud/m3-io-control.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/model.test.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check internal/patchpanel/model.mjs && node --check internal/patchpanel/runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-edge-proxy/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/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/cloud-api-runtime-smoke.mjs && node --check scripts/code-agent-chat-smoke.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/deploy-contract-plan.test.mjs && node --check scripts/deploy-desired-state-plan.mjs && node --check scripts/src/deploy-desired-state-plan.mjs && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --check scripts/artifact-runtime-readiness-guard.test.mjs && node --check scripts/report-lifecycle.mjs && node --check scripts/report-lifecycle.test.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/dev-cd-apply.mjs && node --check scripts/src/dev-cd-apply.mjs && node --check scripts/src/dev-cd-apply.test.mjs && node --check scripts/dev-m3-hardware-loop-smoke.test.mjs && node --check scripts/m3-io-control-e2e.mjs && node --check scripts/src/m3-io-control-e2e.mjs && node --check scripts/m3-io-control-e2e.test.mjs && node --check scripts/dev-cloud-workbench-smoke.test.mjs && node --check scripts/dev-cloud-workbench-layout-smoke.mjs && node --check scripts/rpt004-mvp-e2e-harness.mjs && node --check scripts/src/rpt004-mvp-e2e-harness.mjs && node --check scripts/src/rpt004-mvp-e2e-harness.test.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-dev-m3-cardinality.test.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/src/registry-capabilities.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/src/dev-evidence-blocker-aggregator.test.mjs && node --check scripts/src/dev-m4-agent-loop-smoke-lib.test.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/dev-runtime-provisioning.mjs && node --check scripts/src/dev-runtime-provisioning.mjs && node --check scripts/src/dev-runtime-provisioning.test.mjs && node --check scripts/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.test.mjs && node --check scripts/dev-runtime-postflight.mjs && node --check scripts/src/dev-runtime-postflight.mjs && node --check scripts/src/dev-runtime-postflight.test.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/patch-panel-runtime-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check skills/hwlab-agent-runtime/scripts/hwlab-agent-runtime-cli.mjs && node --check skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs && node --check skills/hwlab-agent-runtime/scripts/m3-io-skill-client.test.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check tools/hwlab-cli/lib/cli.test.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/code-agent-facts.mjs && node --check web/hwlab-cloud-web/code-agent-facts.test.mjs && node --check web/hwlab-cloud-web/code-agent-status.mjs && node --check web/hwlab-cloud-web/code-agent-status.test.mjs && node --check web/hwlab-cloud-web/code-agent-m3-evidence.mjs && node --check web/hwlab-cloud-web/code-agent-m3-evidence.test.mjs && node --check web/hwlab-cloud-web/live-status.mjs && node --check web/hwlab-cloud-web/wiring-status.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/live-status-contract.test.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node --check web/hwlab-cloud-web/scripts/dist-contract.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/report-lifecycle.test.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-provisioning.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-runtime-postflight.mjs --check && node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write && 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 scripts/code-agent-chat-smoke.mjs && node --test web/hwlab-cloud-web/code-agent-facts.test.mjs web/hwlab-cloud-web/code-agent-status.test.mjs web/hwlab-cloud-web/wiring-status.test.mjs web/hwlab-cloud-web/code-agent-m3-evidence.test.mjs scripts/artifact-runtime-readiness-guard.test.mjs scripts/deploy-contract-plan.test.mjs scripts/dev-m3-hardware-loop-smoke.test.mjs scripts/validate-dev-m3-cardinality.test.mjs scripts/dev-cloud-workbench-smoke.test.mjs web/hwlab-cloud-web/scripts/live-status-contract.test.mjs scripts/deploy-desired-state-plan.test.mjs scripts/src/dev-cd-apply.test.mjs scripts/src/dev-deploy-apply.test.mjs scripts/src/dev-runtime-provisioning.test.mjs scripts/src/dev-runtime-migration.test.mjs scripts/src/dev-runtime-postflight.test.mjs scripts/src/dev-m4-agent-loop-smoke-lib.test.mjs scripts/src/dev-evidence-blocker-aggregator.test.mjs skills/hwlab-agent-runtime/scripts/m3-io-skill-client.test.mjs tools/hwlab-cli/lib/cli.test.mjs internal/agent/index.test.mjs internal/audit/index.test.mjs internal/db/schema.test.mjs internal/db/runtime-store.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/m3-io-control.test.mjs internal/cloud/code-agent-session-registry.test.mjs internal/cloud/server.test.mjs internal/dev-entrypoint/http.test.mjs internal/sim/model.test.mjs internal/patchpanel/model.test.mjs internal/patchpanel/runtime.test.mjs && node scripts/cloud-api-runtime-smoke.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh", + "validate": "node scripts/repo-reports-guard.mjs && node scripts/validate-contract.mjs && node scripts/deploy-contract-plan.mjs --check && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-provisioning.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-runtime-postflight.mjs --check && node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --test scripts/artifact-runtime-readiness-guard.test.mjs", + "check": "node scripts/repo-reports-guard.mjs && node --check scripts/repo-reports-guard.mjs && node --check scripts/src/report-paths.mjs && node --check internal/protocol/index.mjs && node --check internal/build-metadata.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/audit/index.mjs && node --check internal/db/runtime-store.mjs && node --check internal/db/runtime-store.test.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/dev-entrypoint/http.mjs && node --check internal/dev-entrypoint/http.test.mjs && node --check internal/cloud/code-agent-contract.mjs && node --check internal/cloud/code-agent-session-registry.mjs && node --check internal/cloud/code-agent-session-registry.test.mjs && node --check internal/cloud/codex-stdio-session.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/code-agent-chat.mjs && node --check internal/cloud/m3-io-control.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/model.test.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check internal/patchpanel/model.mjs && node --check internal/patchpanel/runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-edge-proxy/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/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/cloud-api-runtime-smoke.mjs && node --check scripts/code-agent-chat-smoke.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/deploy-contract-plan.test.mjs && node --check scripts/deploy-desired-state-plan.mjs && node --check scripts/src/deploy-desired-state-plan.mjs && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --check scripts/artifact-runtime-readiness-guard.test.mjs && node --check scripts/report-lifecycle.mjs && node --check scripts/report-lifecycle.test.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/dev-cd-apply.mjs && node --check scripts/src/dev-cd-apply.mjs && node --check scripts/src/dev-cd-apply.test.mjs && node --check scripts/dev-m3-hardware-loop-smoke.test.mjs && node --check scripts/m3-io-control-e2e.mjs && node --check scripts/src/m3-io-control-e2e.mjs && node --check scripts/m3-io-control-e2e.test.mjs && node --check scripts/dev-cloud-workbench-smoke.test.mjs && node --check scripts/dev-cloud-workbench-layout-smoke.mjs && node --check scripts/rpt004-mvp-e2e-harness.mjs && node --check scripts/src/rpt004-mvp-e2e-harness.mjs && node --check scripts/src/rpt004-mvp-e2e-harness.test.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-dev-m3-cardinality.test.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/src/registry-capabilities.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/src/dev-evidence-blocker-aggregator.test.mjs && node --check scripts/src/dev-m4-agent-loop-smoke-lib.test.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/dev-runtime-provisioning.mjs && node --check scripts/src/dev-runtime-provisioning.mjs && node --check scripts/src/dev-runtime-provisioning.test.mjs && node --check scripts/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.test.mjs && node --check scripts/dev-runtime-postflight.mjs && node --check scripts/src/dev-runtime-postflight.mjs && node --check scripts/src/dev-runtime-postflight.test.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/patch-panel-runtime-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check skills/hwlab-agent-runtime/scripts/hwlab-agent-runtime-cli.mjs && node --check skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs && node --check skills/hwlab-agent-runtime/scripts/m3-io-skill-client.test.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check tools/hwlab-cli/lib/cli.test.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/code-agent-facts.mjs && node --check web/hwlab-cloud-web/code-agent-facts.test.mjs && node --check web/hwlab-cloud-web/code-agent-status.mjs && node --check web/hwlab-cloud-web/code-agent-status.test.mjs && node --check web/hwlab-cloud-web/code-agent-m3-evidence.mjs && node --check web/hwlab-cloud-web/code-agent-m3-evidence.test.mjs && node --check web/hwlab-cloud-web/live-status.mjs && node --check web/hwlab-cloud-web/wiring-status.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/live-status-contract.test.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node --check web/hwlab-cloud-web/scripts/dist-contract.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/report-lifecycle.test.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-provisioning.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-runtime-postflight.mjs --check && node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write && 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 scripts/code-agent-chat-smoke.mjs && node --test web/hwlab-cloud-web/code-agent-facts.test.mjs web/hwlab-cloud-web/code-agent-status.test.mjs web/hwlab-cloud-web/wiring-status.test.mjs web/hwlab-cloud-web/code-agent-m3-evidence.test.mjs scripts/artifact-runtime-readiness-guard.test.mjs scripts/deploy-contract-plan.test.mjs scripts/dev-m3-hardware-loop-smoke.test.mjs scripts/validate-dev-m3-cardinality.test.mjs scripts/dev-cloud-workbench-smoke.test.mjs web/hwlab-cloud-web/scripts/live-status-contract.test.mjs scripts/deploy-desired-state-plan.test.mjs scripts/src/dev-cd-apply.test.mjs scripts/src/dev-deploy-apply.test.mjs scripts/src/dev-runtime-provisioning.test.mjs scripts/src/dev-runtime-migration.test.mjs scripts/src/dev-runtime-postflight.test.mjs scripts/src/dev-m4-agent-loop-smoke-lib.test.mjs scripts/src/dev-evidence-blocker-aggregator.test.mjs skills/hwlab-agent-runtime/scripts/m3-io-skill-client.test.mjs tools/hwlab-cli/lib/cli.test.mjs internal/agent/index.test.mjs internal/audit/index.test.mjs internal/db/schema.test.mjs internal/db/runtime-store.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/m3-io-control.test.mjs internal/cloud/code-agent-session-registry.test.mjs internal/cloud/server.test.mjs internal/dev-entrypoint/http.test.mjs internal/sim/model.test.mjs internal/patchpanel/model.test.mjs internal/patchpanel/runtime.test.mjs && node scripts/cloud-api-runtime-smoke.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh", "dev-base-image:preflight": "node scripts/preflight-dev-base-image.mjs", "cloud-api:smoke": "node scripts/cloud-api-runtime-smoke.mjs", "m1:smoke": "node scripts/m1-contract-smoke.mjs", @@ -29,8 +29,8 @@ "web:build": "node web/hwlab-cloud-web/scripts/build.mjs", "artifact-catalog:refresh-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked", "dev-artifact:preflight": "node scripts/dev-artifact-publish.mjs --preflight", - "dev-cd:apply": "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report", - "dev-artifact:publish": "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report", + "dev-cd:apply": "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-cd-apply.json", + "dev-artifact:publish": "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-cd-apply.json", "docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs", "d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs", "runner:issue-visibility:preflight": "node scripts/runner-issue-visibility-preflight.mjs", @@ -41,9 +41,9 @@ "rpt004:mvp:e2e": "node scripts/rpt004-mvp-e2e-harness.mjs", "rpt004:mvp:e2e:live": "node scripts/rpt004-mvp-e2e-harness.mjs --live --url http://74.48.78.17:16666/ --api-url http://74.48.78.17:16667/", "m3:io:e2e": "node scripts/m3-io-control-e2e.mjs", - "web:layout": "node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report reports/dev-gate/dev-cloud-workbench-layout.json", - "web:layout:build": "node scripts/dev-cloud-workbench-layout-smoke.mjs --build --report reports/dev-gate/dev-cloud-workbench-layout-build.json", - "web:layout:live": "node scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-layout-live.json", + "web:layout": "node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json", + "web:layout:build": "node scripts/dev-cloud-workbench-layout-smoke.mjs --build --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-build.json", + "web:layout:live": "node scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-live.json", "gateway:demo:smoke": "node scripts/gateway-outbound-demo-smoke.mjs", "gateway:demo:edge-smoke": "node scripts/gateway-outbound-demo-smoke.mjs --edge" }, diff --git a/reports/d601-k3s-readonly-observability.json b/reports/d601-k3s-readonly-observability.json deleted file mode 100644 index 3b3acd8e..00000000 --- a/reports/d601-k3s-readonly-observability.json +++ /dev/null @@ -1,710 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/d601-k3s-readonly-observability-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/d601-k3s-readonly-observability.json", - "reportVersion": "v1", - "reportKind": "d601-k3s-readonly-observability", - "issue": "pikasTech/HWLAB#46", - "supports": [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#36", - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#42" - ], - "generatedAt": "2026-05-22T12:34:14.403Z", - "mode": "read-only", - "devOnly": true, - "prodDisabled": true, - "namespace": "hwlab-dev", - "runtimeTarget": { - "runner": "D601 Code Queue", - "cluster": "D601 k3s", - "namespace": "hwlab-dev", - "resources": [ - "pods", - "services", - "configmaps" - ] - }, - "safety": { - "noProd": true, - "noSecretsPrinted": true, - "noSecretResourcesRead": true, - "noClusterMutation": true, - "noRuntimeRestart": true, - "noHeavyE2E": true - }, - "forbiddenActions": [ - "prod-access", - "secret-material-read", - "cluster-mutation", - "runtime-restart", - "heavy-e2e", - "master-server-heavy-check" - ], - "validationCommands": [ - "node --check scripts/d601-k3s-readonly-observability.mjs", - "node --check scripts/src/d601-k3s-readonly-observability.mjs", - "node scripts/d601-k3s-readonly-observability.mjs" - ], - "conclusion": "blocked", - "runnerKubeconfigReadable": false, - "runnerKubeconfigProbeOk": true, - "runnerKubeconfigProbeExitCode": 0, - "runnerKubeconfigProbeStderr": "empty", - "d601PublicEndpointsReachable": true, - "d601K3sUnavailable": false, - "statusInference": { - "runnerKubeconfigReadable": false, - "runnerKubeconfigProbeOk": true, - "runnerKubeconfigProbeExitCode": 0, - "runnerKubeconfigProbeStderr": "empty", - "d601PublicEndpointsReachable": true, - "d601K3sUnavailable": false, - "conclusion": "Runner kubeconfig readability is an observability signal only; it is not used to infer D601 k3s or public DEV endpoint availability.", - "blockerClassification": "runner_permission_mount_gap", - "sourceIssue": "pikasTech/HWLAB#46" - }, - "environment": { - "binaries": { - "kubectl": { - "name": "kubectl", - "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": 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", - "available": false, - "path": null - }, - "ssh": { - "name": "ssh", - "available": true, - "path": "/usr/bin/ssh", - "version": { - "ok": true, - "command": "ssh -V", - "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", - "exists": false, - "readable": false, - "error": "ENOENT" - }, - { - "source": "default", - "path": "/etc/rancher/k3s/k3s.yaml", - "exists": false, - "readable": false, - "error": "ENOENT" - }, - { - "source": "default", - "path": "/var/lib/rancher/k3s/server/cred/admin.kubeconfig", - "exists": false, - "readable": false, - "error": "ENOENT" - } - ], - "runnerKubeconfig": { - "path": "/etc/rancher/k3s/k3s.yaml", - "mode": "read-only", - "resource": "pods", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods -o name", - "runnerKubeconfigReadable": false, - "runnerKubeconfigProbeOk": true, - "runnerKubeconfigProbeExitCode": 0, - "runnerKubeconfigProbeStderr": "empty", - "stdoutSummary": "13 pod name(s) observed", - "classification": "runner_permission_mount_gap", - "sourceIssue": "pikasTech/HWLAB#46", - "secretValuesRead": false, - "secretResourcesRead": false, - "note": "This probe forces KUBECONFIG=/etc/rancher/k3s/k3s.yaml for a read-only kubectl get pods command and records only exit code plus redacted stderr/stdout summary. A successful kubectl exit code does not make the kubeconfig file readable; kubectl may use an alternate in-cluster fallback." - }, - "sshBridge": { - "sshBinaryAvailable": true, - "env": [ - { - "name": "HWLAB_D601_SSH_HOST", - "set": false, - "valueRedacted": null - }, - { - "name": "HWLAB_D601_SSH_TARGET", - "set": false, - "valueRedacted": null - }, - { - "name": "HWLAB_D601_MAINTENANCE_HOST", - "set": false, - "valueRedacted": null - }, - { - "name": "D601_SSH_HOST", - "set": false, - "valueRedacted": null - }, - { - "name": "HWLAB_MAINTENANCE_SSH", - "set": false, - "valueRedacted": null - } - ], - "paths": [ - { - "path": "~/.ssh", - "exists": true, - "readable": true, - "type": "directory" - }, - { - "path": "~/.ssh/config", - "exists": false, - "readable": false, - "error": "ENOENT" - }, - { - "path": "~/.ssh/known_hosts", - "exists": true, - "readable": true, - "type": "file", - "sizeBytes": 5186 - } - ], - "configured": false, - "note": "SSH bridge discovery checks only binary, environment variable presence, and file metadata; it does not read SSH config or private keys." - } - }, - "publicEndpoints": { - "activeFrontendEndpoint": "http://74.48.78.17:16666", - "activeApiEndpoint": "http://74.48.78.17:16667", - "d601PublicEndpointsReachable": true, - "probes": [ - { - "id": "frontend-16666", - "url": "http://74.48.78.17:16666/", - "reached": true, - "ok": true, - "status": 200, - "bodySummary": " " - }, - { - "id": "api-health-16667", - "url": "http://74.48.78.17:16667/health", - "reached": true, - "ok": true, - "status": 200, - "serviceId": "hwlab-edge-proxy", - "environment": "dev", - "bodySummary": "json serviceId=hwlab-edge-proxy status=ok" - }, - { - "id": "api-live-16667", - "url": "http://74.48.78.17:16667/health/live", - "reached": true, - "ok": true, - "status": 200, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "bodySummary": "json serviceId=hwlab-cloud-api status=degraded" - } - ] - }, - "cluster": { - "namespace": "hwlab-dev", - "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-5b7487b5c4-fzvgr", - "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": "observability_blocker", - "scope": "runner-kubeconfig-readonly-gap", - "classification": "runner_permission_mount_gap", - "sourceIssue": "pikasTech/HWLAB#46", - "summary": "The runner cannot read /etc/rancher/k3s/k3s.yaml; classify this as a runner permission/mount gap, not a D601 global outage. Alternate read-only cluster probes and public DEV endpoint probes are reported separately.", - "nextTask": "Provide the approved read-only runner kubeconfig mount or document the intended alternate KUBECONFIG path, then rerun the read-only report." - } - ], - "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." - }, - "notes": "This report uses command availability checks, file metadata checks, public DEV endpoint GET probes, 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. A failed runner /etc/rancher/k3s/k3s.yaml probe is classified as a runner permission/mount or read-only observability gap and is not evidence that D601 k3s is globally unavailable." -} diff --git a/reports/dev-gate/dev-artifacts-hwlab-224.json b/reports/dev-gate/dev-artifacts-hwlab-224.json deleted file mode 100644 index 28e63114..00000000 --- a/reports/dev-gate/dev-artifacts-hwlab-224.json +++ /dev/null @@ -1,829 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-artifact-publish.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-artifacts.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "commitId": "86b3976", - "acceptanceLevel": "dev_artifact_publish", - "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 DEV artifact publish report; it is not a DEV-LIVE runtime substitute." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/artifact-catalog.md", - "docs/dev-artifact-publish.md", - "docs/dev-base-image-preflight.md" - ], - "summary": "DEV artifact publish evidence for pikasTech/HWLAB#35, stored in the DEV gate report envelope." - }, - "validationCommands": [ - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-artifact-services.mjs", - "node --check scripts/src/registry-capabilities.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", - "node scripts/dev-artifact-publish.mjs --preflight --no-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "This report records artifact build/publish state only; local smoke remains a separate gate." - ], - "summary": "Local smoke is not implied by artifact publication." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - ], - "evidence": [ - "This report records DEV artifact build/publish state only; deployment dry-run remains separate." - ], - "summary": "DEV deploy dry-run is not implied by artifact publication." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "Registry prefix is localhost or private/internal only.", - "DEV builder base image preflight returns ready before build or publish.", - "Environment remains dev and namespace remains hwlab-dev.", - "PROD profile remains disabled.", - "No secret or token material is required by this workflow." - ], - "summary": "Fatal DEV-only artifact preconditions passed." - }, - "blockers": [], - "artifactPublish": { - "issue": "pikasTech/HWLAB#35", - "status": "published", - "mode": "publish", - "repo": "pikasTech/HWLAB", - "sourceCommitId": "86b39769af98946124398dda6775d4b2a3b205e6", - "registryPrefix": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-23T19:42:29.910Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 6 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-749c489bf9-mcsdp", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:02424bd81b280535f7877a0febb4ac85b3e8d4009525e70a35e8e3dc37dc5443" - }, - { - "pod": "hwlab-agent-skills-7d5cf68bcd-wkcwv", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:26831fd6848abc50a4e8158d97fe5ec0a769a54abd4b9558249baa1691fd2758" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-cloud-api-6c565c556-nmvbh", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:69543a5c537e10593d790dba00e3bdb1e19aaad3790d44e0964d371d0fddab20" - }, - { - "pod": "hwlab-cloud-web-5f74d5d74-mfzqh", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:28378a2582a9e67b7fabbe64b604e33a3404d619c0b2a21b3100f082ec089385" - }, - { - "pod": "hwlab-edge-proxy-5c4c948c68-hwqp7", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:81636f2218282c43a0a107102f61f65f209f52fdfa25f883f418b274c1ca7ad1" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-patch-panel-784bdb49cc-z5jhz", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:521baa534c73bbcbb6b6f01f67574061c4c83178a83fd6f2dedf2ad0a62a228c" - }, - { - "pod": "hwlab-router-9954c75cb-zkmm2", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:e3f26f30d611a9bd63571bef600caeea5ca0770c5bf9b2d921abe74cb1ba1463" - }, - { - "pod": "hwlab-tunnel-client-86d5dc9b6-svcq4", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:f4d88c49f5bb03f3919c8a09f80a55abb63b8dd63bc0dd227b0608e99804d688" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "baseImage": "node:20-bookworm-slim", - "baseImagePreflight": { - "status": "ready", - "imageSource": "local-docker-node:20-*", - "requestedImage": null, - "localTag": "node:20-bookworm-slim", - "imageId": "sha256:9da6b4e352d0d5c94963eba1832408f5b7b08839cd8be9b6610c05de5118c704", - "repoDigests": [ - "node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0" - ], - "candidates": [ - { - "source": "local-docker-node:20-*", - "approval": { - "approved": true, - "reason": "local Node 20 builder base image" - }, - "localTag": "node:20-bookworm-slim", - "imageId": "9da6b4e352d0", - "size": "200MB", - "createdSince": "4 weeks ago" - } - ], - "rejectedCandidates": [], - "publishUsable": true, - "blockers": [], - "nextSteps": [ - "Use the reported localTag as the DEV builder base image for artifact publish.", - "Keep publish DEV-only and record the produced HWLAB service image tags and digests separately." - ], - "recommendation": { - "source": "Preloaded D601 local Docker cache or D601-local registry tag.", - "envVar": "HWLAB_DEV_BASE_IMAGE", - "preferredImageTag": "node:20-bookworm-slim", - "preferredHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "recommendedEnvValue": "node:20-bookworm-slim" - }, - "provision": { - "required": false, - "exactBlocker": null, - "expectedImageTag": "node:20-bookworm-slim", - "recommendedEnv": { - "name": "HWLAB_DEV_BASE_IMAGE", - "value": "node:20-bookworm-slim", - "assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim" - }, - "recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "nextHumanCommand": null, - "commands": { - "d601OnlinePreload": [ - "docker pull node:20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "d601HwlabTagProvision": [ - "docker pull node:20-bookworm-slim", - "docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "offlineSourceExport": [ - "docker pull node:20-bookworm-slim", - "docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar" - ], - "offlineD601Import": [ - "docker load -i hwlab-node20-bookworm-slim.tar", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "artifactPublishDryRun": [ - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/dev-artifact-publish.mjs --preflight --no-report" - ] - } - }, - "blockedReport": null, - "containerEngine": { - "command": "docker", - "available": true, - "version": "Docker version 26.1.5+dfsg1, build a72d7cd", - "errorCode": null, - "stderr": "" - } - }, - "environment": "dev", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "serviceInventory": { - "version": "v2", - "serviceCount": 3, - "requiredServiceCount": 3, - "disabledServiceCount": 0, - "requiredServiceIds": [ - "hwlab-cloud-api", - "hwlab-cloud-web", - "hwlab-edge-proxy" - ], - "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-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 - } - ] - }, - "publishPlan": { - "version": "v2", - "mode": "publish", - "sourceCommitId": "86b39769af98946124398dda6775d4b2a3b205e6", - "imageTag": "86b3976", - "registryTarget": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-23T19:42:29.910Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 6 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-749c489bf9-mcsdp", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:02424bd81b280535f7877a0febb4ac85b3e8d4009525e70a35e8e3dc37dc5443" - }, - { - "pod": "hwlab-agent-skills-7d5cf68bcd-wkcwv", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:26831fd6848abc50a4e8158d97fe5ec0a769a54abd4b9558249baa1691fd2758" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-cloud-api-6c565c556-nmvbh", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:69543a5c537e10593d790dba00e3bdb1e19aaad3790d44e0964d371d0fddab20" - }, - { - "pod": "hwlab-cloud-web-5f74d5d74-mfzqh", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:28378a2582a9e67b7fabbe64b604e33a3404d619c0b2a21b3100f082ec089385" - }, - { - "pod": "hwlab-edge-proxy-5c4c948c68-hwqp7", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:81636f2218282c43a0a107102f61f65f209f52fdfa25f883f418b274c1ca7ad1" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-patch-panel-784bdb49cc-z5jhz", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:521baa534c73bbcbb6b6f01f67574061c4c83178a83fd6f2dedf2ad0a62a228c" - }, - { - "pod": "hwlab-router-9954c75cb-zkmm2", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:e3f26f30d611a9bd63571bef600caeea5ca0770c5bf9b2d921abe74cb1ba1463" - }, - { - "pod": "hwlab-tunnel-client-86d5dc9b6-svcq4", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:f4d88c49f5bb03f3919c8a09f80a55abb63b8dd63bc0dd227b0608e99804d688" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "digestPlaceholder": "not_published", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "86b39769af98946124398dda6775d4b2a3b205e6", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:3a24ac29f2dbf5ffebef1b02811311cb460f603c28c6c5588d8674b8032a5bc5", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:3a24ac29f2dbf5ffebef1b02811311cb460f603c28c6c5588d8674b8032a5bc5", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cloud-web", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "86b39769af98946124398dda6775d4b2a3b205e6", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:c768656bcc3fdc211e67c5ef4d53f0265e9abad8ce74006bac0c08e400acdd30", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:c768656bcc3fdc211e67c5ef4d53f0265e9abad8ce74006bac0c08e400acdd30", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-edge-proxy", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "86b39769af98946124398dda6775d4b2a3b205e6", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:79368e49a3d69c7f126935c791ec6fc8272bb507cd28e8604c870bc113988137", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:79368e49a3d69c7f126935c791ec6fc8272bb507cd28e8604c870bc113988137", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - } - ] - }, - "serviceCount": 3, - "requiredServiceCount": 3, - "disabledServiceCount": 0, - "builtCount": 3, - "publishedCount": 3, - "generatedAt": "2026-05-23T19:44:45.907Z", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:3a24ac29f2dbf5ffebef1b02811311cb460f603c28c6c5588d8674b8032a5bc5", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:3a24ac29f2dbf5ffebef1b02811311cb460f603c28c6c5588d8674b8032a5bc5", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-cloud-api/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:d1974c8c7ed1ed226c6a8a8f57922d5b53126d05d33935510d52e0790d91c3d3" - }, - { - "serviceId": "hwlab-cloud-web", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:c768656bcc3fdc211e67c5ef4d53f0265e9abad8ce74006bac0c08e400acdd30", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:c768656bcc3fdc211e67c5ef4d53f0265e9abad8ce74006bac0c08e400acdd30", - "runtimeKind": "cloud-web", - "distFreshness": { - "status": "pass", - "distPath": "web/hwlab-cloud-web/dist", - "buildCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "freshnessCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "files": [ - { - "kind": "runtime", - "path": "index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/index.html", - "status": "match" - }, - { - "kind": "runtime", - "path": "styles.css", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/styles.css", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/styles.css", - "status": "match" - }, - { - "kind": "runtime", - "path": "auth.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/auth.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/auth.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "app.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/app.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/app.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-facts.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/code-agent-facts.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/code-agent-facts.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/code-agent-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/code-agent-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-m3-evidence.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/code-agent-m3-evidence.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/code-agent-m3-evidence.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "live-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/live-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/live-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "gate-summary.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/gate-summary.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/gate-summary.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "runtime.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/runtime.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/runtime.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "wiring-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/wiring-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/wiring-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "workbench-hardware-panel.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/workbench-hardware-panel.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/workbench-hardware-panel.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "help.md", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/help.md", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/help.md", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/marked.esm.js", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/third_party/marked/marked.esm.js", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/LICENSE", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/third_party/marked/LICENSE", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/third_party/marked/LICENSE", - "status": "match" - }, - { - "kind": "alias", - "path": "gate/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "diagnostics/gate/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/diagnostics/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "help/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-code-agent-timeout-224-current/web/hwlab-cloud-web/dist/help/index.html", - "status": "match" - } - ], - "mismatches": [] - }, - "implementationState": "static-web-wrapper", - "sourceState": "source-present", - "entrypoint": "web/hwlab-cloud-web/index.html", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:89fe08f90c885b263615c9b943a2682275de28e26ff1dd4e604c3958e83b9646" - }, - { - "serviceId": "hwlab-edge-proxy", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:86b3976", - "imageTag": "86b3976", - "buildCreatedAt": "2026-05-23T19:42:29.915Z", - "buildSource": "pikasTech/HWLAB@86b39769af98946124398dda6775d4b2a3b205e6", - "digest": "sha256:79368e49a3d69c7f126935c791ec6fc8272bb507cd28e8604c870bc113988137", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:79368e49a3d69c7f126935c791ec6fc8272bb507cd28e8604c870bc113988137", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-edge-proxy/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:ea70175703176aad767bc39b536d5fa586071ca557c390c90c4aa85a5c7706ee" - } - ], - "blockers": [] - }, - "notes": "DEV-only artifact report. Do not treat runtime placeholder images as real service implementations." -} diff --git a/reports/dev-gate/dev-artifacts-hwlab-277.json b/reports/dev-gate/dev-artifacts-hwlab-277.json deleted file mode 100644 index b7ab3ce8..00000000 --- a/reports/dev-gate/dev-artifacts-hwlab-277.json +++ /dev/null @@ -1,733 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-artifact-publish.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-artifacts.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "commitId": "9e15bac", - "acceptanceLevel": "dev_artifact_publish", - "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 DEV artifact publish report; it is not a DEV-LIVE runtime substitute." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/artifact-catalog.md", - "docs/dev-artifact-publish.md", - "docs/dev-base-image-preflight.md" - ], - "summary": "DEV artifact publish evidence for pikasTech/HWLAB#35, stored in the DEV gate report envelope." - }, - "validationCommands": [ - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-artifact-services.mjs", - "node --check scripts/src/registry-capabilities.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", - "node scripts/dev-artifact-publish.mjs --preflight --no-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "This report records artifact build/publish state only; local smoke remains a separate gate." - ], - "summary": "Local smoke is not implied by artifact publication." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - ], - "evidence": [ - "This report records DEV artifact build/publish state only; deployment dry-run remains separate." - ], - "summary": "DEV deploy dry-run is not implied by artifact publication." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "Registry prefix is localhost or private/internal only.", - "DEV builder base image preflight returns ready before build or publish.", - "Environment remains dev and namespace remains hwlab-dev.", - "PROD profile remains disabled.", - "No secret or token material is required by this workflow." - ], - "summary": "Fatal DEV-only artifact preconditions passed." - }, - "blockers": [], - "artifactPublish": { - "issue": "pikasTech/HWLAB#35", - "status": "built", - "mode": "build", - "repo": "pikasTech/HWLAB", - "sourceCommitId": "9e15bac41895159e7537fb5728fdff5498ac66eb", - "registryPrefix": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-23T20:12:05.578Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 6 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-749c489bf9-mcsdp", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:02424bd81b280535f7877a0febb4ac85b3e8d4009525e70a35e8e3dc37dc5443" - }, - { - "pod": "hwlab-agent-skills-7d5cf68bcd-wkcwv", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:26831fd6848abc50a4e8158d97fe5ec0a769a54abd4b9558249baa1691fd2758" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-cloud-api-6c565c556-nmvbh", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:69543a5c537e10593d790dba00e3bdb1e19aaad3790d44e0964d371d0fddab20" - }, - { - "pod": "hwlab-cloud-web-5f74d5d74-mfzqh", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:28378a2582a9e67b7fabbe64b604e33a3404d619c0b2a21b3100f082ec089385" - }, - { - "pod": "hwlab-edge-proxy-5c4c948c68-hwqp7", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:81636f2218282c43a0a107102f61f65f209f52fdfa25f883f418b274c1ca7ad1" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-patch-panel-784bdb49cc-z5jhz", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:521baa534c73bbcbb6b6f01f67574061c4c83178a83fd6f2dedf2ad0a62a228c" - }, - { - "pod": "hwlab-router-9954c75cb-zkmm2", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:e3f26f30d611a9bd63571bef600caeea5ca0770c5bf9b2d921abe74cb1ba1463" - }, - { - "pod": "hwlab-tunnel-client-86d5dc9b6-svcq4", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:f4d88c49f5bb03f3919c8a09f80a55abb63b8dd63bc0dd227b0608e99804d688" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "baseImage": "node:20-bookworm-slim", - "baseImagePreflight": { - "status": "ready", - "imageSource": "local-docker-node:20-*", - "requestedImage": null, - "localTag": "node:20-bookworm-slim", - "imageId": "sha256:9da6b4e352d0d5c94963eba1832408f5b7b08839cd8be9b6610c05de5118c704", - "repoDigests": [ - "node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0" - ], - "candidates": [ - { - "source": "local-docker-node:20-*", - "approval": { - "approved": true, - "reason": "local Node 20 builder base image" - }, - "localTag": "node:20-bookworm-slim", - "imageId": "9da6b4e352d0", - "size": "200MB", - "createdSince": "4 weeks ago" - } - ], - "rejectedCandidates": [], - "publishUsable": true, - "blockers": [], - "nextSteps": [ - "Use the reported localTag as the DEV builder base image for artifact publish.", - "Keep publish DEV-only and record the produced HWLAB service image tags and digests separately." - ], - "recommendation": { - "source": "Preloaded D601 local Docker cache or D601-local registry tag.", - "envVar": "HWLAB_DEV_BASE_IMAGE", - "preferredImageTag": "node:20-bookworm-slim", - "preferredHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "recommendedEnvValue": "node:20-bookworm-slim" - }, - "provision": { - "required": false, - "exactBlocker": null, - "expectedImageTag": "node:20-bookworm-slim", - "recommendedEnv": { - "name": "HWLAB_DEV_BASE_IMAGE", - "value": "node:20-bookworm-slim", - "assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim" - }, - "recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "nextHumanCommand": null, - "commands": { - "d601OnlinePreload": [ - "docker pull node:20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "d601HwlabTagProvision": [ - "docker pull node:20-bookworm-slim", - "docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "offlineSourceExport": [ - "docker pull node:20-bookworm-slim", - "docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar" - ], - "offlineD601Import": [ - "docker load -i hwlab-node20-bookworm-slim.tar", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "artifactPublishDryRun": [ - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/dev-artifact-publish.mjs --preflight --no-report" - ] - } - }, - "blockedReport": null, - "containerEngine": { - "command": "docker", - "available": true, - "version": "Docker version 26.1.5+dfsg1, build a72d7cd", - "errorCode": null, - "stderr": "" - } - }, - "environment": "dev", - "buildCreatedAt": "2026-05-23T20:12:05.584Z", - "serviceInventory": { - "version": "v2", - "serviceCount": 1, - "requiredServiceCount": 1, - "disabledServiceCount": 0, - "requiredServiceIds": [ - "hwlab-cloud-web" - ], - "disabledServiceIds": [], - "services": [ - { - "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 - } - ] - }, - "publishPlan": { - "version": "v2", - "mode": "build", - "sourceCommitId": "9e15bac41895159e7537fb5728fdff5498ac66eb", - "imageTag": "9e15bac", - "registryTarget": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-23T20:12:05.578Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 6 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-749c489bf9-mcsdp", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:02424bd81b280535f7877a0febb4ac85b3e8d4009525e70a35e8e3dc37dc5443" - }, - { - "pod": "hwlab-agent-skills-7d5cf68bcd-wkcwv", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:26831fd6848abc50a4e8158d97fe5ec0a769a54abd4b9558249baa1691fd2758" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:936addebe7d7132198fd025a90d7d2c5b0d1131a98dc9f695abca0a7921fcdca" - }, - { - "pod": "hwlab-cloud-api-6c565c556-nmvbh", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:69543a5c537e10593d790dba00e3bdb1e19aaad3790d44e0964d371d0fddab20" - }, - { - "pod": "hwlab-cloud-web-5f74d5d74-mfzqh", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:28378a2582a9e67b7fabbe64b604e33a3404d619c0b2a21b3100f082ec089385" - }, - { - "pod": "hwlab-edge-proxy-5c4c948c68-hwqp7", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:81636f2218282c43a0a107102f61f65f209f52fdfa25f883f418b274c1ca7ad1" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:18c12a58a6b36cd1d5b2f0dadd166a7fca2c8e472a975797e1a7474f172413bd" - }, - { - "pod": "hwlab-patch-panel-784bdb49cc-z5jhz", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:521baa534c73bbcbb6b6f01f67574061c4c83178a83fd6f2dedf2ad0a62a228c" - }, - { - "pod": "hwlab-router-9954c75cb-zkmm2", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:e3f26f30d611a9bd63571bef600caeea5ca0770c5bf9b2d921abe74cb1ba1463" - }, - { - "pod": "hwlab-tunnel-client-86d5dc9b6-svcq4", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:f09ad05", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:f4d88c49f5bb03f3919c8a09f80a55abb63b8dd63bc0dd227b0608e99804d688" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "digestPlaceholder": "not_published", - "services": [ - { - "serviceId": "hwlab-cloud-web", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "9e15bac41895159e7537fb5728fdff5498ac66eb", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:9e15bac", - "imageTag": "9e15bac", - "buildCreatedAt": "2026-05-23T20:12:05.584Z", - "buildSource": "pikasTech/HWLAB@9e15bac41895159e7537fb5728fdff5498ac66eb", - "digest": "not_published", - "repositoryDigest": null, - "digestStatus": "placeholder", - "status": "built", - "notPublishedReason": "build_only_not_published" - } - ] - }, - "serviceCount": 1, - "requiredServiceCount": 1, - "disabledServiceCount": 0, - "builtCount": 1, - "publishedCount": 0, - "generatedAt": "2026-05-23T20:12:43.943Z", - "services": [ - { - "serviceId": "hwlab-cloud-web", - "status": "built", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:9e15bac", - "imageTag": "9e15bac", - "buildCreatedAt": "2026-05-23T20:12:05.584Z", - "buildSource": "pikasTech/HWLAB@9e15bac41895159e7537fb5728fdff5498ac66eb", - "digest": "not_published", - "repositoryDigest": null, - "runtimeKind": "cloud-web", - "distFreshness": { - "status": "pass", - "distPath": "web/hwlab-cloud-web/dist", - "buildCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "freshnessCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "files": [ - { - "kind": "runtime", - "path": "index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/index.html", - "status": "match" - }, - { - "kind": "runtime", - "path": "styles.css", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/styles.css", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/styles.css", - "status": "match" - }, - { - "kind": "runtime", - "path": "auth.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/auth.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/auth.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "app.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/app.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/app.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-facts.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/code-agent-facts.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/code-agent-facts.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/code-agent-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/code-agent-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-m3-evidence.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/code-agent-m3-evidence.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/code-agent-m3-evidence.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "live-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/live-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/live-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "gate-summary.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/gate-summary.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/gate-summary.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "runtime.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/runtime.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/runtime.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "wiring-status.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/wiring-status.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/wiring-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "workbench-hardware-panel.mjs", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/workbench-hardware-panel.mjs", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/workbench-hardware-panel.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "help.md", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/help.md", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/help.md", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/marked.esm.js", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/third_party/marked/marked.esm.js", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/LICENSE", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/third_party/marked/LICENSE", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/third_party/marked/LICENSE", - "status": "match" - }, - { - "kind": "alias", - "path": "gate/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "diagnostics/gate/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/diagnostics/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "help/index.html", - "sourcePath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/index.html", - "distPath": "/workspace/hwlab/.worktrees/fix-277-current-main/web/hwlab-cloud-web/dist/help/index.html", - "status": "match" - } - ], - "mismatches": [] - }, - "implementationState": "static-web-wrapper", - "sourceState": "source-present", - "entrypoint": "web/hwlab-cloud-web/index.html", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": "build_only_not_published", - "localImageId": "sha256:d7366b68c60ee2bad8a7add0bd8bcf8b7a74874cf87b89863f2c0547e97e48cd" - } - ], - "blockers": [] - }, - "notes": "DEV-only artifact report. Do not treat runtime placeholder images as real service implementations." -} diff --git a/reports/dev-gate/dev-artifacts.json b/reports/dev-gate/dev-artifacts.json deleted file mode 100644 index c7886708..00000000 --- a/reports/dev-gate/dev-artifacts.json +++ /dev/null @@ -1,1309 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-artifact-publish.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-artifacts.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "commitId": "48dfbf9", - "acceptanceLevel": "dev_artifact_publish", - "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 DEV artifact publish report; it is not a DEV-LIVE runtime substitute." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/artifact-catalog.md", - "docs/dev-artifact-publish.md", - "docs/dev-base-image-preflight.md" - ], - "summary": "DEV artifact publish evidence for pikasTech/HWLAB#35, stored in the DEV gate report envelope." - }, - "validationCommands": [ - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-artifact-services.mjs", - "node --check scripts/src/registry-capabilities.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", - "node scripts/dev-artifact-publish.mjs --preflight --no-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "This report records artifact build/publish state only; local smoke remains a separate gate." - ], - "summary": "Local smoke is not implied by artifact publication." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - ], - "evidence": [ - "This report records DEV artifact build/publish state only; deployment dry-run remains separate." - ], - "summary": "DEV deploy dry-run is not implied by artifact publication." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "Registry prefix is localhost or private/internal only.", - "DEV builder base image preflight returns ready before build or publish.", - "Environment remains dev and namespace remains hwlab-dev.", - "PROD profile remains disabled.", - "No secret or token material is required by this workflow." - ], - "summary": "Fatal DEV-only artifact preconditions passed." - }, - "blockers": [], - "artifactPublish": { - "issue": "pikasTech/HWLAB#35", - "status": "published", - "mode": "publish", - "repo": "pikasTech/HWLAB", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryPrefix": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-24T00:07:33.358Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 10 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c5f796204e9609a191230d9faa3bd7740ad50c4977a7eee7ef545665ccf4097c" - }, - { - "pod": "hwlab-agent-skills-69485c4dc4-mpgdb", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:4b006f52cf27438956f5269d39483d2ebcf8966b30cf19cd378616b423f93e1f" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:fff35a6eec4a70375122f87636759de62039465449053c584d9bc9572b7d1b0d" - }, - { - "pod": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:cd46ef24e97f430d7761b2cb7100990eda9f79ddf35f8ffc58fba81cd75e9acf" - }, - { - "pod": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:a95189dd0cabf9197b7a15dc4cc2995eccf27898e2b0e26c7fd9cbf952fe8119" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-patch-panel-55b9c584d7-7rzqb", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:36f569ce71dd9da2c8432ded7f00be9b9e51d0ec5409eeee871dfc3efbd9dbc9" - }, - { - "pod": "hwlab-router-76fd584bdb-t26cs", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:258cc4231a7e0c4de47cce9a8be37b60c9a8a942d09d91b695b006f0dfc396d9" - }, - { - "pod": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:e44e0a0f5b3e88014c85414feeecc892a34e73e2137160cc905d526aede6cf02" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "baseImage": "node:20-bookworm-slim", - "baseImagePreflight": { - "status": "ready", - "imageSource": "local-docker-node:20-*", - "requestedImage": null, - "localTag": "node:20-bookworm-slim", - "imageId": "sha256:9da6b4e352d0d5c94963eba1832408f5b7b08839cd8be9b6610c05de5118c704", - "repoDigests": [ - "node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0" - ], - "candidates": [ - { - "source": "local-docker-node:20-*", - "approval": { - "approved": true, - "reason": "local Node 20 builder base image" - }, - "localTag": "node:20-bookworm-slim", - "imageId": "9da6b4e352d0", - "size": "200MB", - "createdSince": "4 weeks ago" - } - ], - "rejectedCandidates": [], - "publishUsable": true, - "blockers": [], - "nextSteps": [ - "Use the reported localTag as the DEV builder base image for artifact publish.", - "Keep publish DEV-only and record the produced HWLAB service image tags and digests separately." - ], - "recommendation": { - "source": "Preloaded D601 local Docker cache or D601-local registry tag.", - "envVar": "HWLAB_DEV_BASE_IMAGE", - "preferredImageTag": "node:20-bookworm-slim", - "preferredHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "recommendedEnvValue": "node:20-bookworm-slim" - }, - "provision": { - "required": false, - "exactBlocker": null, - "expectedImageTag": "node:20-bookworm-slim", - "recommendedEnv": { - "name": "HWLAB_DEV_BASE_IMAGE", - "value": "node:20-bookworm-slim", - "assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim" - }, - "recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "nextHumanCommand": null, - "commands": { - "d601OnlinePreload": [ - "docker pull node:20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "d601HwlabTagProvision": [ - "docker pull node:20-bookworm-slim", - "docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "offlineSourceExport": [ - "docker pull node:20-bookworm-slim", - "docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar" - ], - "offlineD601Import": [ - "docker load -i hwlab-node20-bookworm-slim.tar", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "artifactPublishDryRun": [ - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/dev-artifact-publish.mjs --preflight --no-report" - ] - } - }, - "blockedReport": null, - "containerEngine": { - "command": "docker", - "available": true, - "version": "Docker version 26.1.5+dfsg1, build a72d7cd", - "errorCode": null, - "stderr": "" - } - }, - "environment": "dev", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "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": "publish", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "imageTag": "48dfbf9", - "registryTarget": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-24T00:07:33.358Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 10 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c5f796204e9609a191230d9faa3bd7740ad50c4977a7eee7ef545665ccf4097c" - }, - { - "pod": "hwlab-agent-skills-69485c4dc4-mpgdb", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:4b006f52cf27438956f5269d39483d2ebcf8966b30cf19cd378616b423f93e1f" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:fff35a6eec4a70375122f87636759de62039465449053c584d9bc9572b7d1b0d" - }, - { - "pod": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:cd46ef24e97f430d7761b2cb7100990eda9f79ddf35f8ffc58fba81cd75e9acf" - }, - { - "pod": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:a95189dd0cabf9197b7a15dc4cc2995eccf27898e2b0e26c7fd9cbf952fe8119" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-patch-panel-55b9c584d7-7rzqb", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:36f569ce71dd9da2c8432ded7f00be9b9e51d0ec5409eeee871dfc3efbd9dbc9" - }, - { - "pod": "hwlab-router-76fd584bdb-t26cs", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:258cc4231a7e0c4de47cce9a8be37b60c9a8a942d09d91b695b006f0dfc396d9" - }, - { - "pod": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:e44e0a0f5b3e88014c85414feeecc892a34e73e2137160cc905d526aede6cf02" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "digestPlaceholder": "not_published", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cloud-web", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-mgr", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-worker", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-worker@sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway@sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway-simu", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-box-simu", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-patch-panel", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-router", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-router@sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-tunnel-client", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-edge-proxy", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cli", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cli@sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-skills", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - } - ] - }, - "serviceCount": 13, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "builtCount": 13, - "publishedCount": 13, - "generatedAt": "2026-05-24T00:18:16.112Z", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-cloud-api/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:9c816bbd693420beb2f776341ca6af1013123923396a6327e010d85b51e510d8" - }, - { - "serviceId": "hwlab-cloud-web", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "runtimeKind": "cloud-web", - "distFreshness": { - "status": "pass", - "distPath": "web/hwlab-cloud-web/dist", - "buildCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "freshnessCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "files": [ - { - "kind": "runtime", - "path": "index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/index.html", - "status": "match" - }, - { - "kind": "runtime", - "path": "styles.css", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/styles.css", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/styles.css", - "status": "match" - }, - { - "kind": "runtime", - "path": "auth.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/auth.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/auth.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "app.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/app.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/app.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-facts.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-facts.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-facts.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-m3-evidence.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-m3-evidence.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-m3-evidence.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "live-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/live-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/live-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "gate-summary.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/gate-summary.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/gate-summary.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "runtime.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/runtime.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/runtime.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "wiring-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/wiring-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/wiring-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "workbench-hardware-panel.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/workbench-hardware-panel.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/workbench-hardware-panel.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "help.md", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/help.md", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/help.md", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/marked.esm.js", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/third_party/marked/marked.esm.js", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/LICENSE", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/third_party/marked/LICENSE", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/third_party/marked/LICENSE", - "status": "match" - }, - { - "kind": "alias", - "path": "gate/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "diagnostics/gate/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/diagnostics/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "help/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/help/index.html", - "status": "match" - } - ], - "mismatches": [] - }, - "implementationState": "static-web-wrapper", - "sourceState": "source-present", - "entrypoint": "web/hwlab-cloud-web/index.html", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:3318fd51a8778fa713c7c1cd8697f0a49d59457a9df50011692e2161d90d01e5" - }, - { - "serviceId": "hwlab-agent-mgr", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-agent-mgr/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:07f95021ca29226179f93b3c04134f7015e2a0adc55f1855da29a35faf345fc8" - }, - { - "serviceId": "hwlab-agent-worker", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-worker@sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-agent-worker/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:7fcc27b11dbdfdbbb099f29f6d0299b5858172a432d8c69540a53331f62eeccc" - }, - { - "serviceId": "hwlab-gateway", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway@sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-gateway/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:9a7a4df65534a20e87b9e3c772b4dc7521dc28f0a3f6e83fd9c4893a716962cf" - }, - { - "serviceId": "hwlab-gateway-simu", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-gateway-simu/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:49336246347a76b52992b3101d068128cb56aa8f00d06bf50e8170959e5ace08" - }, - { - "serviceId": "hwlab-box-simu", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-box-simu/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:396feaf03e04de49b28323fe3604690b3ae3032fb942ddc451aa195b936ac2ef" - }, - { - "serviceId": "hwlab-patch-panel", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-patch-panel/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:ddb03264993e56e8c69ac98f9b59cc1e3bbb04ea6a6ce8f5deac99d2830f3390" - }, - { - "serviceId": "hwlab-router", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-router@sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-router/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:a62d9de35de70c79d1c4ded8f29fd0e17f9c326130f5e5891de1e3486bcb0a32" - }, - { - "serviceId": "hwlab-tunnel-client", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-tunnel-client/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:8f8d93f293f9bc431cbc6141a667728d4e5fbbdc2da63088e640309910239d3f" - }, - { - "serviceId": "hwlab-edge-proxy", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "runtimeKind": "node-command", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "cmd/hwlab-edge-proxy/main.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:2884b8ce00a38b74696d8d49780390ba8df2dd0b1558922f348cafd5b107b933" - }, - { - "serviceId": "hwlab-cli", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cli@sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "runtimeKind": "cli", - "implementationState": "repo-entrypoint", - "sourceState": "source-present", - "entrypoint": "tools/hwlab-cli/bin/hwlab-cli.mjs", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:ca36b3a47ba312d09230547f3c6880b3353b89962228aba7509f705ef02dfd9f" - }, - { - "serviceId": "hwlab-agent-skills", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "runtimeKind": "skills-bundle", - "implementationState": "repo-bundle", - "sourceState": "source-present", - "entrypoint": "skills/hwlab-agent-runtime/SKILL.md", - "publishEnabled": true, - "artifactRequired": true, - "artifactScope": "required", - "notPublishedReason": null, - "localImageId": "sha256:907bc7cead49aa9b82b2c3b905752808c8b692d6140340e255e5f201f4d7cb46" - } - ], - "blockers": [] - }, - "notes": "DEV-only artifact report. Do not treat runtime placeholder images as real service implementations." -} diff --git a/reports/dev-gate/dev-cd-apply.json b/reports/dev-gate/dev-cd-apply.json deleted file mode 100644 index f457afc3..00000000 --- a/reports/dev-gate/dev-cd-apply.json +++ /dev/null @@ -1,7378 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-cd-apply.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-cd-apply.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#274", - "taskId": "dev-cd-apply", - "commitId": "48dfbf9", - "acceptanceLevel": "dev_cd_apply", - "devOnly": true, - "prodDisabled": true, - "status": "blocked", - "generatedAt": "2026-05-24T00:18:39.740Z", - "reportLifecycle": { - "version": "v1", - "state": "active", - "activeEndpoint": "http://74.48.78.17:16667", - "activeBrowserEndpoint": "http://74.48.78.17:16666", - "deprecatedEndpoint": null, - "summary": "Current DEV CD transaction report; the only DEV side-effect entrypoint is scripts/dev-cd-apply.mjs." - }, - "supports": [ - "pikasTech/HWLAB#274", - "pikasTech/HWLAB#116", - "pikasTech/HWLAB#235", - "pikasTech/HWLAB#50", - "pikasTech/HWLAB#57", - "pikasTech/HWLAB#67", - "pikasTech/HWLAB#73" - ], - "sourceContract": { - "status": "blocked", - "documents": [ - "docs/reference/deployment-publish.md", - "docs/reference/dev-runtime-boundary.md", - "docs/dev-artifact-publish.md", - "docs/dev-deploy-apply.md", - "docs/artifact-catalog.md" - ], - "summary": "DEV CD publish/apply/verify side effects are serialized by one Kubernetes Lease and use deploy/deploy.json as the apply desired-state source." - }, - "validationCommands": [ - "node --check scripts/dev-cd-apply.mjs", - "node --check scripts/src/dev-cd-apply.mjs", - "node --test scripts/src/dev-cd-apply.test.mjs", - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-deploy-apply.mjs", - "node --test scripts/src/dev-deploy-apply.test.mjs", - "node scripts/deploy-desired-state-plan.mjs --check", - "node scripts/validate-artifact-catalog.mjs", - "git diff --check" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "npm run check" - ], - "evidence": [ - "Local checks are listed as validation commands and are not implied by a DEV CD transaction." - ], - "summary": "The transaction report records DEV publish/apply/verify evidence; local smoke is a separate gate." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report" - ], - "evidence": [ - "The transaction path owns live side effects; dry-run remains a preflight support mode." - ], - "summary": "DEV CD mutation requires --apply and the transaction Lease lock." - }, - "devPreconditions": { - "status": "blocked", - "requirements": [ - "DEV CD apply must force KUBECONFIG=/etc/rancher/k3s/k3s.yaml and verify native node d601 before any Lease, Job, apply, or rollout side effect.", - "DEV CD apply must reject docker-desktop, desktop-control-plane, 127.0.0.1:11700, bare kubectl ambiguity, and second hwlab-dev control-plane signals before mutation.", - "DEV CD apply must verify required SecretRef existence and key names without reading or printing Secret values.", - "HEAD must match the requested target ref before publish.", - "Lease/hwlab-dev-cd-lock must be acquired before publish/apply.", - "Legacy publish/apply side-effect scripts must run with HWLAB_CD_TRANSACTION_ID.", - "DEV DB role/database provisioning and runtime migration must run through repo-owned commands before workload apply.", - "deploy/deploy.json, artifact catalog, and workloads must converge before apply.", - "When deploy/deploy.json already points to a published promotion commit, HEAD may be a later control commit and the transaction must consume that promotion without republishing HEAD.", - "Public 16666 and 16667 live health plus M3 durable postflight must be recorded in this report." - ], - "summary": "One or more DEV CD transaction preconditions failed." - }, - "transaction": { - "transactionId": "5332b2f8-5619-490e-8100-e79fa427f6f9", - "ownerTaskId": "DC-DCSN-P0-2026-003-D601", - "targetNamespace": "hwlab-dev", - "lockName": "hwlab-dev-cd-lock", - "lockBackend": "Lease", - "startedAt": "2026-05-24T00:07:29.282Z", - "finishedAt": "2026-05-24T00:18:39.740Z", - "ttlSeconds": 3600, - "phases": [ - { - "phase": "publishing", - "status": "entered", - "at": "2026-05-24T00:07:31.483Z" - }, - { - "phase": "applying", - "status": "entered", - "at": "2026-05-24T00:18:16.303Z" - }, - { - "phase": "verifying", - "status": "entered", - "at": "2026-05-24T00:18:29.423Z" - }, - { - "phase": "released", - "status": "released", - "at": "2026-05-24T00:18:39.740Z" - } - ], - "release": { - "status": "released", - "command": "kubectl -n hwlab-dev replace -f - -o json", - "lock": { - "lockBackend": "Lease", - "lockName": "hwlab-dev-cd-lock", - "resourceVersion": "1519233", - "holderIdentity": "", - "promotionCommit": "48dfbf9", - "deployJsonHash": "sha256:b23766b798d1ecacb87d30b94ee8ffe77e2ccbe7772ce27e5c94ee152d06f543", - "ownerTaskId": "DC-DCSN-P0-2026-003-D601", - "transactionId": "5332b2f8-5619-490e-8100-e79fa427f6f9", - "phase": "released", - "startedAt": "2026-05-24T00:07:29.483000Z", - "updatedAt": "2026-05-24T00:18:39.700000Z", - "ttlSeconds": 3600, - "liveBefore": { - "status": "pass", - "expectedCommit": null, - "endpoints": [ - { - "id": "cloud-web-16666", - "url": "http://74.48.78.17:16666/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.593Z", - "finishedAt": "2026-05-24T00:07:30.157Z", - "expectedServiceId": "hwlab-cloud-web", - "expectedCommit": null, - "serviceId": "hwlab-cloud-web", - "environment": "dev", - "applicationStatus": "ok", - "ready": null, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageTag": "3df89fe", - "observedAt": null, - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - }, - { - "id": "cloud-api-16667", - "url": "http://74.48.78.17:16667/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.594Z", - "finishedAt": "2026-05-24T00:07:31.417Z", - "expectedServiceId": "hwlab-cloud-api", - "expectedCommit": null, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "applicationStatus": "ok", - "ready": true, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageTag": "3df89fe", - "observedAt": "2026-05-24T00:07:30.656Z", - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - } - ], - "summary": { - "checked": 2, - "reachable": 2, - "identityMatches": 2, - "commitMatches": null, - "ports": [ - 16666, - 16667 - ] - } - }, - "targetNamespace": "hwlab-dev", - "targetRef": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "releasedAt": "2026-05-24T00:18:39.700000Z", - "releaseStatus": "blocked", - "staleLockBrokenAt": null, - "staleLockPrevious": null - } - } - }, - "devCdApply": { - "mode": "apply", - "target": { - "ref": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "promotionCommit": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "shortCommitId": "48dfbf9", - "promotionSource": "target-ref", - "publishRequired": true, - "controlRef": { - "ref": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "commitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "shortCommitId": "48dfbf9" - }, - "headCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "headMatchesTarget": true, - "desiredStateCheck": null, - "artifactBoundary": { - "status": "degraded", - "controlCommit": { - "commitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "shortCommitId": "48dfbf9" - }, - "artifactMergeCommit": { - "commitId": "b893116e59f54d671b88dcab34072c2f1671540e", - "shortCommitId": "b893116", - "isMergeCommit": true, - "parents": [ - "034c90763cdcff5083db02612cb9d0d137904860", - "1f292ffd683a124acbfe019396ec2cd200c117e8" - ], - "promotionParent": "034c90763cdcff5083db02612cb9d0d137904860", - "artifactParent": "1f292ffd683a124acbfe019396ec2cd200c117e8", - "promotionParentMatches": false, - "changedPaths": [ - "deploy/deploy.json", - "deploy/k8s/base/workloads.yaml" - ] - }, - "artifactCommit": { - "commitId": "1f292ffd683a124acbfe019396ec2cd200c117e8", - "shortCommitId": "1f292ff", - "parents": [ - "034c90763cdcff5083db02612cb9d0d137904860" - ], - "promotionParentMatches": false - }, - "desiredState": { - "deployCommitId": "48dfbf9", - "deployJsonHash": "sha256:b23766b798d1ecacb87d30b94ee8ffe77e2ccbe7772ce27e5c94ee152d06f543", - "catalogCommitId": "48dfbf9", - "catalogHash": "sha256:0a501437652f961c9fad01c6036297efea318f1ef29ca14fb72f29ab6c3bdf8e", - "reportCommitId": "606156f", - "reportSourceCommitId": "606156f604e507f8725f6ae9040cea8204375a86", - "reportHash": "sha256:a5a1818df4fa43f49270ee383a7dec77050a387420dfbea04ef5bc81f02018ee", - "deployCommitMatches": true, - "catalogCommitMatches": true, - "reportCommitMatches": false, - "desiredStatePaths": [ - "deploy/artifact-catalog.dev.json", - "deploy/deploy.json", - "deploy/k8s/base/workloads.yaml", - "reports/dev-gate/dev-artifacts.json" - ] - } - }, - "namespace": "hwlab-dev", - "apiEndpoint": "http://74.48.78.17:16667", - "browserEndpoint": "http://74.48.78.17:16666/" - }, - "deployJson": { - "before": { - "path": "deploy/deploy.json", - "hash": "sha256:b23766b798d1ecacb87d30b94ee8ffe77e2ccbe7772ce27e5c94ee152d06f543", - "commitId": "48dfbf9", - "namespace": "hwlab-dev", - "environment": "dev", - "endpoint": "http://74.48.78.17:16667", - "manifest": { - "manifestVersion": "v1", - "environment": "dev", - "commitId": "48dfbf9", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "health": { - "method": "GET", - "path": "/health/live", - "responseFormat": "json" - }, - "publicEndpoints": { - "frontend": { - "url": "http://74.48.78.17:16666", - "protocol": "http", - "host": "74.48.78.17", - "port": 16666, - "serviceId": "hwlab-cloud-web", - "frpProxy": "hwlab-dev-cloud-web" - }, - "api": { - "url": "http://74.48.78.17:16667", - "protocol": "http", - "host": "74.48.78.17", - "port": 16667, - "serviceId": "hwlab-edge-proxy", - "frpProxy": "hwlab-dev-edge-proxy" - } - }, - "frp": { - "server": { - "address": "74.48.78.17", - "bindPort": 7000 - }, - "proxies": [ - { - "name": "hwlab-dev-cloud-web", - "endpointId": "frontend", - "type": "tcp", - "localServiceId": "hwlab-cloud-web", - "localHost": "hwlab-cloud-web.hwlab-dev.svc.cluster.local", - "localPort": 8080, - "remotePort": 16666 - }, - { - "name": "hwlab-dev-edge-proxy", - "endpointId": "api", - "type": "tcp", - "localServiceId": "hwlab-edge-proxy", - "localHost": "hwlab-edge-proxy.hwlab-dev.svc.cluster.local", - "localPort": 6667, - "remotePort": 16667 - }, - { - "name": "hwlab-dev-tunnel-health", - "type": "tcp", - "localServiceId": "hwlab-tunnel-client", - "localHost": "hwlab-tunnel-client.hwlab-dev.svc.cluster.local", - "localPort": 7402, - "remotePort": 7402 - } - ] - }, - "k3s": { - "serviceMappings": [ - { - "serviceId": "hwlab-cloud-api", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "port": 6667, - "targetPort": "http" - }, - { - "serviceId": "hwlab-cloud-web", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "port": 8080, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "port": 7001, - "targetPort": "http" - }, - { - "serviceId": "hwlab-cli", - "name": "hwlab-cli", - "namespace": "hwlab-dev", - "port": 7501, - "targetPort": "http" - }, - { - "serviceId": "hwlab-agent-skills", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "port": 7430, - "targetPort": "http" - }, - { - "serviceId": "hwlab-agent-mgr", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "port": 7410, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway-simu", - "name": "hwlab-gateway-simu-1", - "namespace": "hwlab-dev", - "port": 7101, - "targetPort": "http" - }, - { - "serviceId": "hwlab-box-simu", - "name": "hwlab-box-simu-1", - "namespace": "hwlab-dev", - "port": 7201, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway-simu", - "name": "hwlab-gateway-simu-2", - "namespace": "hwlab-dev", - "port": 7101, - "targetPort": "http" - }, - { - "serviceId": "hwlab-box-simu", - "name": "hwlab-box-simu-2", - "namespace": "hwlab-dev", - "port": 7201, - "targetPort": "http" - }, - { - "serviceId": "hwlab-patch-panel", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "port": 7301, - "targetPort": "http" - }, - { - "serviceId": "hwlab-router", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "port": 7401, - "targetPort": "http" - }, - { - "serviceId": "hwlab-tunnel-client", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "port": 7402, - "targetPort": "http" - }, - { - "serviceId": "hwlab-edge-proxy", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "port": 6667, - "targetPort": "http" - } - ] - }, - "profiles": { - "dev": { - "name": "dev", - "enabled": true, - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "notes": "DEV-only k3s deployment skeleton. D601 reaches the master edge through frp reverse tunnel." - }, - "prod": { - "name": "prod", - "enabled": false, - "namespace": "hwlab-prod", - "notes": "Reserved placeholder only. PROD deployment and acceptance are explicitly out of scope." - } - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ENVIRONMENT": "dev", - "HWLAB_PUBLIC_ENDPOINT": "http://74.48.78.17:16667", - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9", - "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN": "unidesk-backend,provider-gateway,microservice-proxy", - "HWLAB_CLOUD_DB_URL": "secretRef:hwlab-cloud-api-dev-db/database-url", - "HWLAB_CLOUD_DB_SSL_MODE": "disable", - "HWLAB_CLOUD_DB_CONTRACT": "dev-redacted-presence-only", - "HWLAB_CLOUD_RUNTIME_ADAPTER": "postgres", - "HWLAB_CLOUD_RUNTIME_DURABLE": "true", - "HWLAB_M3_IO_CONTROL_ENABLED": "true", - "HWLAB_M3_GATEWAY_SIMU_1_URL": "http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101", - "HWLAB_M3_GATEWAY_SIMU_2_URL": "http://hwlab-gateway-simu-2.hwlab-dev.svc.cluster.local:7101", - "HWLAB_M3_PATCH_PANEL_URL": "http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301", - "HWLAB_CODE_AGENT_PROVIDER": "codex-stdio", - "HWLAB_CODE_AGENT_MODEL": "gpt-5.5", - "HWLAB_CODE_AGENT_TIMEOUT_MS": "150000", - "HWLAB_CODE_AGENT_OPENAI_BASE_URL": "http://172.26.26.227:17680/v1/responses", - "HWLAB_CODE_AGENT_HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED": "1", - "HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR": "repo-owned", - "HWLAB_CODE_AGENT_WORKSPACE": "/workspace/hwlab", - "HWLAB_CODE_AGENT_CODEX_WORKSPACE": "/workspace/hwlab", - "HWLAB_CODE_AGENT_CODEX_SANDBOX": "workspace-write", - "HWLAB_CODE_AGENT_CODEX_COMMAND": "/app/node_modules/.bin/codex", - "CODEX_HOME": "/codex-home", - "OPENAI_API_KEY": "secretRef:hwlab-code-agent-provider/openai-api-key", - "HWLAB_CODE_AGENT_SKILLS_DIRS": "/app/skills:/root/.agents/skills:/home/ubuntu/.agents/skills" - } - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ENVIRONMENT": "dev", - "HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS": "180000", - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - } - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_AGENT_SESSION_MODE": "control-plane", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_AGENT_SESSION_MODE": "session-scoped", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_GATEWAY_MODE": "hardware-boundary", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 2, - "instanceNames": [ - "hwlab-gateway-simu-1", - "hwlab-gateway-simu-2" - ], - "identityStrategy": "indexed-workloads", - "env": { - "HWLAB_GATEWAY_MODE": "simulator", - "HWLAB_GATEWAY_ID": "gwsimu_1,gwsimu_2", - "HWLAB_BOX_IDS": "boxsimu_1,boxsimu_2", - "HWLAB_BOX_RESOURCES": "res_boxsimu_1,res_boxsimu_2", - "HWLAB_BOX_URLS": "http://hwlab-box-simu-1.hwlab-dev.svc.cluster.local:7201,http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201" - } - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 2, - "instanceNames": [ - "hwlab-box-simu-1", - "hwlab-box-simu-2" - ], - "identityStrategy": "indexed-workloads", - "env": { - "HWLAB_BOX_ID": "boxsimu_1,boxsimu_2", - "HWLAB_BOX_RESOURCE_ID": "res_boxsimu_1,res_boxsimu_2", - "HWLAB_GATEWAY_SESSION_ID": "gws_gwsimu_1,gws_gwsimu_2", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "m3Route": { - "fromResourceId": "res_boxsimu_1", - "fromPort": "DO1", - "patchPanelServiceId": "hwlab-patch-panel", - "toResourceId": "res_boxsimu_2", - "toPort": "DI1" - }, - "env": { - "HWLAB_PATCH_PANEL_MODE": "dev-skeleton", - "HWLAB_PATCH_PANEL_ENDPOINT_MAP": "{\"res_boxsimu_2\":\"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201\"}", - "HWLAB_PATCH_PANEL_WIRING_CONFIG": "{\"wiringConfigId\":\"wir_m3_do1_di1\",\"projectId\":\"prj_m3_hardware_loop\",\"gatewaySessionId\":\"gws_gwsimu_1\",\"name\":\"M3 simulated DO1 to DI1 patch wiring\",\"status\":\"active\",\"connections\":[{\"from\":{\"resourceId\":\"res_boxsimu_1\",\"port\":\"DO1\"},\"to\":{\"resourceId\":\"res_boxsimu_2\",\"port\":\"DI1\"},\"mode\":\"exclusive\"}],\"constraints\":{\"propagation\":\"patch-panel-only\",\"localLoopbackSubstituteAllowed\":false},\"createdAt\":\"2026-05-21T00:00:00.000Z\",\"updatedAt\":\"2026-05-21T00:00:00.000Z\"}", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ROUTER_MODE": "edge-reachability", - "HWLAB_EDGE_PROXY_SERVICE": "hwlab-edge-proxy" - } - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_TUNNEL_MODE": "frp-client", - "HWLAB_FRP_SERVER_ADDR": "74.48.78.17", - "HWLAB_FRP_PUBLIC_PORT": "16667", - "HWLAB_FRP_WEB_PUBLIC_PORT": "16666" - } - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_EDGE_LISTEN": "0.0.0.0:6667", - "HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_EDGE_PROXY_TIMEOUT_MS": "180000" - } - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_CLI_ENDPOINT": "http://74.48.78.17:16667" - } - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_SKILLS_COMMIT_ID": "48dfbf9" - } - } - ], - "createdAt": "2026-05-21T00:00:00Z" - } - }, - "after": { - "path": "deploy/deploy.json", - "hash": "sha256:b23766b798d1ecacb87d30b94ee8ffe77e2ccbe7772ce27e5c94ee152d06f543", - "commitId": "48dfbf9", - "namespace": "hwlab-dev", - "environment": "dev", - "endpoint": "http://74.48.78.17:16667", - "manifest": { - "manifestVersion": "v1", - "environment": "dev", - "commitId": "48dfbf9", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "health": { - "method": "GET", - "path": "/health/live", - "responseFormat": "json" - }, - "publicEndpoints": { - "frontend": { - "url": "http://74.48.78.17:16666", - "protocol": "http", - "host": "74.48.78.17", - "port": 16666, - "serviceId": "hwlab-cloud-web", - "frpProxy": "hwlab-dev-cloud-web" - }, - "api": { - "url": "http://74.48.78.17:16667", - "protocol": "http", - "host": "74.48.78.17", - "port": 16667, - "serviceId": "hwlab-edge-proxy", - "frpProxy": "hwlab-dev-edge-proxy" - } - }, - "frp": { - "server": { - "address": "74.48.78.17", - "bindPort": 7000 - }, - "proxies": [ - { - "name": "hwlab-dev-cloud-web", - "endpointId": "frontend", - "type": "tcp", - "localServiceId": "hwlab-cloud-web", - "localHost": "hwlab-cloud-web.hwlab-dev.svc.cluster.local", - "localPort": 8080, - "remotePort": 16666 - }, - { - "name": "hwlab-dev-edge-proxy", - "endpointId": "api", - "type": "tcp", - "localServiceId": "hwlab-edge-proxy", - "localHost": "hwlab-edge-proxy.hwlab-dev.svc.cluster.local", - "localPort": 6667, - "remotePort": 16667 - }, - { - "name": "hwlab-dev-tunnel-health", - "type": "tcp", - "localServiceId": "hwlab-tunnel-client", - "localHost": "hwlab-tunnel-client.hwlab-dev.svc.cluster.local", - "localPort": 7402, - "remotePort": 7402 - } - ] - }, - "k3s": { - "serviceMappings": [ - { - "serviceId": "hwlab-cloud-api", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "port": 6667, - "targetPort": "http" - }, - { - "serviceId": "hwlab-cloud-web", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "port": 8080, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "port": 7001, - "targetPort": "http" - }, - { - "serviceId": "hwlab-cli", - "name": "hwlab-cli", - "namespace": "hwlab-dev", - "port": 7501, - "targetPort": "http" - }, - { - "serviceId": "hwlab-agent-skills", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "port": 7430, - "targetPort": "http" - }, - { - "serviceId": "hwlab-agent-mgr", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "port": 7410, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway-simu", - "name": "hwlab-gateway-simu-1", - "namespace": "hwlab-dev", - "port": 7101, - "targetPort": "http" - }, - { - "serviceId": "hwlab-box-simu", - "name": "hwlab-box-simu-1", - "namespace": "hwlab-dev", - "port": 7201, - "targetPort": "http" - }, - { - "serviceId": "hwlab-gateway-simu", - "name": "hwlab-gateway-simu-2", - "namespace": "hwlab-dev", - "port": 7101, - "targetPort": "http" - }, - { - "serviceId": "hwlab-box-simu", - "name": "hwlab-box-simu-2", - "namespace": "hwlab-dev", - "port": 7201, - "targetPort": "http" - }, - { - "serviceId": "hwlab-patch-panel", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "port": 7301, - "targetPort": "http" - }, - { - "serviceId": "hwlab-router", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "port": 7401, - "targetPort": "http" - }, - { - "serviceId": "hwlab-tunnel-client", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "port": 7402, - "targetPort": "http" - }, - { - "serviceId": "hwlab-edge-proxy", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "port": 6667, - "targetPort": "http" - } - ] - }, - "profiles": { - "dev": { - "name": "dev", - "enabled": true, - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "notes": "DEV-only k3s deployment skeleton. D601 reaches the master edge through frp reverse tunnel." - }, - "prod": { - "name": "prod", - "enabled": false, - "namespace": "hwlab-prod", - "notes": "Reserved placeholder only. PROD deployment and acceptance are explicitly out of scope." - } - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ENVIRONMENT": "dev", - "HWLAB_PUBLIC_ENDPOINT": "http://74.48.78.17:16667", - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9", - "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN": "unidesk-backend,provider-gateway,microservice-proxy", - "HWLAB_CLOUD_DB_URL": "secretRef:hwlab-cloud-api-dev-db/database-url", - "HWLAB_CLOUD_DB_SSL_MODE": "disable", - "HWLAB_CLOUD_DB_CONTRACT": "dev-redacted-presence-only", - "HWLAB_CLOUD_RUNTIME_ADAPTER": "postgres", - "HWLAB_CLOUD_RUNTIME_DURABLE": "true", - "HWLAB_M3_IO_CONTROL_ENABLED": "true", - "HWLAB_M3_GATEWAY_SIMU_1_URL": "http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101", - "HWLAB_M3_GATEWAY_SIMU_2_URL": "http://hwlab-gateway-simu-2.hwlab-dev.svc.cluster.local:7101", - "HWLAB_M3_PATCH_PANEL_URL": "http://hwlab-patch-panel.hwlab-dev.svc.cluster.local:7301", - "HWLAB_CODE_AGENT_PROVIDER": "codex-stdio", - "HWLAB_CODE_AGENT_MODEL": "gpt-5.5", - "HWLAB_CODE_AGENT_TIMEOUT_MS": "150000", - "HWLAB_CODE_AGENT_OPENAI_BASE_URL": "http://172.26.26.227:17680/v1/responses", - "HWLAB_CODE_AGENT_HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED": "1", - "HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR": "repo-owned", - "HWLAB_CODE_AGENT_WORKSPACE": "/workspace/hwlab", - "HWLAB_CODE_AGENT_CODEX_WORKSPACE": "/workspace/hwlab", - "HWLAB_CODE_AGENT_CODEX_SANDBOX": "workspace-write", - "HWLAB_CODE_AGENT_CODEX_COMMAND": "/app/node_modules/.bin/codex", - "CODEX_HOME": "/codex-home", - "OPENAI_API_KEY": "secretRef:hwlab-code-agent-provider/openai-api-key", - "HWLAB_CODE_AGENT_SKILLS_DIRS": "/app/skills:/root/.agents/skills:/home/ubuntu/.agents/skills" - } - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ENVIRONMENT": "dev", - "HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_CLOUD_WEB_PROXY_TIMEOUT_MS": "180000", - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - } - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_AGENT_SESSION_MODE": "control-plane", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_AGENT_SESSION_MODE": "session-scoped", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_GATEWAY_MODE": "hardware-boundary", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 2, - "instanceNames": [ - "hwlab-gateway-simu-1", - "hwlab-gateway-simu-2" - ], - "identityStrategy": "indexed-workloads", - "env": { - "HWLAB_GATEWAY_MODE": "simulator", - "HWLAB_GATEWAY_ID": "gwsimu_1,gwsimu_2", - "HWLAB_BOX_IDS": "boxsimu_1,boxsimu_2", - "HWLAB_BOX_RESOURCES": "res_boxsimu_1,res_boxsimu_2", - "HWLAB_BOX_URLS": "http://hwlab-box-simu-1.hwlab-dev.svc.cluster.local:7201,http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201" - } - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 2, - "instanceNames": [ - "hwlab-box-simu-1", - "hwlab-box-simu-2" - ], - "identityStrategy": "indexed-workloads", - "env": { - "HWLAB_BOX_ID": "boxsimu_1,boxsimu_2", - "HWLAB_BOX_RESOURCE_ID": "res_boxsimu_1,res_boxsimu_2", - "HWLAB_GATEWAY_SESSION_ID": "gws_gwsimu_1,gws_gwsimu_2", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "m3Route": { - "fromResourceId": "res_boxsimu_1", - "fromPort": "DO1", - "patchPanelServiceId": "hwlab-patch-panel", - "toResourceId": "res_boxsimu_2", - "toPort": "DI1" - }, - "env": { - "HWLAB_PATCH_PANEL_MODE": "dev-skeleton", - "HWLAB_PATCH_PANEL_ENDPOINT_MAP": "{\"res_boxsimu_2\":\"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201\"}", - "HWLAB_PATCH_PANEL_WIRING_CONFIG": "{\"wiringConfigId\":\"wir_m3_do1_di1\",\"projectId\":\"prj_m3_hardware_loop\",\"gatewaySessionId\":\"gws_gwsimu_1\",\"name\":\"M3 simulated DO1 to DI1 patch wiring\",\"status\":\"active\",\"connections\":[{\"from\":{\"resourceId\":\"res_boxsimu_1\",\"port\":\"DO1\"},\"to\":{\"resourceId\":\"res_boxsimu_2\",\"port\":\"DI1\"},\"mode\":\"exclusive\"}],\"constraints\":{\"propagation\":\"patch-panel-only\",\"localLoopbackSubstituteAllowed\":false},\"createdAt\":\"2026-05-21T00:00:00.000Z\",\"updatedAt\":\"2026-05-21T00:00:00.000Z\"}", - "HWLAB_ENVIRONMENT": "dev" - } - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_ROUTER_MODE": "edge-reachability", - "HWLAB_EDGE_PROXY_SERVICE": "hwlab-edge-proxy" - } - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_TUNNEL_MODE": "frp-client", - "HWLAB_FRP_SERVER_ADDR": "74.48.78.17", - "HWLAB_FRP_PUBLIC_PORT": "16667", - "HWLAB_FRP_WEB_PUBLIC_PORT": "16666" - } - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_EDGE_LISTEN": "0.0.0.0:6667", - "HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "HWLAB_EDGE_PROXY_TIMEOUT_MS": "180000" - } - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 0, - "env": { - "HWLAB_CLI_ENDPOINT": "http://74.48.78.17:16667" - } - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_SKILLS_COMMIT_ID": "48dfbf9" - } - } - ], - "createdAt": "2026-05-21T00:00:00Z" - } - } - }, - "lock": { - "acquired": true, - "current": { - "lockBackend": "Lease", - "lockName": "hwlab-dev-cd-lock", - "resourceVersion": "1519083", - "holderIdentity": "DC-DCSN-P0-2026-003-D601/5332b2f8-5619-490e-8100-e79fa427f6f9", - "promotionCommit": "48dfbf9", - "deployJsonHash": "sha256:b23766b798d1ecacb87d30b94ee8ffe77e2ccbe7772ce27e5c94ee152d06f543", - "ownerTaskId": "DC-DCSN-P0-2026-003-D601", - "transactionId": "5332b2f8-5619-490e-8100-e79fa427f6f9", - "phase": "verifying", - "startedAt": "2026-05-24T00:07:29.483000Z", - "updatedAt": "2026-05-24T00:18:29.335000Z", - "ttlSeconds": 3600, - "liveBefore": { - "status": "pass", - "expectedCommit": null, - "endpoints": [ - { - "id": "cloud-web-16666", - "url": "http://74.48.78.17:16666/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.593Z", - "finishedAt": "2026-05-24T00:07:30.157Z", - "expectedServiceId": "hwlab-cloud-web", - "expectedCommit": null, - "serviceId": "hwlab-cloud-web", - "environment": "dev", - "applicationStatus": "ok", - "ready": null, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageTag": "3df89fe", - "observedAt": null, - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - }, - { - "id": "cloud-api-16667", - "url": "http://74.48.78.17:16667/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.594Z", - "finishedAt": "2026-05-24T00:07:31.417Z", - "expectedServiceId": "hwlab-cloud-api", - "expectedCommit": null, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "applicationStatus": "ok", - "ready": true, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageTag": "3df89fe", - "observedAt": "2026-05-24T00:07:30.656Z", - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - } - ], - "summary": { - "checked": 2, - "reachable": 2, - "identityMatches": 2, - "commitMatches": null, - "ports": [ - 16666, - 16667 - ] - } - }, - "targetNamespace": "hwlab-dev", - "targetRef": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "releasedAt": null, - "releaseStatus": "running", - "staleLockBrokenAt": null, - "staleLockPrevious": null - }, - "staleBreak": null - }, - "steps": [ - { - "id": "desired-state-check-before-publish", - "phase": "publishing", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/deploy-desired-state-plan.mjs --check --pretty", - "code": 0, - "startedAt": "2026-05-24T00:07:31.483Z", - "finishedAt": "2026-05-24T00:07:31.522Z", - "stdoutJson": { - "kind": "hwlab-deploy-desired-state-plan", - "mode": "read-only-plan", - "status": "pass", - "source": { - "deploy": "deploy/deploy.json", - "artifactCatalog": "deploy/artifact-catalog.dev.json", - "workloads": "deploy/k8s/base/workloads.yaml", - "optionalReport": "reports/dev-gate/dev-artifacts.json" - }, - "safety": { - "readOnly": true, - "sourceOrDryRunSupportOnly": true, - "kubectlApply": false, - "registryPull": false, - "registryPush": false, - "imageBuild": false, - "serviceRestart": false, - "prod": false, - "devLiveClaim": false - }, - "checkSemantics": "--check exits non-zero for missing/invalid desired-state sources, internal commit/image mirror drift, invalid target tags, partial target drift, or any mismatch with an explicit --target-ref, --target-tag, or --promotion-commit. When the checked target ref is the current checked-out merge commit, the comparable current-main target is its accepted first parent because a commit cannot embed its own content hash. Without --check, a uniform older desired-state under --target-ref/--target-tag is a read-only promotion plan.", - "summary": { - "desiredCommitId": "48dfbf9", - "desiredImageTag": "48dfbf9", - "artifactState": "contract-skeleton", - "ciPublished": false, - "registryVerified": false, - "services": 13, - "workloadContainers": 13, - "presentMirrorCount": 14, - "diagnostics": 0, - "blockers": 0, - "targetConvergence": "not_requested" - }, - "target": null, - "promotionBoundary": { - "writes": [], - "authoritativeDesiredState": [ - "deploy/deploy.json", - "deploy/artifact-catalog.dev.json", - "deploy/k8s/base/workloads.yaml" - ], - "nonAuthoritativeEvidence": [ - "reports/dev-gate/dev-artifacts.json" - ], - "note": "This planner reviews source desired-state only. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence." - }, - "cloudApiDb": { - "contractVersion": "v1", - "environment": "dev", - "status": "pass", - "ready": true, - "secretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "sslMode": { - "env": "HWLAB_CLOUD_DB_SSL_MODE", - "value": "disable", - "workloadValue": "disable", - "expected": "disable", - "deployMatches": true, - "workloadMatches": true, - "secret": false - }, - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false - }, - "codeAgentProvider": { - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "status": "pass", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "runtime-env" - } - ], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "deployPresent": true, - "workloadPresent": true, - "deployMatchesDevProxy": true, - "workloadMatchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "missingDeployEnv": [], - "missingWorkloadEnv": [], - "deployMismatches": [], - "workloadMismatches": [], - "missingSecretRefs": [], - "missingEgressContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "providerConnected": false, - "fixtureEvidence": false - }, - "reportHints": { - "path": "reports/dev-gate/dev-artifacts.json", - "present": true, - "authoritative": false, - "note": "report snapshots are contextual evidence only; deploy desired-state remains authoritative in deploy/, not reports/", - "commitId": "606156f", - "matchesDesiredState": false, - "artifactPublish": { - "status": "published", - "sourceCommitId": "606156f604e507f8725f6ae9040cea8204375a86", - "sourceMatchesDesiredState": false, - "publishedCount": 13, - "serviceCount": 13, - "registryVerified": null, - "registryCapabilitiesClassification": "degraded" - } - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-cloud-web", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-agent-mgr", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-agent-worker", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Job", - "name": "hwlab-agent-worker-template", - "namespace": "hwlab-dev", - "container": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-gateway", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "container": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-gateway-simu", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "StatefulSet", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-box-simu", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "StatefulSet", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-patch-panel", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-router", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-tunnel-client", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-edge-proxy", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-cli", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Job", - "name": "hwlab-cli-template", - "namespace": "hwlab-dev", - "container": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - }, - { - "serviceId": "hwlab-agent-skills", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_SKILLS_COMMIT_ID": { - "name": "HWLAB_SKILLS_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_SKILLS_COMMIT_ID": { - "name": "HWLAB_SKILLS_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": null - } - ], - "diagnostics": [] - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": null - }, - { - "id": "artifact-publish", - "phase": "publishing", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/dev-artifact-publish.mjs --publish", - "code": 0, - "startedAt": "2026-05-24T00:07:31.523Z", - "finishedAt": "2026-05-24T00:18:16.117Z", - "stdoutJson": { - "status": "published", - "mode": "publish", - "reportPath": "reports/dev-gate/dev-artifacts.json", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryPrefix": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-24T00:07:33.358Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 10 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c5f796204e9609a191230d9faa3bd7740ad50c4977a7eee7ef545665ccf4097c" - }, - { - "pod": "hwlab-agent-skills-69485c4dc4-mpgdb", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:4b006f52cf27438956f5269d39483d2ebcf8966b30cf19cd378616b423f93e1f" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:fff35a6eec4a70375122f87636759de62039465449053c584d9bc9572b7d1b0d" - }, - { - "pod": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:cd46ef24e97f430d7761b2cb7100990eda9f79ddf35f8ffc58fba81cd75e9acf" - }, - { - "pod": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:a95189dd0cabf9197b7a15dc4cc2995eccf27898e2b0e26c7fd9cbf952fe8119" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-patch-panel-55b9c584d7-7rzqb", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:36f569ce71dd9da2c8432ded7f00be9b9e51d0ec5409eeee871dfc3efbd9dbc9" - }, - { - "pod": "hwlab-router-76fd584bdb-t26cs", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:258cc4231a7e0c4de47cce9a8be37b60c9a8a942d09d91b695b006f0dfc396d9" - }, - { - "pod": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:e44e0a0f5b3e88014c85414feeecc892a34e73e2137160cc905d526aede6cf02" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "baseImagePreflight": { - "status": "ready", - "imageSource": "local-docker-node:20-*", - "requestedImage": null, - "localTag": "node:20-bookworm-slim", - "imageId": "sha256:9da6b4e352d0d5c94963eba1832408f5b7b08839cd8be9b6610c05de5118c704", - "repoDigests": [ - "node@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0" - ], - "candidates": [ - { - "source": "local-docker-node:20-*", - "approval": { - "approved": true, - "reason": "local Node 20 builder base image" - }, - "localTag": "node:20-bookworm-slim", - "imageId": "9da6b4e352d0", - "size": "200MB", - "createdSince": "4 weeks ago" - } - ], - "rejectedCandidates": [], - "publishUsable": true, - "blockers": [], - "nextSteps": [ - "Use the reported localTag as the DEV builder base image for artifact publish.", - "Keep publish DEV-only and record the produced HWLAB service image tags and digests separately." - ], - "recommendation": { - "source": "Preloaded D601 local Docker cache or D601-local registry tag.", - "envVar": "HWLAB_DEV_BASE_IMAGE", - "preferredImageTag": "node:20-bookworm-slim", - "preferredHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "recommendedEnvValue": "node:20-bookworm-slim" - }, - "provision": { - "required": false, - "exactBlocker": null, - "expectedImageTag": "node:20-bookworm-slim", - "recommendedEnv": { - "name": "HWLAB_DEV_BASE_IMAGE", - "value": "node:20-bookworm-slim", - "assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim" - }, - "recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "nextHumanCommand": null, - "commands": { - "d601OnlinePreload": [ - "docker pull node:20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "d601HwlabTagProvision": [ - "docker pull node:20-bookworm-slim", - "docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim", - "HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "offlineSourceExport": [ - "docker pull node:20-bookworm-slim", - "docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar" - ], - "offlineD601Import": [ - "docker load -i hwlab-node20-bookworm-slim.tar", - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs" - ], - "artifactPublishDryRun": [ - "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/dev-artifact-publish.mjs --preflight --no-report" - ] - } - }, - "blockedReport": null, - "containerEngine": { - "command": "docker", - "available": true, - "version": "Docker version 26.1.5+dfsg1, build a72d7cd", - "errorCode": null, - "stderr": "" - } - }, - "publishPlan": { - "version": "v2", - "mode": "publish", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "imageTag": "48dfbf9", - "registryTarget": "127.0.0.1:5000/hwlab", - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-24T00:07:33.358Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 10 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "context": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c5f796204e9609a191230d9faa3bd7740ad50c4977a7eee7ef545665ccf4097c" - }, - { - "pod": "hwlab-agent-skills-69485c4dc4-mpgdb", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:4b006f52cf27438956f5269d39483d2ebcf8966b30cf19cd378616b423f93e1f" - }, - { - "pod": "hwlab-box-simu-0", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-box-simu-1", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:e8aa955c810724ca9990fede69c54866dd7abe789ccee9da83ded75a75a99850" - }, - { - "pod": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:fff35a6eec4a70375122f87636759de62039465449053c584d9bc9572b7d1b0d" - }, - { - "pod": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:cd46ef24e97f430d7761b2cb7100990eda9f79ddf35f8ffc58fba81cd75e9acf" - }, - { - "pod": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:a95189dd0cabf9197b7a15dc4cc2995eccf27898e2b0e26c7fd9cbf952fe8119" - }, - { - "pod": "hwlab-frpc-59f57d479c-tgjhl", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-0", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-gateway-simu-1", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:1a5145313c95c6dfd1e8fca9f10f89cb81178dd545cd43fa158f8c9ac40ca25f" - }, - { - "pod": "hwlab-patch-panel-55b9c584d7-7rzqb", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:36f569ce71dd9da2c8432ded7f00be9b9e51d0ec5409eeee871dfc3efbd9dbc9" - }, - { - "pod": "hwlab-router-76fd584bdb-t26cs", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:258cc4231a7e0c4de47cce9a8be37b60c9a8a942d09d91b695b006f0dfc396d9" - }, - { - "pod": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:3df89fe", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:e44e0a0f5b3e88014c85414feeecc892a34e73e2137160cc905d526aede6cf02" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "digestPlaceholder": "not_published", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cloud-web", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-mgr", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-worker", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-worker@sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway@sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway-simu", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-box-simu", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-patch-panel", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-router", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-router@sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-tunnel-client", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-edge-proxy", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cli", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-cli@sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-skills", - "enabled": true, - "required": true, - "artifactScope": "required", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "registryTarget": "127.0.0.1:5000/hwlab/hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "buildSource": "pikasTech/HWLAB@48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "repositoryDigest": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "digestStatus": "real", - "status": "published", - "notPublishedReason": null - } - ] - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cloud-web", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "distFreshness": { - "status": "pass", - "distPath": "web/hwlab-cloud-web/dist", - "buildCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "freshnessCommand": "node web/hwlab-cloud-web/scripts/build.mjs", - "files": [ - { - "kind": "runtime", - "path": "index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/index.html", - "status": "match" - }, - { - "kind": "runtime", - "path": "styles.css", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/styles.css", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/styles.css", - "status": "match" - }, - { - "kind": "runtime", - "path": "auth.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/auth.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/auth.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "app.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/app.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/app.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-facts.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-facts.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-facts.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "code-agent-m3-evidence.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/code-agent-m3-evidence.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/code-agent-m3-evidence.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "live-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/live-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/live-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "gate-summary.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/gate-summary.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/gate-summary.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "runtime.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/runtime.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/runtime.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "wiring-status.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/wiring-status.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/wiring-status.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "workbench-hardware-panel.mjs", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/workbench-hardware-panel.mjs", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/workbench-hardware-panel.mjs", - "status": "match" - }, - { - "kind": "runtime", - "path": "help.md", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/help.md", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/help.md", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/marked.esm.js", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/third_party/marked/marked.esm.js", - "status": "match" - }, - { - "kind": "runtime", - "path": "third_party/marked/LICENSE", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/third_party/marked/LICENSE", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/third_party/marked/LICENSE", - "status": "match" - }, - { - "kind": "alias", - "path": "gate/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "diagnostics/gate/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/diagnostics/gate/index.html", - "status": "match" - }, - { - "kind": "alias", - "path": "help/index.html", - "sourcePath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/index.html", - "distPath": "/home/ubuntu/hwlab-d601-dev-cd-48dfbf9/web/hwlab-cloud-web/dist/help/index.html", - "status": "match" - } - ], - "mismatches": [] - }, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-mgr", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-worker", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway-simu", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-box-simu", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-patch-panel", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-router", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-tunnel-client", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-edge-proxy", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cli", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-skills", - "status": "published", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "notPublishedReason": null - } - ], - "blockers": [] - }, - "stdoutTail": null, - "stderrTail": "\"event\":\"publish_start\",\"serviceId\":\"hwlab-tunnel-client\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9\"}\n{\"event\":\"publish_done\",\"serviceId\":\"hwlab-tunnel-client\",\"status\":\"published\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9\",\"digest\":\"sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f\"}\n{\"event\":\"build_start\",\"serviceId\":\"hwlab-edge-proxy\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9\"}\n{\"event\":\"build_done\",\"serviceId\":\"hwlab-edge-proxy\",\"status\":\"built\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9\"}\n{\"event\":\"publish_start\",\"serviceId\":\"hwlab-edge-proxy\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9\"}\n{\"event\":\"publish_done\",\"serviceId\":\"hwlab-edge-proxy\",\"status\":\"published\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9\",\"digest\":\"sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05\"}\n{\"event\":\"build_start\",\"serviceId\":\"hwlab-cli\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9\"}\n{\"event\":\"build_done\",\"serviceId\":\"hwlab-cli\",\"status\":\"built\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9\"}\n{\"event\":\"publish_start\",\"serviceId\":\"hwlab-cli\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9\"}\n{\"event\":\"publish_done\",\"serviceId\":\"hwlab-cli\",\"status\":\"published\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9\",\"digest\":\"sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b\"}\n{\"event\":\"build_start\",\"serviceId\":\"hwlab-agent-skills\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9\"}\n{\"event\":\"build_done\",\"serviceId\":\"hwlab-agent-skills\",\"status\":\"built\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9\"}\n{\"event\":\"publish_start\",\"serviceId\":\"hwlab-agent-skills\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9\"}\n{\"event\":\"publish_done\",\"serviceId\":\"hwlab-agent-skills\",\"status\":\"published\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9\",\"digest\":\"sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d\"}", - "reportPath": "reports/dev-gate/dev-artifacts.json" - }, - { - "id": "artifact-catalog-refresh", - "phase": "publishing", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/refresh-artifact-catalog.mjs --target-ref 48dfbf9e7f85f2dd8e17505582e811bdacfd60d5 --publish-report reports/dev-gate/dev-artifacts.json", - "code": 0, - "startedAt": "2026-05-24T00:18:16.118Z", - "finishedAt": "2026-05-24T00:18:16.162Z", - "stdoutJson": { - "status": "published", - "targetRef": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "artifactCommitId": "48dfbf9", - "wrote": [ - "deploy/deploy.json", - "deploy/artifact-catalog.dev.json", - "deploy/k8s/base/workloads.yaml" - ], - "ciPublished": true, - "registryVerified": true, - "publishedCount": 13, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "notPublishedCount": 0, - "digestPolicy": "catalog digests copied only for required services from a published DEV artifact report; disabled services stay not_published", - "services": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "buildCreatedAt": "2026-05-24T00:07:33.363Z", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "publishState": "published", - "artifactRequired": true, - "notPublishedReason": null - } - ] - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": null - }, - { - "id": "artifact-catalog-validate", - "phase": "publishing", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/validate-artifact-catalog.mjs", - "code": 0, - "startedAt": "2026-05-24T00:18:16.163Z", - "finishedAt": "2026-05-24T00:18:16.194Z", - "stdoutJson": null, - "stdoutTail": "validated 13 DEV artifact catalog services at 48dfbf9 (published)", - "stderrTail": "", - "reportPath": null - }, - { - "id": "desired-state-check-after-refresh", - "phase": "publishing", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/deploy-desired-state-plan.mjs --promotion-commit 48dfbf9e7f85f2dd8e17505582e811bdacfd60d5 --check --pretty", - "code": 0, - "startedAt": "2026-05-24T00:18:16.194Z", - "finishedAt": "2026-05-24T00:18:16.233Z", - "stdoutJson": { - "kind": "hwlab-deploy-desired-state-plan", - "mode": "read-only-plan", - "status": "pass", - "source": { - "deploy": "deploy/deploy.json", - "artifactCatalog": "deploy/artifact-catalog.dev.json", - "workloads": "deploy/k8s/base/workloads.yaml", - "optionalReport": "reports/dev-gate/dev-artifacts.json" - }, - "safety": { - "readOnly": true, - "sourceOrDryRunSupportOnly": true, - "kubectlApply": false, - "registryPull": false, - "registryPush": false, - "imageBuild": false, - "serviceRestart": false, - "prod": false, - "devLiveClaim": false - }, - "checkSemantics": "--check exits non-zero for missing/invalid desired-state sources, internal commit/image mirror drift, invalid target tags, partial target drift, or any mismatch with an explicit --target-ref, --target-tag, or --promotion-commit. When the checked target ref is the current checked-out merge commit, the comparable current-main target is its accepted first parent because a commit cannot embed its own content hash. Without --check, a uniform older desired-state under --target-ref/--target-tag is a read-only promotion plan.", - "summary": { - "desiredCommitId": "48dfbf9", - "desiredImageTag": "48dfbf9", - "artifactState": "published", - "ciPublished": true, - "registryVerified": true, - "services": 13, - "workloadContainers": 13, - "presentMirrorCount": 14, - "diagnostics": 0, - "blockers": 0, - "targetConvergence": "already_promoted" - }, - "target": { - "targetRef": null, - "promotionCommit": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "commitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "shortCommitId": "48dfbf9", - "tag": "48dfbf9", - "tagSource": "promotion-commit-short", - "acceptedCurrentMain": null, - "comparisonCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "comparisonTag": "48dfbf9", - "requireConvergence": true, - "convergenceRequirement": "promotion-commit", - "convergence": { - "state": "already_promoted", - "comparableFields": 81, - "matchingTargetFields": 81, - "pendingTargetFields": 0, - "matchingPaths": [ - "deploy/deploy.json.commitId", - "deploy/artifact-catalog.dev.json.commitId", - "deploy/deploy.json.services.hwlab-cloud-api.image", - "deploy/deploy.json.services.hwlab-cloud-api.env.HWLAB_COMMIT_ID", - "deploy/deploy.json.services.hwlab-cloud-api.env.HWLAB_IMAGE", - "deploy/deploy.json.services.hwlab-cloud-api.env.HWLAB_IMAGE_TAG", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-api.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-api.image", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-api.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-api.containers[0].image", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-api.containers[0].env.HWLAB_COMMIT_ID", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-api.containers[0].env.HWLAB_IMAGE", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-api.containers[0].env.HWLAB_IMAGE_TAG", - "deploy/deploy.json.services.hwlab-cloud-web.image", - "deploy/deploy.json.services.hwlab-cloud-web.env.HWLAB_COMMIT_ID", - "deploy/deploy.json.services.hwlab-cloud-web.env.HWLAB_IMAGE", - "deploy/deploy.json.services.hwlab-cloud-web.env.HWLAB_IMAGE_TAG", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-web.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-web.image", - "deploy/artifact-catalog.dev.json.services.hwlab-cloud-web.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-web.containers[0].image", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-web.containers[0].env.HWLAB_COMMIT_ID", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-web.containers[0].env.HWLAB_IMAGE", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-cloud-web.containers[0].env.HWLAB_IMAGE_TAG", - "deploy/deploy.json.services.hwlab-agent-mgr.image", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-mgr.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-mgr.image", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-mgr.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-agent-mgr.containers[0].image", - "deploy/deploy.json.services.hwlab-agent-worker.image", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-worker.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-worker.image", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-worker.imageTag", - "deploy/k8s/base/workloads.yaml.Job.hwlab-agent-worker-template.containers[0].image", - "deploy/deploy.json.services.hwlab-gateway.image", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway.image", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-gateway.containers[0].image", - "deploy/deploy.json.services.hwlab-gateway-simu.image", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway-simu.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway-simu.image", - "deploy/artifact-catalog.dev.json.services.hwlab-gateway-simu.imageTag", - "deploy/k8s/base/workloads.yaml.StatefulSet.hwlab-gateway-simu.containers[0].image", - "deploy/deploy.json.services.hwlab-box-simu.image", - "deploy/artifact-catalog.dev.json.services.hwlab-box-simu.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-box-simu.image", - "deploy/artifact-catalog.dev.json.services.hwlab-box-simu.imageTag", - "deploy/k8s/base/workloads.yaml.StatefulSet.hwlab-box-simu.containers[0].image", - "deploy/deploy.json.services.hwlab-patch-panel.image", - "deploy/artifact-catalog.dev.json.services.hwlab-patch-panel.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-patch-panel.image", - "deploy/artifact-catalog.dev.json.services.hwlab-patch-panel.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-patch-panel.containers[0].image", - "deploy/deploy.json.services.hwlab-router.image", - "deploy/artifact-catalog.dev.json.services.hwlab-router.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-router.image", - "deploy/artifact-catalog.dev.json.services.hwlab-router.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-router.containers[0].image", - "deploy/deploy.json.services.hwlab-tunnel-client.image", - "deploy/artifact-catalog.dev.json.services.hwlab-tunnel-client.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-tunnel-client.image", - "deploy/artifact-catalog.dev.json.services.hwlab-tunnel-client.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-tunnel-client.containers[0].image", - "deploy/deploy.json.services.hwlab-edge-proxy.image", - "deploy/artifact-catalog.dev.json.services.hwlab-edge-proxy.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-edge-proxy.image", - "deploy/artifact-catalog.dev.json.services.hwlab-edge-proxy.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-edge-proxy.containers[0].image", - "deploy/deploy.json.services.hwlab-cli.image", - "deploy/artifact-catalog.dev.json.services.hwlab-cli.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-cli.image", - "deploy/artifact-catalog.dev.json.services.hwlab-cli.imageTag", - "deploy/k8s/base/workloads.yaml.Job.hwlab-cli-template.containers[0].image", - "deploy/deploy.json.services.hwlab-agent-skills.image", - "deploy/deploy.json.services.hwlab-agent-skills.env.HWLAB_SKILLS_COMMIT_ID", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-skills.commitId", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-skills.image", - "deploy/artifact-catalog.dev.json.services.hwlab-agent-skills.imageTag", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-agent-skills.containers[0].image", - "deploy/k8s/base/workloads.yaml.Deployment.hwlab-agent-skills.containers[0].env.HWLAB_SKILLS_COMMIT_ID" - ], - "pendingPaths": [] - }, - "commands": [ - "node scripts/deploy-desired-state-plan.mjs --promotion-commit 48dfbf9e7f85f2dd8e17505582e811bdacfd60d5 --check --pretty" - ] - }, - "promotionBoundary": { - "writes": [], - "authoritativeDesiredState": [ - "deploy/deploy.json", - "deploy/artifact-catalog.dev.json", - "deploy/k8s/base/workloads.yaml" - ], - "nonAuthoritativeEvidence": [ - "reports/dev-gate/dev-artifacts.json" - ], - "note": "This planner reviews source desired-state only. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence." - }, - "cloudApiDb": { - "contractVersion": "v1", - "environment": "dev", - "status": "pass", - "ready": true, - "secretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "sslMode": { - "env": "HWLAB_CLOUD_DB_SSL_MODE", - "value": "disable", - "workloadValue": "disable", - "expected": "disable", - "deployMatches": true, - "workloadMatches": true, - "secret": false - }, - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false - }, - "codeAgentProvider": { - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "status": "pass", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "runtime-env" - } - ], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "deployPresent": true, - "workloadPresent": true, - "deployMatchesDevProxy": true, - "workloadMatchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "missingDeployEnv": [], - "missingWorkloadEnv": [], - "deployMismatches": [], - "workloadMismatches": [], - "missingSecretRefs": [], - "missingEgressContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "providerConnected": false, - "fixtureEvidence": false - }, - "reportHints": { - "path": "reports/dev-gate/dev-artifacts.json", - "present": true, - "authoritative": false, - "note": "report snapshots are contextual evidence only; deploy desired-state remains authoritative in deploy/, not reports/", - "commitId": "48dfbf9", - "matchesDesiredState": true, - "artifactPublish": { - "status": "published", - "sourceCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "sourceMatchesDesiredState": true, - "publishedCount": 13, - "serviceCount": 13, - "registryVerified": null, - "registryCapabilitiesClassification": "degraded" - } - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-cloud-api", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-cloud-web", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_COMMIT_ID": { - "name": "HWLAB_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE": { - "name": "HWLAB_IMAGE", - "value": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - "HWLAB_IMAGE_TAG": { - "name": "HWLAB_IMAGE_TAG", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-cloud-web", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-agent-mgr", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-agent-mgr", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-agent-worker", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Job", - "name": "hwlab-agent-worker-template", - "namespace": "hwlab-dev", - "container": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-agent-worker-template", - "container": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-gateway", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "container": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-gateway", - "container": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-gateway-simu", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "StatefulSet", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-gateway-simu", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-box-simu", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "StatefulSet", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-box-simu", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-patch-panel", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-patch-panel", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-router", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-router", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-tunnel-client", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-tunnel-client", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-edge-proxy", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-edge-proxy", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-cli", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Job", - "name": "hwlab-cli-template", - "namespace": "hwlab-dev", - "container": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": {}, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_SKILLS_COMMIT_ID", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-cli-template", - "container": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9" - } - ] - } - }, - { - "serviceId": "hwlab-agent-skills", - "deploy": { - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_SKILLS_COMMIT_ID": { - "name": "HWLAB_SKILLS_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - }, - "catalog": { - "commitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "publishState": "published", - "artifactRequired": true - }, - "workloads": [ - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "imageTag": "48dfbf9", - "envMirrors": { - "present": { - "HWLAB_SKILLS_COMMIT_ID": { - "name": "HWLAB_SKILLS_COMMIT_ID", - "value": "48dfbf9", - "expected": "48dfbf9", - "status": "match" - } - }, - "missing": [ - "HWLAB_COMMIT_ID", - "HWLAB_IMAGE", - "HWLAB_IMAGE_TAG", - "HWLAB_IMAGE_DIGEST" - ], - "diagnostics": [] - } - } - ], - "promotion": { - "targetCommitId": "48dfbf9e7f85f2dd8e17505582e811bdacfd60d5", - "targetImageTag": "48dfbf9", - "deployImage": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "catalogImage": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "workloadImages": [ - { - "workload": "hwlab-agent-skills", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9" - } - ] - } - } - ], - "diagnostics": [] - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": null - }, - { - "id": "runtime-db-provisioning", - "phase": "applying", - "status": "pass", - "command": "kubectl -n hwlab-dev apply/wait/logs job/hwlab-runtime-provision-5332b2f8", - "code": 0, - "startedAt": "2026-05-24T00:18:16.303Z", - "finishedAt": "2026-05-24T00:18:23.129Z", - "stdoutJson": { - "issue": "pikasTech/HWLAB#311", - "mode": "apply", - "generatedAt": "2026-05-24T00:18:19.999Z", - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "prodAllowed": false, - "dbUrlEnv": "HWLAB_CLOUD_DB_URL", - "dbUrlSecretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url" - }, - "adminUrlEnv": "HWLAB_CLOUD_DB_ADMIN_URL", - "adminUrlSecretRef": { - "env": "HWLAB_CLOUD_DB_ADMIN_URL", - "secretName": "hwlab-cloud-api-dev-db-admin", - "secretKey": "admin-url" - }, - "dbEndpointAuthority": "secret-url-host" - }, - "db": { - "targetUrlPresent": true, - "targetUrlValueRedacted": true, - "targetEndpointRedacted": true, - "targetEndpointClass": "secret-url-host", - "targetParseStatus": "ready", - "targetErrorCode": null, - "targetRoleNamePresent": true, - "targetDatabaseNamePresent": true, - "targetDatabaseAllowed": true, - "targetPasswordPresent": true, - "sslMode": "disable", - "sslModeSecret": false, - "adminUrlPresent": true, - "adminUrlValueRedacted": true, - "adminEndpointRedacted": true, - "dbUrlSecretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url" - }, - "adminUrlSecretRef": { - "env": "HWLAB_CLOUD_DB_ADMIN_URL", - "secretName": "hwlab-cloud-api-dev-db-admin", - "secretKey": "admin-url" - }, - "endpointAuthority": "secret-url-host" - }, - "applyBoundary": { - "defaultMode": "check", - "mode": "apply", - "liveDbReads": true, - "liveDbWrites": true, - "requiresForLiveDbRead": [ - "--confirm-dev", - "HWLAB_CLOUD_DB_URL from hwlab-cloud-api-dev-db/database-url", - "HWLAB_CLOUD_DB_ADMIN_URL from hwlab-cloud-api-dev-db-admin/admin-url unless runtime readiness already proves auth" - ], - "requiresForApply": [ - "--apply", - "--confirm-dev", - "--confirmed-non-production", - "HWLAB_CLOUD_DB_URL from hwlab-cloud-api-dev-db/database-url", - "HWLAB_CLOUD_DB_ADMIN_URL from hwlab-cloud-api-dev-db-admin/admin-url" - ], - "writeScope": [ - "DEV Postgres target role named by the redacted cloud-api DB URL", - "DEV Postgres target database named by the redacted cloud-api DB URL", - "DEV target DB CONNECT and public schema CREATE/USAGE grants for that role" - ], - "noWriteScope": [ - "Kubernetes Secret resources", - "PROD", - "service restarts", - "schema migrations", - "M3/M4/M5 acceptance" - ], - "forbidden": [ - "printing DB URLs, passwords, tokens, Secret values, or kubeconfig material", - "manual psql or node stdin database writes", - "mutating PROD" - ] - }, - "safety": { - "devOnly": true, - "environment": "dev", - "targetNamespace": "hwlab-dev", - "prodAllowed": false, - "sourceStoresSecretValues": false, - "printsSecretValues": false, - "secretValuesPrinted": false, - "dbUrlValueRedacted": true, - "adminUrlValueRedacted": true, - "endpointRedacted": true, - "readsKubernetesSecrets": false, - "writesKubernetesSecrets": false, - "liveDbWrites": true, - "fixtureEvidenceAllowed": false - }, - "actions": { - "liveDbReadAttempted": true, - "liveDbWriteAttempted": true, - "adminInspectionAttempted": false, - "adminWriteAttempted": true, - "appRuntimeReadinessAttempted": true, - "roleCreated": false, - "roleExisted": true, - "rolePasswordSynchronized": true, - "databaseCreated": false, - "databaseExisted": true, - "databaseConnectGranted": true, - "schemaPrivilegesGranted": true, - "readinessVerified": true - }, - "provisioning": { - "targetRole": { - "checked": true, - "exists": true, - "created": false, - "ready": true, - "targetNamePresent": true, - "inferredFromRuntime": true - }, - "targetDatabase": { - "checked": true, - "exists": true, - "created": false, - "ready": true, - "targetNamePresent": true, - "inferredFromRuntime": true - }, - "privileges": { - "checked": true, - "ready": true, - "databaseConnectGranted": true, - "schemaPrivilegesGranted": true - }, - "ready": true - }, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "fixtureEvidence": false, - "connection": { - "queryAttempted": true, - "queryResult": "durable_readiness_ready", - "endpointRedacted": true, - "valueRedacted": true, - "errorCode": null - }, - "schema": { - "checked": true, - "ready": true, - "missingTables": [], - "missingColumns": [] - }, - "migration": { - "checked": true, - "ready": true, - "requiredMigrationId": "0001_cloud_core_skeleton", - "appliedMigrationId": "0001_cloud_core_skeleton", - "missing": false - }, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - } - }, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "role": { - "checked": false, - "ready": false, - "status": "not_checked", - "blocker": null, - "summary": "role existence requires DEV admin catalog inspection" - }, - "database": { - "checked": false, - "ready": false, - "status": "not_checked", - "blocker": null, - "summary": "database existence requires DEV admin catalog inspection" - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - } - }, - "downstreamRuntimeGates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - }, - "blockers": [], - "safetyRefusal": false, - "conclusion": { - "status": "ready", - "summary": "DEV DB role/database provisioning completed without printing secret values.", - "blockerCount": 0 - } - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": "reports/dev-gate/dev-runtime-provisioning-report.json", - "k8sJob": { - "jobName": "hwlab-runtime-provision-5332b2f8", - "phase": "complete", - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "secretRefsOnly": true, - "secretValuesRead": false, - "secretValuesPrinted": false - } - }, - { - "id": "runtime-db-migration", - "phase": "applying", - "status": "pass", - "command": "kubectl -n hwlab-dev apply/wait/logs job/hwlab-runtime-migrate-5332b2f8", - "code": 0, - "startedAt": "2026-05-24T00:18:23.129Z", - "finishedAt": "2026-05-24T00:18:26.325Z", - "stdoutJson": { - "issue": "pikasTech/HWLAB#164", - "mode": "apply", - "generatedAt": "2026-05-24T00:18:23.761Z", - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "prodAllowed": false, - "dbUrlEnv": "HWLAB_CLOUD_DB_URL", - "dbUrlSecretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url" - }, - "dbEndpointAuthority": "secret-url-host" - }, - "migration": { - "id": "0001_cloud_core_skeleton", - "path": "internal/db/migrations/0001_cloud_core_skeleton.sql", - "sha256": "814c654ba17a4f515b0179940ee248462648d17c814ec76c034f398bd92c7a97", - "schemaVersion": "runtime-durable-postgres-v1", - "ledgerTable": "hwlab_schema_migrations", - "runtimeAdapter": "postgres" - }, - "db": { - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false, - "source": "runtime-env" - } - ], - "urlPresent": true, - "urlValueRedacted": true, - "sslMode": "disable", - "sslModeSecret": false, - "secretRef": { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url" - }, - "endpointAuthority": "secret-url-host", - "optionalPublicDnsAliasRequired": false - }, - "sourceCheck": { - "ready": true, - "checked": true, - "migrationPath": "internal/db/migrations/0001_cloud_core_skeleton.sql", - "requiredMigrationId": "0001_cloud_core_skeleton", - "requiredSchemaVersion": "runtime-durable-postgres-v1", - "requiredTables": [ - "gateway_sessions", - "box_resources", - "box_capabilities", - "hardware_operations", - "audit_events", - "evidence_records" - ], - "requiredLedgerTable": "hwlab_schema_migrations", - "missing": [], - "connectsToDatabase": false, - "fixtureEvidence": false - }, - "applyBoundary": { - "defaultMode": "check", - "mode": "apply", - "liveDbReads": true, - "liveDbWrites": true, - "requiresForLiveDbRead": [ - "--confirm-dev", - "HWLAB_CLOUD_DB_URL from hwlab-cloud-api-dev-db/database-url" - ], - "requiresForApply": [ - "--apply", - "--confirm-dev", - "--confirmed-non-production", - "HWLAB_CLOUD_DB_URL from hwlab-cloud-api-dev-db/database-url" - ], - "writeScope": [ - "DEV Postgres schema objects declared by internal/db/migrations/0001_cloud_core_skeleton.sql", - "DEV hwlab_schema_migrations ledger row 0001_cloud_core_skeleton" - ], - "noWriteScope": [ - "Kubernetes Secret resources", - "PROD", - "service restarts", - "M3/M4/M5 acceptance" - ], - "forbidden": [ - "printing DB URLs, passwords, tokens, Secret values, or kubeconfig material", - "using fixture output as DEV-LIVE runtime evidence", - "mutating PROD" - ] - }, - "safety": { - "devOnly": true, - "environment": "dev", - "targetNamespace": "hwlab-dev", - "prodAllowed": false, - "sourceStoresSecretValues": false, - "printsSecretValues": false, - "dbUrlValueRedacted": true, - "endpointRedacted": true, - "readsKubernetesSecrets": false, - "writesKubernetesSecrets": false, - "liveDbWrites": true, - "fixtureEvidenceAllowed": false - }, - "actions": { - "sourceValidated": true, - "liveDbReadAttempted": true, - "liveDbWriteAttempted": true, - "migrationApplied": true, - "readinessVerified": true - }, - "gates": { - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - }, - "readiness": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null, - "summary": "ready" - } - }, - "blockers": [], - "safetyRefusal": false, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "fixtureEvidence": false, - "connection": { - "queryAttempted": true, - "queryResult": "durable_readiness_ready", - "endpointRedacted": true, - "valueRedacted": true, - "errorCode": null - }, - "schema": { - "checked": true, - "ready": true, - "requiredTables": [ - "gateway_sessions", - "box_resources", - "box_capabilities", - "hardware_operations", - "audit_events", - "evidence_records" - ], - "missingTables": [], - "missingColumns": [] - }, - "migration": { - "checked": true, - "ready": true, - "table": "hwlab_schema_migrations", - "requiredMigrationId": "0001_cloud_core_skeleton", - "requiredSchemaVersion": "runtime-durable-postgres-v1", - "appliedMigrationId": "0001_cloud_core_skeleton", - "appliedSchemaVersion": "runtime-durable-postgres-v1", - "missing": false - } - }, - "conclusion": { - "status": "ready", - "summary": "DEV runtime migration applied and durable runtime readiness was verified without printing secret values.", - "blockerCount": 0 - } - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": "reports/dev-gate/dev-runtime-migration-report.json", - "k8sJob": { - "jobName": "hwlab-runtime-migrate-5332b2f8", - "phase": "complete", - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "secretRefsOnly": true, - "secretValuesRead": false, - "secretValuesPrinted": false - } - }, - { - "id": "dev-deploy-apply", - "phase": "applying", - "status": "pass", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report --kubeconfig /etc/rancher/k3s/k3s.yaml", - "code": 0, - "startedAt": "2026-05-24T00:18:26.325Z", - "finishedAt": "2026-05-24T00:18:29.334Z", - "stdoutJson": { - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-deploy-report.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#33", - "supports": [ - "pikasTech/HWLAB#164", - "pikasTech/HWLAB#143", - "pikasTech/HWLAB#99", - "pikasTech/HWLAB#63", - "pikasTech/HWLAB#50", - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#17", - "pikasTech/HWLAB#32", - "pikasTech/HWLAB#30" - ], - "taskId": "dev-deploy-apply", - "commitId": "48dfbf9", - "acceptanceLevel": "dev_deploy_apply", - "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 DEV deploy apply report; active public API/health endpoint is 16667 and browser endpoint is 16666." - }, - "status": "pass", - "generatedAt": "2026-05-24T00:18:29.328Z", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/dev-deploy-apply.md", - "docs/artifact-catalog.md", - "deploy/README.md" - ], - "summary": "DEV deploy apply is pinned to hwlab-dev and the frozen HWLAB service IDs" - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-deploy-apply.mjs", - "node --check scripts/src/dev-deploy-apply.mjs", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "This deploy apply task did not run local smoke because artifact/executor blockers stop promotion first." - ], - "summary": "Local smoke remains available but is not a substitute for DEV artifact publish evidence." - }, - "dryRun": { - "status": "pass", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - "evidence": [ - "artifact services checked: 13", - "expected artifact commit: 48dfbf9", - "namespace: hwlab-dev", - "workloads planned: 13", - "kubectl executor: /usr/local/bin/kubectl", - "kubectl kubeconfig source: flag:--kubeconfig", - "kubectl kubeconfig: /etc/rancher/k3s/k3s.yaml", - "live health: pass", - "code agent provider desired-state: pass", - "code agent provider live env before apply: pass", - "template job replacements: 2", - "legacy simulator deployment cleanups: 0" - ], - "summary": "DEV dry-run preflight passed." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "DEV artifact catalog must contain CI publish evidence and registry digests", - "kubectl must be available for D601 hwlab-dev and must not target PROD", - "hwlab-cloud-api /health/live must report serviceId, dev environment, and DB env readiness without exposing secret values", - "hwlab-cloud-api must declare and preserve OPENAI_API_KEY from hwlab-code-agent-provider/openai-api-key plus the DEV Code Agent egress proxy env without reading Secret values", - "pods, services, configmaps, and image commit tags must be observable in hwlab-dev" - ], - "summary": "DEV deploy preconditions are satisfied." - }, - "devDeployApply": { - "mode": "apply", - "conclusion": { - "status": "ready", - "reason": "All DEV-only preconditions passed; human approval is still required before apply.", - "blockerCount": 0 - }, - "applyBoundary": { - "currentMode": "apply-requested", - "defaultNoWrite": false, - "mutationAttempted": true, - "mutationAllowed": true, - "applyRequiresFlags": [ - "--apply", - "--confirm-dev", - "--confirmed-non-production" - ], - "kubeconfigSource": "flag:--kubeconfig", - "writeScope": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -k deploy/k8s/dev, namespace hwlab-dev only; delete/recreate is allowed only for explicitly allowlisted suspended DEV template Jobs and stale legacy simulator Deployments", - "noWriteScope": "source/catalog/k8s validation, optional DEV health probe, KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl read, replacement planning, and server-side dry-run only", - "forbiddenActions": [ - "prod-deploy", - "secret-read", - "runtime-substitute", - "unidesk-restart", - "code-queue-restart", - "backend-core-restart", - "heavy-master-e2e", - "force-push" - ] - }, - "mutationAttempted": true, - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "prodDisabled": true - }, - "artifactPlan": { - "expectedArtifactCommit": "48dfbf9", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "sourceCommitId": "48dfbf9", - "serviceCommitIds": [ - "48dfbf9" - ], - "matchesSourceCommit": true, - "published": true, - "registryVerified": true, - "imageCount": 13, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "unpublishedServices": [] - }, - "workloadPlan": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-cloud-api" - ], - "containers": [ - { - "name": "hwlab-cloud-api", - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-cloud-web" - ], - "containers": [ - { - "name": "hwlab-cloud-web", - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-agent-mgr" - ], - "containers": [ - { - "name": "hwlab-agent-mgr", - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9" - } - ] - }, - { - "kind": "Job", - "name": "hwlab-agent-worker-template", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-agent-worker" - ], - "containers": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-gateway" - ], - "containers": [ - { - "name": "hwlab-gateway", - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9" - } - ] - }, - { - "kind": "StatefulSet", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "replicas": 2, - "serviceIds": [ - "hwlab-gateway-simu" - ], - "containers": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9" - } - ] - }, - { - "kind": "StatefulSet", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "replicas": 2, - "serviceIds": [ - "hwlab-box-simu" - ], - "containers": [ - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-patch-panel" - ], - "containers": [ - { - "name": "hwlab-patch-panel", - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-router" - ], - "containers": [ - { - "name": "hwlab-router", - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-tunnel-client" - ], - "containers": [ - { - "name": "hwlab-tunnel-client", - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-edge-proxy" - ], - "containers": [ - { - "name": "hwlab-edge-proxy", - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9" - } - ] - }, - { - "kind": "Job", - "name": "hwlab-cli-template", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-cli" - ], - "containers": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-agent-skills" - ], - "containers": [ - { - "name": "hwlab-agent-skills", - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9" - } - ] - } - ], - "servicePlan": [ - { - "kind": "Service", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-1", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-2", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-web", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 8080, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu-1", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu-2", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7001, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-cli", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cli", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7501, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "serviceId": "hwlab-agent-skills", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7430, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "serviceId": "hwlab-agent-mgr", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7410, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "serviceId": "hwlab-patch-panel", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7301, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "serviceId": "hwlab-router", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7401, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "serviceId": "hwlab-tunnel-client", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7402, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "serviceId": "hwlab-edge-proxy", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ] - } - ], - "templateJobReplacementPolicy": { - "status": "active", - "namespace": "hwlab-dev", - "scope": "DEV-only suspended template Jobs", - "allowedJobs": [ - { - "name": "hwlab-agent-worker-template", - "serviceId": "hwlab-agent-worker", - "reason": "suspended session worker template Job has immutable spec.template" - }, - { - "name": "hwlab-cli-template", - "serviceId": "hwlab-cli", - "reason": "suspended CLI template Job has immutable spec.template" - } - ] - }, - "templateJobReplacements": [ - { - "namespace": "hwlab-dev", - "jobName": "hwlab-agent-worker-template", - "allowed": true, - "desiredSuspended": true, - "liveExists": true, - "liveSuspended": true, - "oldImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:3df89fe", - "newImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "oldImages": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:3df89fe" - } - ], - "newImages": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9" - } - ], - "replace": true, - "result": "replaced", - "reason": "Live suspended template Job was deleted and recreated by kubectl apply.", - "deleteCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-agent-worker-template --ignore-not-found=true", - "deleteStdout": "job.batch \"hwlab-agent-worker-template\" deleted from hwlab-dev namespace", - "deleteStderr": "" - }, - { - "namespace": "hwlab-dev", - "jobName": "hwlab-cli-template", - "allowed": true, - "desiredSuspended": true, - "liveExists": true, - "liveSuspended": true, - "oldImage": "127.0.0.1:5000/hwlab/hwlab-cli:3df89fe", - "newImage": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "oldImages": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:3df89fe" - } - ], - "newImages": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9" - } - ], - "replace": true, - "result": "replaced", - "reason": "Live suspended template Job was deleted and recreated by kubectl apply.", - "deleteCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-cli-template --ignore-not-found=true", - "deleteStdout": "job.batch \"hwlab-cli-template\" deleted from hwlab-dev namespace", - "deleteStderr": "" - } - ], - "legacySimulatorDeploymentCleanupPolicy": { - "status": "active", - "namespace": "hwlab-dev", - "scope": "DEV-only stale simulator Deployments replaced by indexed StatefulSets", - "allowedDeployments": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "replacementKind": "StatefulSet", - "reason": "M3 DEV requires exactly two indexed gateway-simu identities/sessions" - }, - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "replacementKind": "StatefulSet", - "reason": "M3 DEV requires exactly two indexed box-simu resource identities" - } - ] - }, - "legacySimulatorDeploymentCleanups": [ - { - "namespace": "hwlab-dev", - "deploymentName": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "allowed": true, - "desiredReplacementExists": true, - "desiredReplacementKind": "StatefulSet", - "liveExists": false, - "oldImage": null, - "newImage": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "oldImages": [], - "newImages": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9" - } - ], - "cleanup": false, - "result": "not_found", - "reason": "Live legacy Deployment is absent; no cleanup is needed." - }, - { - "namespace": "hwlab-dev", - "deploymentName": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "allowed": true, - "desiredReplacementExists": true, - "desiredReplacementKind": "StatefulSet", - "liveExists": false, - "oldImage": null, - "newImage": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "oldImages": [], - "newImages": [ - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9" - } - ], - "cleanup": false, - "result": "not_found", - "reason": "Live legacy Deployment is absent; no cleanup is needed." - } - ], - "cloudWebRollout": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "after_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "44", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageMatchesDesired": true, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - }, - "runtimeEnvMatchesDesired": true, - "runtimeEnvDrift": [], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "cloudWebRolloutBeforeApply": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "before_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "43", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageMatchesDesired": false, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "drift", - "matchesDesired": false, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ], - "drift": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ] - }, - "runtimeEnvMatchesDesired": false, - "runtimeEnvDrift": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "cloudWebRolloutAfterApply": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "after_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "44", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageMatchesDesired": true, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - }, - "runtimeEnvMatchesDesired": true, - "runtimeEnvDrift": [], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "codeAgentProvider": { - "desiredState": { - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "status": "pass", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "runtime-env" - } - ], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "deployPresent": true, - "workloadPresent": true, - "deployMatchesDevProxy": true, - "workloadMatchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "missingDeployEnv": [], - "missingWorkloadEnv": [], - "deployMismatches": [], - "workloadMismatches": [], - "missingSecretRefs": [], - "missingEgressContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "providerConnected": false, - "fixtureEvidence": false - }, - "liveBeforeApply": { - "phase": "before_apply", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "status": "pass", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-api -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "expectedSecretRef": "hwlab-code-agent-provider/openai-api-key", - "expectedEgressTarget": "dev-egress-proxy", - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true, - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "present": true, - "redacted": true, - "source": "runtime-env" - } - ], - "missingEnv": [], - "mismatchedEnv": [], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "present": true, - "matchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "secretMaterialRead": false, - "providerConnected": false, - "deployment": "hwlab-cloud-api", - "container": "hwlab-cloud-api", - "validationScope": "deployment-env-and-secretKeyRef-metadata-only" - }, - "liveAfterApply": { - "phase": "after_apply", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "status": "pass", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-api -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "expectedSecretRef": "hwlab-code-agent-provider/openai-api-key", - "expectedEgressTarget": "dev-egress-proxy", - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true, - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "present": true, - "redacted": true, - "source": "runtime-env" - } - ], - "missingEnv": [], - "mismatchedEnv": [], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "present": true, - "matchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "secretMaterialRead": false, - "providerConnected": false, - "deployment": "hwlab-cloud-api", - "container": "hwlab-cloud-api", - "validationScope": "deployment-env-and-secretKeyRef-metadata-only" - }, - "validationGate": "no-secret provider env/secretKeyRef preservation", - "providerCallAttempted": false, - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true - }, - "applyStep": { - "status": "pass", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -k deploy/k8s/dev", - "stdout": "namespace/hwlab-dev unchanged\nconfigmap/hwlab-dev-health-contract unchanged\nservice/hwlab-agent-mgr unchanged\nservice/hwlab-agent-skills unchanged\nservice/hwlab-box-simu unchanged\nservice/hwlab-box-simu-1 unchanged\nservice/hwlab-box-simu-2 unchanged\nservice/hwlab-cli unchanged\nservice/hwlab-cloud-api unchanged\nservice/hwlab-cloud-web unchanged\nservice/hwlab-edge-proxy unchanged\nservice/hwlab-gateway unchanged\nservice/hwlab-gateway-simu unchanged\nservice/hwlab-gateway-simu-1 unchanged\nservice/hwlab-gateway-simu-2 unchanged\nservice/hwlab-patch-panel unchanged\nservice/hwlab-router unchanged\nservice/hwlab-tunnel-client unchanged\ndeployment.apps/hwlab-agent-mgr configured\ndeployment.apps/hwlab-agent-skills configured\ndeployment.apps/hwlab-cloud-api configured\ndeployment.apps/hwlab-cloud-web configured\ndeployment.apps/hwlab-edge-proxy configured\ndeployment.apps/hwlab-gateway configured\ndeployment.apps/hwlab-patch-panel configured\ndeployment.apps/hwlab-router configured\ndeployment.apps/hwlab-tunnel-client configured\nstatefulset.apps/hwlab-box-simu configured\nstatefulset.apps/hwlab-gateway-simu configured\njob.batch/hwlab-agent-worker-template created\njob.batch/hwlab-cli-template created", - "stderr": "# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically.\n# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically.", - "mutationAttempted": true, - "expectedImmutableTemplateJobDryRun": false, - "expectedImmutableTemplateJobs": [] - }, - "manualCommands": { - "status": "ready", - "beforeHumanApproval": [ - "node scripts/dev-deploy-apply.mjs --dry-run --write-report", - "node scripts/validate-dev-gate-report.mjs" - ], - "afterHumanApproval": [ - "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report" - ], - "summary": "Run the dry-run plan and validator immediately before requesting human approval; live apply still requires the explicit DEV confirmation flags." - }, - "rollbackHint": { - "namespace": "hwlab-dev", - "strategy": "DEV-only Kubernetes rollback using deployment revision history; do not touch PROD or UniDesk services.", - "captureBeforeApply": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods,services,configmaps,deployments,jobs -o wide", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployments -o json" - ], - "deploymentRollbackCommands": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-cloud-api", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-cloud-web", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-agent-mgr", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-gateway", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-patch-panel", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-router", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-tunnel-client", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-edge-proxy", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-agent-skills" - ], - "jobCleanupCommands": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-agent-worker-template --ignore-not-found", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-cli-template --ignore-not-found" - ], - "postRollbackChecks": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-api --timeout=120s", - "curl -fsS http://74.48.78.17:16667/health/live" - ] - }, - "remainingBlockers": [], - "artifactEvidence": [ - { - "serviceId": "hwlab-cloud-api", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "publishState": "published" - }, - { - "serviceId": "hwlab-cloud-web", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-mgr", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-worker", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "publishState": "published" - }, - { - "serviceId": "hwlab-gateway", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "publishState": "published" - }, - { - "serviceId": "hwlab-gateway-simu", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "publishState": "published" - }, - { - "serviceId": "hwlab-box-simu", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "publishState": "published" - }, - { - "serviceId": "hwlab-patch-panel", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "publishState": "published" - }, - { - "serviceId": "hwlab-router", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "publishState": "published" - }, - { - "serviceId": "hwlab-tunnel-client", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "publishState": "published" - }, - { - "serviceId": "hwlab-edge-proxy", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "publishState": "published" - }, - { - "serviceId": "hwlab-cli", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-skills", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "publishState": "published" - } - ], - "cloudApiDb": { - "status": "contract-ready", - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "manifestPresent": true, - "k8sPresent": true, - "redacted": true, - "source": "k8s-secret-ref", - "secretRef": { - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "redacted": true - } - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "manifestPresent": true, - "k8sPresent": true, - "redacted": false, - "source": "runtime-env", - "secretRef": null - } - ], - "missingManifest": [], - "missingK8s": [], - "missingSecretRefs": [], - "mismatchedNonSecretEnv": [], - "missingDnsContract": [], - "dns": { - "ready": true, - "source": "optional-public-dns-alias", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "requiredForReadiness": false, - "usedForProbe": false, - "servicePresent": false, - "secretMaterialRead": false, - "liveDbEvidence": false, - "missing": [], - "actual": {} - }, - "runtimeHealth": { - "status": "degraded", - "ready": false, - "configReady": true, - "connected": false, - "liveConnected": false, - "connectionChecked": false, - "connectionAttempted": false, - "connectionResult": "unknown", - "liveDbEvidence": false, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false - } - ], - "missingEnv": [], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "envInjected": true, - "secretPresent": "not_observed", - "secretKeyPresent": "not_observed", - "redacted": true - } - ], - "endpointSource": "secret-url-host", - "optionalPublicDnsAlias": { - "source": "optional-public-dns-alias", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "requiredForReadiness": false, - "usedForProbe": false, - "envPresent": false, - "readyGate": "not_readiness_authority" - }, - "connection": { - "attempted": false, - "result": "unknown", - "endpointRedacted": true, - "valueRedacted": true - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "readinessLayers": { - "dns": { - "status": "not_proven", - "result": "unknown" - }, - "tcp": { - "status": "not_proven", - "result": "unknown" - }, - "ssl": { - "status": "not_proven", - "result": "unknown" - }, - "auth": { - "status": "not_proven", - "result": "unknown" - }, - "schema": { - "status": "not_proven", - "result": "unknown" - }, - "migration": { - "status": "not_proven", - "result": "unknown" - }, - "durability": { - "status": "not_proven", - "result": "unknown" - } - }, - "blocker": null, - "fixtureEvidence": false - }, - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false, - "note": "This check records DB env, Secret reference, and Secret URL host authority only. Optional cloud-api-db alias presence is diagnostic and is not live DB evidence." - }, - "k8sManifest": { - "resourceCounts": { - "workloads": 13, - "services": 16, - "configMaps": 1 - }, - "images": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "kind": "Deployment", - "name": "hwlab-cloud-api" - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "kind": "Deployment", - "name": "hwlab-cloud-web" - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "kind": "Deployment", - "name": "hwlab-agent-mgr" - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "kind": "Job", - "name": "hwlab-agent-worker-template" - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "kind": "Deployment", - "name": "hwlab-gateway" - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "kind": "StatefulSet", - "name": "hwlab-gateway-simu" - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "kind": "StatefulSet", - "name": "hwlab-box-simu" - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "kind": "Deployment", - "name": "hwlab-patch-panel" - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "kind": "Deployment", - "name": "hwlab-router" - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "kind": "Deployment", - "name": "hwlab-tunnel-client" - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "kind": "Deployment", - "name": "hwlab-edge-proxy" - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "kind": "Job", - "name": "hwlab-cli-template" - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "kind": "Deployment", - "name": "hwlab-agent-skills" - } - ], - "runtimeIdentityEnv": [ - { - "serviceId": "hwlab-cloud-web", - "kind": "Deployment", - "name": "hwlab-cloud-web", - "container": "hwlab-cloud-web", - "expected": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - } - ] - }, - "clusterObservation": { - "status": "pass", - "executor": "/usr/local/bin/kubectl", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig", - "commandPrefix": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get pods,services,configmaps,deployments,jobs -n hwlab-dev -o json", - "version": "clientVersion:\n buildDate: \"2026-05-12T09:54:35Z\"\n compiler: gc\n gitCommit: 756939600b9a7180fc2df6550a4585b638875e67\n gitTreeState: clean\n gitVersion: v1.36.1\n goVersion: go1.26.2\n major: \"1\"\n minor: \"36\"\n platform: linux/amd64\nkustomizeVersion: v5.8.1", - "resources": [ - { - "kind": "Pod", - "name": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-agent-skills-69485c4dc4-mpgdb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-box-simu-0", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-box-simu-1", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-frpc-59f57d479c-tgjhl", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-gateway-simu-0", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-gateway-simu-1", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-patch-panel-55b9c584d7-7rzqb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-router-76fd584bdb-t26cs", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-runtime-migrate-5332b2f8-mlg9p", - "phase": "Succeeded" - }, - { - "kind": "Pod", - "name": "hwlab-runtime-provision-5332b2f8-qdpq7", - "phase": "Succeeded" - }, - { - "kind": "Pod", - "name": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "phase": "Running" - }, - { - "kind": "Service", - "name": "hwlab-agent-mgr" - }, - { - "kind": "Service", - "name": "hwlab-agent-skills" - }, - { - "kind": "Service", - "name": "hwlab-box-simu" - }, - { - "kind": "Service", - "name": "hwlab-box-simu-1" - }, - { - "kind": "Service", - "name": "hwlab-box-simu-2" - }, - { - "kind": "Service", - "name": "hwlab-cli" - }, - { - "kind": "Service", - "name": "hwlab-cloud-api" - }, - { - "kind": "Service", - "name": "hwlab-cloud-web" - }, - { - "kind": "Service", - "name": "hwlab-edge-proxy" - }, - { - "kind": "Service", - "name": "hwlab-gateway" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-1" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-2" - }, - { - "kind": "Service", - "name": "hwlab-patch-panel" - }, - { - "kind": "Service", - "name": "hwlab-router" - }, - { - "kind": "Service", - "name": "hwlab-tunnel-client" - }, - { - "kind": "ConfigMap", - "name": "hwlab-dev-health-contract" - }, - { - "kind": "ConfigMap", - "name": "hwlab-frpc-config" - }, - { - "kind": "ConfigMap", - "name": "kube-root-ca.crt" - }, - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-frpc", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-gateway" - }, - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-router", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Job", - "name": "hwlab-agent-worker-template" - }, - { - "kind": "Job", - "name": "hwlab-cli-template" - }, - { - "kind": "Job", - "name": "hwlab-runtime-migrate-5332b2f8" - }, - { - "kind": "Job", - "name": "hwlab-runtime-provision-5332b2f8" - } - ] - }, - "liveProbe": { - "status": "pass", - "url": "http://74.48.78.17:16667/health/live", - "httpStatus": 200, - "body": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"ok\",\"ready\":true,\"revision\":\"3df89fe15100f3ef3c2e12e1a001b5370d54eff9\",\"readiness\":{\"contractVersion\":\"v3\",\"ready\":true,\"status\":\"ok\",\"blockerCodes\":[],\"blockers\":[],\"components\":{\"db\":\"ready\",\"codeAgent\":\"ready\",\"runtime\":\"ready\",\"provider\":\"ready\",\"dbDurable\":\"ready\",\"sessionRunner\":\"codex_stdio_ready\",\"codexStdio\":\"ready\"},\"provider\":{\"status\":\"ready\",\"ready\":true,\"configured\":true,\"provider\":\"codex-stdio\",\"model\":\"gpt-5.5\",\"backend" - } - }, - "blockers": [], - "notes": "DEV-only result. No PROD resources, secret values, force push, heavy e2e, browser e2e, or UniDesk runtime substitute were used." - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": "reports/dev-gate/dev-deploy-report.json" - }, - { - "id": "runtime-durable-postflight", - "phase": "verifying", - "status": "blocked", - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report --fail-on-blocked", - "code": 1, - "startedAt": "2026-05-24T00:18:29.423Z", - "finishedAt": "2026-05-24T00:18:39.661Z", - "stdoutJson": { - "issue": "pikasTech/HWLAB#311", - "mode": "dev-live", - "generatedAt": "2026-05-24T00:18:29.459Z", - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "prodAllowed": false, - "apiHealthUrl": "http://74.48.78.17:16667/health/live", - "apiV1Url": "http://74.48.78.17:16667/v1", - "m3Target": "api" - }, - "actions": { - "healthLiveReadAttempted": true, - "v1ReadAttempted": true, - "m3MutationAttempted": true, - "m3MutationAllowed": true - }, - "apiHealth": { - "check": "GET /health/live", - "httpStatus": 200, - "reachable": true, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "status": "ok", - "ready": true, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "blockedLayer": null, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - } - }, - "runtimeDurableReadiness": { - "status": "ready", - "ready": true, - "classification": "runtime_durable_ready", - "reasonCode": "runtime_durable_ready", - "reason": "runtime durable ready; adapter=postgres; queryResult=durable_readiness_ready", - "impact": "M3 durable postflight may run because runtime persistence readiness is green.", - "safeNextAction": "Continue with repo-owned DEV CD postflight; do not read or print Secret values.", - "retryable": true, - "readonly": false, - "evidence": { - "adapter": "postgres", - "durable": true, - "runtimeReady": true, - "durabilityReady": true, - "liveRuntimeEvidence": true, - "blocker": null, - "blockedLayer": null, - "queryResult": "durable_readiness_ready", - "readyQueryResult": true, - "rawStatus": "ok", - "postgresRequired": true, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - }, - "secretMaterialRead": false - } - }, - "db": { - "ready": true, - "connected": true, - "liveDbEvidence": true, - "endpointSource": "secret-url-host", - "connectionResult": "connected", - "endpointRedacted": true, - "valueRedacted": true, - "runtimeReadiness": { - "ready": true, - "status": "ready", - "blocker": null, - "queryResult": "durable_readiness_ready", - "requiredEvidence": "runtime_adapter_schema_migration_read_query" - } - }, - "readiness": { - "ready": true, - "status": "ok", - "durabilityReady": true, - "durabilityBlockedLayer": null - }, - "blockerCodes": [], - "m3IoControl": null, - "error": null - }, - "apiV1": { - "check": "GET /v1", - "httpStatus": 200, - "reachable": true, - "serviceId": "hwlab-cloud-api", - "environment": null, - "status": "ok", - "ready": true, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "blockedLayer": null, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - } - }, - "runtimeDurableReadiness": { - "status": "ready", - "ready": true, - "classification": "runtime_durable_ready", - "reasonCode": "runtime_durable_ready", - "reason": "runtime durable ready; adapter=postgres; queryResult=durable_readiness_ready", - "impact": "M3 durable postflight may run because runtime persistence readiness is green.", - "safeNextAction": "Continue with repo-owned DEV CD postflight; do not read or print Secret values.", - "retryable": true, - "readonly": false, - "evidence": { - "adapter": "postgres", - "durable": true, - "runtimeReady": true, - "durabilityReady": true, - "liveRuntimeEvidence": true, - "blocker": null, - "blockedLayer": null, - "queryResult": "durable_readiness_ready", - "readyQueryResult": true, - "rawStatus": "ok", - "postgresRequired": true, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - }, - "secretMaterialRead": false - } - }, - "db": { - "ready": true, - "connected": true, - "liveDbEvidence": true, - "endpointSource": "secret-url-host", - "connectionResult": "connected", - "endpointRedacted": true, - "valueRedacted": true, - "runtimeReadiness": { - "ready": true, - "status": "ready", - "blocker": null, - "queryResult": "durable_readiness_ready", - "requiredEvidence": "runtime_adapter_schema_migration_read_query" - } - }, - "readiness": { - "ready": true, - "status": "ok", - "durabilityReady": true, - "durabilityBlockedLayer": null - }, - "blockerCodes": [], - "m3IoControl": { - "route": "/v1/m3/io", - "status": "available", - "enabled": true, - "contractVersion": "m3-io-control-v1", - "directFrontendGatewayOrBoxAccess": false, - "genericHardwareRpcExposedToFrontend": false - }, - "error": null - }, - "m3": { - "mode": "dev-live", - "status": "blocked", - "classification": "source_contract_failed", - "trustedGreen": false, - "result": "SOURCE/static contract failed.", - "operationCount": 4, - "persisted": true, - "persistenceContract": { - "contractVersion": "m3-do1-true-false-persisted-v1", - "requiredRoute": "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", - "requiredValues": [ - true, - true, - false, - false - ], - "ready": true, - "sequence": [ - { - "id": "write-do1-true", - "action": "do.write", - "expectedValue": true, - "observedValue": true, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "read-di1-true", - "action": "di.read", - "expectedValue": true, - "observedValue": true, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "write-do1-false", - "action": "do.write", - "expectedValue": false, - "observedValue": false, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "read-di1-false", - "action": "di.read", - "expectedValue": false, - "observedValue": false, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - } - ] - }, - "operations": [ - { - "id": "write-do1-true", - "action": "do.write", - "status": "succeeded", - "operationId": "op_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a", - "traceId": "trc_m3_io_e2e_write_do1_true_9d47c939-0464-437a-a2a5-90cff6714434", - "auditId": "aud_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a_succeeded", - "evidenceId": "evd_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a_succeeded", - "resultValue": true, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "read-di1-true", - "action": "di.read", - "status": "succeeded", - "operationId": "op_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd", - "traceId": "trc_m3_io_e2e_read_di1_true_a81f0788-33ab-42e1-a16e-16e2c171db1b", - "auditId": "aud_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd_succeeded", - "evidenceId": "evd_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd_succeeded", - "resultValue": true, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "write-do1-false", - "action": "do.write", - "status": "succeeded", - "operationId": "op_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a", - "traceId": "trc_m3_io_e2e_write_do1_false_9925768c-ca34-4b17-a9fa-344e7103ec97", - "auditId": "aud_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a_succeeded", - "evidenceId": "evd_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a_succeeded", - "resultValue": false, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "read-di1-false", - "action": "di.read", - "status": "succeeded", - "operationId": "op_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8", - "traceId": "trc_m3_io_e2e_read_di1_false_83b6a1c9-cd8f-402b-bee4-d4c7f3973832", - "auditId": "aud_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8_succeeded", - "evidenceId": "evd_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8_succeeded", - "resultValue": false, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - } - ] - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "m3-durable-postflight", - "status": "open", - "reason": "M3 true/false durable postflight did not produce trusted green persisted evidence.", - "impact": "M3 durable readiness cannot be accepted because trusted evidence is not green.", - "safeNextAction": "Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "retryable": true, - "summary": "M3 true/false durable postflight did not produce trusted green persisted evidence. Impact: M3 durable readiness cannot be accepted because trusted evidence is not green. Safe next action: Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "sourceIssue": "pikasTech/HWLAB#311", - "evidence": { - "classification": "source_contract_failed", - "trustedGreen": false, - "operationCount": 4 - } - } - ], - "safety": { - "devOnly": true, - "environment": "dev", - "prodAllowed": false, - "liveHttpReads": true, - "liveM3Writes": true, - "liveM3WritesRequireDurableRuntimeReady": true, - "readsKubernetesSecrets": false, - "writesKubernetesSecrets": false, - "secretValuesPrinted": false, - "dbUrlValueRedacted": true, - "endpointRedacted": true - }, - "safetyRefusal": false, - "conclusion": { - "status": "blocked", - "summary": "DEV runtime postflight is blocked; M3 is skipped unless durable runtime readiness is proven first.", - "blockerCount": 1 - } - }, - "stdoutTail": null, - "stderrTail": "", - "reportPath": "reports/dev-gate/dev-runtime-postflight-report.json" - } - ], - "liveBefore": { - "status": "pass", - "expectedCommit": null, - "endpoints": [ - { - "id": "cloud-web-16666", - "url": "http://74.48.78.17:16666/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.593Z", - "finishedAt": "2026-05-24T00:07:30.157Z", - "expectedServiceId": "hwlab-cloud-web", - "expectedCommit": null, - "serviceId": "hwlab-cloud-web", - "environment": "dev", - "applicationStatus": "ok", - "ready": null, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageTag": "3df89fe", - "observedAt": null, - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - }, - { - "id": "cloud-api-16667", - "url": "http://74.48.78.17:16667/health/live", - "status": "pass", - "httpStatus": 200, - "reachable": true, - "identityMatches": true, - "startedAt": "2026-05-24T00:07:29.594Z", - "finishedAt": "2026-05-24T00:07:31.417Z", - "expectedServiceId": "hwlab-cloud-api", - "expectedCommit": null, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "applicationStatus": "ok", - "ready": true, - "observedCommit": "3df89fe", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:3df89fe", - "imageTag": "3df89fe", - "observedAt": "2026-05-24T00:07:30.656Z", - "serviceMatches": true, - "environmentMatches": true, - "commitMatches": null, - "blockerCodes": [] - } - ], - "summary": { - "checked": 2, - "reachable": 2, - "identityMatches": 2, - "commitMatches": null, - "ports": [ - 16666, - 16667 - ] - } - }, - "liveVerify": { - "status": "not_run", - "reason": "transaction blocked before live verify", - "endpoints": [], - "summary": { - "checked": 0 - } - }, - "preflight": { - "status": "pass", - "scope": "pre-lock-pre-side-effect", - "generatedAt": "2026-05-24T00:07:29.308Z", - "namespace": "hwlab-dev", - "kubeconfig": { - "required": "/etc/rancher/k3s/k3s.yaml", - "selected": "/etc/rancher/k3s/k3s.yaml", - "source": "flag:--kubeconfig", - "enforcedEnv": true - }, - "kubectl": { - "executor": "/usr/local/bin/kubectl", - "commandPrefix": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl" - }, - "controlPlane": { - "forced": { - "label": "forced-d601-k3s", - "contextCommand": { - "command": "kubectl config current-context", - "code": 0, - "ok": true, - "summary": "in-cluster" - }, - "serverCommand": { - "command": "kubectl config view --minify -o jsonpath={.clusters[0].cluster.server}", - "code": 0, - "ok": true, - "summary": "https://10.43.0.1:443" - }, - "nodesCommand": { - "command": "kubectl get nodes -o jsonpath={.items[*].metadata.name}", - "code": 0, - "ok": true, - "summary": "d601" - }, - "contextName": "in-cluster", - "serverUrl": "https://10.43.0.1:443", - "nodeNames": [ - "d601" - ], - "status": "observed" - }, - "bare": { - "label": "bare-kubectl", - "contextCommand": { - "command": "kubectl config current-context", - "code": 1, - "ok": false, - "summary": "error: current-context is not set" - }, - "serverCommand": { - "command": "kubectl config view --minify -o jsonpath={.clusters[0].cluster.server}", - "code": 1, - "ok": false, - "summary": "error: current-context must exist in order to minify" - }, - "nodesCommand": { - "command": "kubectl get nodes -o jsonpath={.items[*].metadata.name}", - "code": 0, - "ok": true, - "summary": "d601" - }, - "contextName": null, - "serverUrl": null, - "nodeNames": [ - "d601" - ], - "status": "unavailable", - "namespace": { - "command": "kubectl get namespace hwlab-dev -o name", - "code": 0, - "ok": true, - "summary": "namespace/hwlab-dev" - }, - "hwlabDevNamespaceObserved": true, - "secondControlPlaneCandidate": null - } - }, - "secretRefs": [ - { - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "status": "present", - "exists": true, - "keyPresent": true, - "keysObserved": [ - "database-url" - ], - "existenceCommand": "kubectl -n hwlab-dev get secret hwlab-cloud-api-dev-db -o name", - "keyCommand": "kubectl -n hwlab-dev describe secret hwlab-cloud-api-dev-db", - "secretValueRead": false, - "secretValuePrinted": false - }, - { - "secretName": "hwlab-cloud-api-dev-db-admin", - "secretKey": "admin-url", - "status": "present", - "exists": true, - "keyPresent": true, - "keysObserved": [ - "admin-url" - ], - "existenceCommand": "kubectl -n hwlab-dev get secret hwlab-cloud-api-dev-db-admin -o name", - "keyCommand": "kubectl -n hwlab-dev describe secret hwlab-cloud-api-dev-db-admin", - "secretValueRead": false, - "secretValuePrinted": false - }, - { - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "status": "present", - "exists": true, - "keyPresent": true, - "keysObserved": [ - "openai-api-key" - ], - "existenceCommand": "kubectl -n hwlab-dev get secret hwlab-code-agent-provider -o name", - "keyCommand": "kubectl -n hwlab-dev describe secret hwlab-code-agent-provider", - "secretValueRead": false, - "secretValuePrinted": false - } - ], - "safety": { - "writeSideEffectsAttempted": false, - "prodTouched": false, - "secretValuesRead": false, - "secretValuesPrinted": false, - "secretKeyNamesOnly": true - }, - "blockers": [] - }, - "reportPaths": { - "transaction": "reports/dev-gate/dev-cd-apply.json", - "artifacts": "reports/dev-gate/dev-artifacts.json", - "deployApply": "reports/dev-gate/dev-deploy-report.json", - "runtimeProvisioning": "reports/dev-gate/dev-runtime-provisioning-report.json", - "runtimeMigration": "reports/dev-gate/dev-runtime-migration-report.json", - "runtimePostflight": "reports/dev-gate/dev-runtime-postflight-report.json" - }, - "safety": { - "prodTouched": false, - "secretValuesRead": false, - "secretValuesPrinted": false, - "manualDbWrite": false, - "oldPublicPortsUsed": false, - "mutationAttempted": true, - "singleLock": "Lease/hwlab-dev/hwlab-dev-cd-lock" - } - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "runtime-durable-postflight", - "status": "open", - "reason": "M3 true/false durable postflight did not produce trusted green persisted evidence.", - "impact": "M3 durable readiness cannot be accepted because trusted evidence is not green.", - "safeNextAction": "Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "retryable": true, - "summary": "M3 true/false durable postflight did not produce trusted green persisted evidence. Impact: M3 durable readiness cannot be accepted because trusted evidence is not green. Safe next action: Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "evidence": { - "command": "/opt/node-v20.19.2-linux-x64/bin/node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report --fail-on-blocked", - "code": 1, - "reportPath": "reports/dev-gate/dev-runtime-postflight-report.json", - "blockerScope": "m3-durable-postflight", - "k8sJob": null - } - } - ], - "notes": "DEV-only CD transaction evidence. This does not claim M3 PASS or runtime durability." -} diff --git a/reports/dev-gate/dev-cloud-workbench-dom-only.json b/reports/dev-gate/dev-cloud-workbench-dom-only.json deleted file mode 100644 index 0dddc3b4..00000000 --- a/reports/dev-gate/dev-cloud-workbench-dom-only.json +++ /dev/null @@ -1,551 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-dom-only.json", - "reportVersion": "v1", - "status": "blocked", - "issue": "pikasTech/HWLAB#7", - "taskId": "dev-cloud-workbench-live", - "commitId": "6911138398e4", - "acceptanceLevel": "dev_cloud_workbench_live", - "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 deployed browser user journey report for the 16666 Cloud Workbench; it does not substitute for M3/M4/M5 hardware acceptance." - }, - "task": "DC-DCSN-P0-2026-003", - "refs": [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#108", - "pikasTech/HWLAB#99", - "pikasTech/HWLAB#78", - "pikasTech/HWLAB#117", - "pikasTech/HWLAB#118", - "pikasTech/HWLAB#119", - "pikasTech/HWLAB#120", - "pikasTech/HWLAB#131", - "pikasTech/HWLAB#143", - "pikasTech/HWLAB#164", - "pikasTech/HWLAB#224" - ], - "mode": "dom-only", - "url": "http://74.48.78.17:16666/", - "generatedAt": "2026-05-23T06:47:32.343Z", - "evidenceLevel": "BLOCKED", - "devLive": false, - "sourceIdentity": { - "status": "observed", - "source": "git-head", - "commitId": "6911138398e41485048193a6bf298a4b2e070b64", - "shortCommitId": "6911138398e4", - "ref": "test/d601-dom-only-feedback-validation", - "worktreeState": "clean", - "dirty": false, - "reportCommitId": "6911138398e4", - "summary": "Source identity was derived from the current worktree or source environment and is separate from the live runtime identity." - }, - "runtimeIdentity": { - "status": "observed", - "source": "health-live", - "endpoint": "http://74.48.78.17:16667/health/live", - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "healthStatus": "degraded", - "ready": false, - "commitId": "ffffced", - "commitSource": "runtime-env", - "imageTag": "ffffced", - "observedAt": "2026-05-23T06:47:14.607Z", - "runtime": { - "durable": false, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_auth_blocked", - "connection": { - "queryAttempted": true, - "queryResult": "auth_blocked" - } - }, - "readiness": { - "ready": false, - "status": "degraded", - "durability": { - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_auth_blocked", - "blockedLayer": "auth", - "queryResult": "auth_blocked" - } - }, - "blockerCodes": [ - "runtime_durable_adapter_auth_blocked" - ], - "summary": "Live runtime identity was observed through the existing read-only health endpoint and is not inferred from source git HEAD." - }, - "expectedRuntimeIdentity": { - "status": "observed", - "serviceId": "hwlab-cloud-api", - "source": "deploy/artifact-catalog.dev.json+deploy/deploy.json", - "commitId": "ffffced", - "imageTag": "ffffced", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:ffffced", - "summary": "Expected DEV runtime identity was derived from current source deploy desired state, not from live health." - }, - "deploymentIdentity": { - "status": "pass", - "expectedCommit": "ffffced", - "expectedReportCommitId": "6911138398e4", - "expectedImageTag": "ffffced", - "expectedSource": "deploy/artifact-catalog.dev.json+deploy/deploy.json", - "observedRuntimeCommit": "ffffced", - "observedImageTag": "ffffced", - "evidence": [ - "expected=ffffced", - "expectedImageTag=ffffced", - "runtimeCommit=ffffced", - "imageTag=ffffced" - ], - "summary": "Live API runtime identity matches the current source desired runtime identity." - }, - "webAssetIdentity": { - "status": "pass", - "assets": [ - { - "path": "index.html", - "status": "match", - "httpStatus": 200, - "contentType": "text/html; charset=utf-8", - "sourceHash": "47caa4cca88306c2", - "liveHash": "47caa4cca88306c2", - "sourceLength": 15062, - "liveLength": 15062 - }, - { - "path": "styles.css", - "status": "match", - "httpStatus": 200, - "contentType": "text/css; charset=utf-8", - "sourceHash": "f0649d37f7597e2a", - "liveHash": "f0649d37f7597e2a", - "sourceLength": 22493, - "liveLength": 22493 - }, - { - "path": "app.mjs", - "status": "match", - "httpStatus": 200, - "contentType": "text/javascript; charset=utf-8", - "sourceHash": "3deecca70ab81e80", - "liveHash": "3deecca70ab81e80", - "sourceLength": 78631, - "liveLength": 78631 - } - ], - "mismatches": [], - "summary": "Live 16666 primary web assets match the current clean source files." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/cloud-web-workbench.md", - "docs/reference/cloud-workbench.md", - "docs/reference/code-agent-chat-readiness.md" - ], - "summary": "The deployed browser journey is anchored to the Cloud Workbench and Code Agent readiness contracts." - }, - "validationCommands": [ - "node --check scripts/dev-cloud-workbench-smoke.mjs", - "node --check scripts/src/dev-cloud-workbench-smoke-lib.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source", - "node scripts/dev-cloud-workbench-smoke.mjs --mobile", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-timeout-fixture", - "node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json", - "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --source", - "node scripts/dev-cloud-workbench-smoke.mjs --mobile", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-timeout-fixture" - ], - "evidence": [ - "Source mode verifies the source contract and same-origin Code Agent wiring only.", - "Mobile browser mode verifies the 390x844 outer-scroll lock and help route at SOURCE level.", - "Local fixture browser mode verifies both Code Agent prompts past the old 4500ms window at SOURCE level.", - "Local timeout fixture verifies bounded timeout UI classification without live acceptance." - ], - "summary": "Source, mobile, and local fixture workbench smokes are SOURCE-level evidence and do not prove the deployed browser journey." - }, - "dryRun": { - "status": "not_applicable", - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --source" - ], - "evidence": [ - "No dry-run substitute is accepted for the deployed browser journey." - ], - "summary": "The journey evidence is collected from the real DEV browser endpoint, not a dry-run fixture." - }, - "devPreconditions": { - "status": "blocked", - "requirements": [ - "GET http://74.48.78.17:16666/ serves the Cloud Workbench HTML.", - "Browser DOM exposes the default workbench and core controls.", - "DOM-only mode may inspect read-only deployed DOM wiring after identity preflight blocks, but must not send Code Agent chat.", - "A controlled same-origin POST /v1/agent/chat returns completed UI-visible evidence or reports a blocker.", - "No hardware write API, service restart, PROD access, or secret read is performed." - ], - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json" - ], - "summary": "Deployed browser DOM was inspected read-only; Code Agent chat was not posted." - }, - "endpoints": { - "frontend": "http://74.48.78.17:16666", - "api": "http://74.48.78.17:16667", - "edge": "http://74.48.78.17:16667" - }, - "checks": [ - { - "id": "live-api-runtime-readiness", - "status": "degraded", - "summary": "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance.", - "evidence": [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "blocker=runtime_durable_adapter_auth_blocked" - ], - "observations": { - "status": "degraded", - "healthStatus": "degraded", - "ready": false, - "runtimeDurable": false, - "runtimeReady": false, - "durabilityReady": false, - "durableBlocked": false, - "blockerCodes": [ - "runtime_durable_adapter_auth_blocked" - ], - "evidence": [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "blocker=runtime_durable_adapter_auth_blocked" - ], - "summary": "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance." - } - }, - { - "id": "live-runtime-current-main", - "status": "pass", - "summary": "Live API runtime identity matches the current source desired runtime identity.", - "evidence": [ - "expected=ffffced", - "expectedImageTag=ffffced", - "runtimeCommit=ffffced", - "imageTag=ffffced" - ], - "observations": { - "status": "pass", - "expectedCommit": "ffffced", - "expectedReportCommitId": "6911138398e4", - "expectedImageTag": "ffffced", - "expectedSource": "deploy/artifact-catalog.dev.json+deploy/deploy.json", - "observedRuntimeCommit": "ffffced", - "observedImageTag": "ffffced", - "evidence": [ - "expected=ffffced", - "expectedImageTag=ffffced", - "runtimeCommit=ffffced", - "imageTag=ffffced" - ], - "summary": "Live API runtime identity matches the current source desired runtime identity." - } - }, - { - "id": "live-http-html", - "status": "pass", - "summary": "Live URL serves the Cloud Workbench HTML.", - "evidence": [ - "http://74.48.78.17:16666/", - "HTTP 200", - "text/html; charset=utf-8" - ] - }, - { - "id": "live-web-assets-current-main", - "status": "pass", - "summary": "Live 16666 primary web assets match the current clean source files.", - "evidence": [ - "index.html: match", - "styles.css: match", - "app.mjs: match" - ], - "observations": { - "status": "pass", - "assets": [ - { - "path": "index.html", - "status": "match", - "httpStatus": 200, - "contentType": "text/html; charset=utf-8", - "sourceHash": "47caa4cca88306c2", - "liveHash": "47caa4cca88306c2", - "sourceLength": 15062, - "liveLength": 15062 - }, - { - "path": "styles.css", - "status": "match", - "httpStatus": 200, - "contentType": "text/css; charset=utf-8", - "sourceHash": "f0649d37f7597e2a", - "liveHash": "f0649d37f7597e2a", - "sourceLength": 22493, - "liveLength": 22493 - }, - { - "path": "app.mjs", - "status": "match", - "httpStatus": 200, - "contentType": "text/javascript; charset=utf-8", - "sourceHash": "3deecca70ab81e80", - "liveHash": "3deecca70ab81e80", - "sourceLength": 78631, - "liveLength": 78631 - } - ], - "mismatches": [], - "summary": "Live 16666 primary web assets match the current clean source files." - } - }, - { - "id": "live-browser-dom", - "status": "blocked", - "summary": "Browser DOM did not satisfy the workbench contract.", - "evidence": [ - "title=HWLAB 云工作台", - "bodyOverflow=hidden", - "htmlOverflow=hidden", - "firstViewportForbidden=验收", - "gateRouteAvailable=true", - "m3WiringRows=1", - "codeAgentPostAttempts=0" - ], - "observations": { - "title": "HWLAB 云工作台", - "bodyOverflow": "hidden", - "htmlOverflow": "hidden", - "workspaceHidden": false, - "gateHidden": true, - "helpHidden": true, - "diagnosticsHidden": null, - "outerScrollLocked": true, - "rootAfterScrollAttempt": { - "windowScrollY": 0, - "htmlScrollTop": 0, - "bodyScrollTop": 0 - }, - "firstViewportForbiddenPresent": [ - "验收" - ], - "firstViewportTextSample": "工作台 内部复核 使用说明 资源树 HWLAB Cloud 云工作台 只读工作区 编写任务 查看接线 查看状态 硬件资源 8 prj_m3_hardware_loop 当前项目 gws_gwsimu_1 hwlab-gateway-simu / 已连接 gws_gwsimu_2 hwlab-gateway-simu / 已连接 res_boxsimu_1 simulator_endpoint / 可用 res_boxsimu_2 simulator_endpoint / 可用 pps_m3_hardware_loop hwlab-patch-panel / 待处理 agt_m5-0001 hwlab-agent-mgr / Agent 会话 wkr_m5-0001 hwlab-agent-worker / 完成 常用能力 3 来源 任务草稿 在底部输入区记录要交给 Agent 的任务,不会", - "labelsPresent": true, - "navLabelsPresent": true, - "defaultBackstageHidden": true, - "gateRouteAvailable": true, - "wiringHeaders": [ - "源设备", - "源端口", - "接线盘", - "目标设备", - "目标端口", - "状态", - "证据来源", - "trace/evidence" - ], - "m3WiringRows": [ - [ - "res_boxsimu_1", - "DO1", - "hwlab-patch-panel", - "res_boxsimu_2", - "DI1", - "待可信记录", - "来源拓扑", - "等待可信记录" - ] - ], - "coreControlsVisible": { - "commandInput": true, - "commandSend": true, - "agentChatStatus": true, - "conversationList": true, - "hardwareList": true, - "wiringTab": true, - "recordsTab": true - }, - "codeAgentPostGuard": { - "installed": true, - "blockedPath": "/v1/agent/chat", - "attemptedCount": 0, - "attempts": [] - } - } - }, - { - "id": "live-gate-route", - "status": "blocked", - "summary": "Direct /gate route was observed in an older DOM-only report, but the checked-in observations do not include the current single-table gate contract fields; kept BLOCKED instead of inferring DEV-LIVE gate readiness.", - "evidence": [ - "pathname=/gate", - "gateHidden=false", - "diagnosticsTermsPresent=true", - "codeAgentPostAttempts=0", - "single-table gate observations missing", - "kept blocked to avoid false green" - ], - "observations": { - "pathname": "/gate", - "hash": "", - "title": "HWLAB 云工作台", - "workspaceHidden": true, - "gateHidden": false, - "helpHidden": true, - "diagnosticsTermsPresent": true, - "codeAgentPostGuard": { - "installed": true, - "blockedPath": "/v1/agent/chat", - "attemptedCount": 0, - "attempts": [] - } - }, - "blocker": "observability_blocker" - }, - { - "id": "live-help-markdown-route", - "status": "pass", - "summary": "Desktop browser help route remains non-default, markdown-rendered, and scrollable.", - "evidence": [ - "hash=#help", - "heading=云工作台内部使用说明", - "rootStillLocked=true", - "helpPanelScrolls=true", - "codeAgentPostAttempts=0" - ], - "observations": { - "hash": "#help", - "pathname": "/", - "helpState": "ready", - "workspaceHidden": true, - "gateHidden": true, - "helpHidden": false, - "heading": "云工作台内部使用说明", - "renderedLists": 26, - "codeCount": 42, - "hasRawMarkdownHeading": false, - "termsPresent": true, - "helpPanelScrolls": true, - "rootStillLocked": true, - "helpScroll": { - "before": 0, - "after": 871, - "clientHeight": 495, - "scrollHeight": 1366, - "overflowY": "auto" - }, - "routeIsHashHelp": true, - "routeIsSlashHashHelp": false, - "routeIsDirectHelp": false, - "nonDefaultMarkdownHelp": true, - "codeAgentPostGuard": { - "installed": true, - "blockedPath": "/v1/agent/chat", - "attemptedCount": 0, - "attempts": [] - } - } - }, - { - "id": "live-code-agent-browser-journey", - "status": "not_applicable", - "summary": "DOM-only mode never posts the browser Code Agent journey; /v1/agent/chat is not sent.", - "evidence": [ - "POST /v1/agent/chat not_applicable", - "POST /v1/agent/chat not sent", - "DOM-only read-only mode" - ], - "observations": { - "request": { - "method": "POST", - "status": "not_applicable", - "urlPath": "/v1/agent/chat" - }, - "response": { - "status": "not_applicable", - "provider": "not_observed", - "model": "not_observed", - "backend": "not_observed", - "traceId": "not_observed", - "hasReply": false, - "error": null - }, - "classification": { - "status": "not_applicable", - "reason": "dom_only_no_code_agent_post", - "codeAgentBrowserJourneySkipped": true - }, - "networkEvents": [] - } - } - ], - "blockers": [ - { - "type": "observability_blocker", - "scope": "live-browser-dom", - "status": "open", - "summary": "Browser DOM did not satisfy the workbench contract." - }, - { - "type": "observability_blocker", - "scope": "live-gate-route", - "status": "open", - "summary": "Checked-in DOM-only /gate observations are missing current single-table contract fields; rerun DOM-only smoke after DEV rollout for fresh evidence." - } - ], - "safety": { - "prodTouched": false, - "servicesRestarted": false, - "heavyE2E": false, - "hardwareWriteApis": false, - "sourceIsDevLive": false, - "liveMode": "dom-only-readonly-no-code-agent-post", - "codeAgentBrowserJourneySkipped": true, - "codeAgentPostSent": false, - "retainedApiFields": [ - "runtime.commitId", - "runtime.imageTag", - "web asset hash/status", - "DOM route/control/wiring observations" - ], - "statement": "DOM-only live smoke preserves runtime and web-asset identity preflight semantics, continues with read-only browser DOM/help inspection even when identity is blocked, and never posts /v1/agent/chat, reads Secret values, mutates live DEV, or claims M3/M4/M5 acceptance." - }, - "artifacts": { - "reportPath": "/workspace/hwlab-d601-dom-only-feedback-validation/reports/dev-gate/dev-cloud-workbench-dom-only.json" - } -} diff --git a/reports/dev-gate/dev-cloud-workbench-live.json b/reports/dev-gate/dev-cloud-workbench-live.json deleted file mode 100644 index b136c95c..00000000 --- a/reports/dev-gate/dev-cloud-workbench-live.json +++ /dev/null @@ -1,316 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-live.json", - "reportVersion": "v1", - "status": "degraded", - "issue": "pikasTech/HWLAB#7", - "taskId": "dev-cloud-workbench-live", - "commitId": "unknown", - "acceptanceLevel": "dev_cloud_workbench_live", - "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 deployed browser user journey report for the 16666 Cloud Workbench; deployed UI usable in degraded/read-only mode and does not substitute for M3/M4/M5 DEV-LIVE acceptance." - }, - "task": "DC-DCSN-P0-2026-003", - "refs": [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#108", - "pikasTech/HWLAB#99", - "pikasTech/HWLAB#78", - "pikasTech/HWLAB#117", - "pikasTech/HWLAB#118", - "pikasTech/HWLAB#119", - "pikasTech/HWLAB#120", - "pikasTech/HWLAB#131", - "pikasTech/HWLAB#143", - "pikasTech/HWLAB#164", - "pikasTech/HWLAB#224" - ], - "mode": "live", - "url": "http://74.48.78.17:16666/", - "generatedAt": "2026-05-22T18:11:35.955Z", - "evidenceLevel": "DEV-LIVE-BROWSER-DEGRADED", - "devLive": false, - "sourceIdentity": { - "status": "observed", - "source": "git-head", - "commitId": "ee9d60f00b1bc47c1d99b7edb8746623333fe544", - "shortCommitId": "ee9d60f00b1b", - "ref": "test/deployed-workbench-journey-evidence", - "worktreeState": "dirty", - "dirty": true, - "reportCommitId": "unknown", - "summary": "Source HEAD was observed, but the worktree was not cleanly attributable when the report was generated; top-level commitId is unknown rather than pretending to identify uncommitted source." - }, - "runtimeIdentity": { - "status": "observed", - "source": "health-live", - "endpoint": "http://74.48.78.17:16667/health/live", - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "healthStatus": "degraded", - "ready": false, - "commitId": "c7de474", - "commitSource": "runtime-env", - "imageTag": "c7de474", - "observedAt": "2026-05-22T18:11:24.844Z", - "runtime": { - "durable": false, - "ready": false, - "status": "degraded", - "blocker": "runtime_durable_adapter_query_blocked", - "connection": { - "queryAttempted": true, - "queryResult": "query_blocked" - } - }, - "readiness": { - "ready": false, - "status": "degraded", - "durability": { - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_query_blocked", - "blockedLayer": "durability_query", - "queryResult": "query_blocked" - } - }, - "blockerCodes": [ - "runtime_durable_adapter_query_blocked" - ], - "summary": "Live runtime identity was observed through the existing read-only health endpoint and is not inferred from source git HEAD." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/cloud-web-workbench.md", - "docs/reference/cloud-workbench.md", - "docs/reference/code-agent-chat-readiness.md" - ], - "summary": "The deployed browser journey is anchored to the Cloud Workbench and Code Agent readiness contracts." - }, - "validationCommands": [ - "node --check scripts/dev-cloud-workbench-smoke.mjs", - "node --check scripts/src/dev-cloud-workbench-smoke-lib.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture", - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json", - "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-live.json", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --source", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture" - ], - "evidence": [ - "Source mode verifies the source contract and same-origin Code Agent wiring only.", - "Local fixture browser mode verifies both Code Agent prompts past the old 4500ms window at SOURCE level." - ], - "summary": "Source and local fixture workbench smokes are SOURCE-level evidence and do not prove the deployed browser journey." - }, - "dryRun": { - "status": "not_applicable", - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --source" - ], - "evidence": [ - "No dry-run substitute is accepted for the deployed browser journey." - ], - "summary": "The journey evidence is collected from the real DEV browser endpoint, not a dry-run fixture." - }, - "devPreconditions": { - "status": "degraded", - "requirements": [ - "GET http://74.48.78.17:16666/ serves the Cloud Workbench HTML.", - "Browser DOM exposes the default workbench and core controls.", - "Two controlled same-origin POST /v1/agent/chat prompts return completed UI-visible evidence or clear backend timeout/provider/browser blockers after the configured longer bound.", - "The frontend must not mark the heavy Code Agent request as a permanent failure around 4500ms.", - "No hardware write API, service restart, PROD access, or secret read is performed." - ], - "commands": [ - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json" - ], - "summary": "Deployed UI usable in degraded/read-only mode; API health/runtime durability is degraded, so this is not full DEV-LIVE acceptance." - }, - "endpoints": { - "frontend": "http://74.48.78.17:16666", - "api": "http://74.48.78.17:16667", - "edge": "http://74.48.78.17:16667" - }, - "checks": [ - { - "id": "live-http-html", - "status": "pass", - "summary": "Live URL serves the Cloud Workbench HTML.", - "evidence": [ - "http://74.48.78.17:16666/", - "HTTP 200", - "text/html; charset=utf-8" - ] - }, - { - "id": "live-browser-dom", - "status": "pass", - "summary": "Browser DOM confirms the default workbench, core controls, and outer scroll lock.", - "evidence": [ - "title=HWLAB 云工作台", - "bodyOverflow=hidden", - "htmlOverflow=hidden" - ], - "observations": { - "title": "HWLAB 云工作台", - "bodyOverflow": "hidden", - "htmlOverflow": "hidden", - "workspaceHidden": false, - "gateHidden": true, - "helpHidden": true, - "outerScrollLocked": true, - "labelsPresent": true, - "coreControlsVisible": { - "commandInput": true, - "commandSend": true, - "agentChatStatus": true, - "conversationList": true, - "hardwareList": true, - "wiringTab": true, - "recordsTab": true - } - } - }, - { - "id": "live-api-runtime-readiness", - "status": "degraded", - "summary": "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance.", - "evidence": [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "runtime_durable_adapter_query_blocked", - "blocker=runtime_durable_adapter_query_blocked" - ], - "observations": { - "status": "degraded", - "healthStatus": "degraded", - "ready": false, - "runtimeDurable": false, - "runtimeReady": false, - "durabilityReady": false, - "durableBlocked": true, - "blockerCodes": [ - "runtime_durable_adapter_query_blocked" - ], - "evidence": [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "runtime_durable_adapter_query_blocked", - "blocker=runtime_durable_adapter_query_blocked" - ], - "summary": "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance." - } - }, - { - "id": "live-code-agent-browser-journey", - "status": "pass", - "summary": "Deployed browser journey opened the workbench, sent a Code Agent message, and observed a completed UI response.", - "evidence": [ - "POST /v1/agent/chat HTTP 200", - "status=completed", - "provider=openai-responses", - "model=gpt-5.5", - "traceId=trc_cloud-web-workbench-1779473490624-11", - "uiStatus=已回复" - ], - "observations": { - "controls": { - "appShell": true, - "workspace": true, - "commandInput": true, - "commandSend": true, - "agentChatStatus": true, - "conversationList": true, - "hardwareList": true, - "wiringTab": true, - "recordsTab": true - }, - "request": { - "method": "POST", - "status": 200, - "urlPath": "/v1/agent/chat" - }, - "response": { - "status": "completed", - "provider": "openai-responses", - "model": "gpt-5.5", - "backend": "hwlab-cloud-api/openai-responses", - "traceId": "trc_cloud-web-workbench-1779473490624-11", - "hasReply": true, - "error": null - }, - "ui": { - "title": "HWLAB 云工作台", - "workspaceHidden": false, - "gateHidden": true, - "helpHidden": true, - "agentChatStatus": "已回复", - "inputCleared": true, - "completedMessageVisible": true, - "failedMessageVisible": false, - "messageCount": 4, - "completedMessageHasNonSensitiveMeta": true - }, - "networkEvents": [ - { - "method": "POST", - "status": 200, - "urlPath": "/v1/agent/chat", - "body": { - "status": "completed", - "provider": "openai-responses", - "model": "gpt-5.5", - "backend": "hwlab-cloud-api/openai-responses", - "traceId": "trc_cloud-web-workbench-1779473490624-11", - "hasReply": true, - "error": null - } - } - ] - } - } - ], - "blockers": [], - "safety": { - "prodTouched": false, - "servicesRestarted": false, - "heavyE2E": false, - "hardwareWriteApis": false, - "sourceIsDevLive": false, - "liveMode": "browser-user-journey-with-code-agent-post", - "retainedApiFields": [ - "status", - "provider", - "model", - "backend", - "traceId", - "hasReply", - "error.code", - "error.missingEnv" - ], - "statement": "Live smoke records deployed UI usable in degraded/read-only mode only; API health/runtime durability is degraded, so this is not full DEV-LIVE acceptance and must not be used to infer M3/M4/M5 acceptance." - }, - "artifacts": { - "reportPath": "/workspace/hwlab/.worktrees/deployed-workbench-journey-evidence/reports/dev-gate/dev-cloud-workbench-live.json" - } -} diff --git a/reports/dev-gate/dev-deploy-report.json b/reports/dev-gate/dev-deploy-report.json deleted file mode 100644 index d4ededea..00000000 --- a/reports/dev-gate/dev-deploy-report.json +++ /dev/null @@ -1,1799 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-deploy-report.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#33", - "supports": [ - "pikasTech/HWLAB#164", - "pikasTech/HWLAB#143", - "pikasTech/HWLAB#99", - "pikasTech/HWLAB#63", - "pikasTech/HWLAB#50", - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#17", - "pikasTech/HWLAB#32", - "pikasTech/HWLAB#30" - ], - "taskId": "dev-deploy-apply", - "commitId": "48dfbf9", - "acceptanceLevel": "dev_deploy_apply", - "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 DEV deploy apply report; active public API/health endpoint is 16667 and browser endpoint is 16666." - }, - "status": "pass", - "generatedAt": "2026-05-24T00:18:29.328Z", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/dev-deploy-apply.md", - "docs/artifact-catalog.md", - "deploy/README.md" - ], - "summary": "DEV deploy apply is pinned to hwlab-dev and the frozen HWLAB service IDs" - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-deploy-apply.mjs", - "node --check scripts/src/dev-deploy-apply.mjs", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - "localSmoke": { - "status": "not_run", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "This deploy apply task did not run local smoke because artifact/executor blockers stop promotion first." - ], - "summary": "Local smoke remains available but is not a substitute for DEV artifact publish evidence." - }, - "dryRun": { - "status": "pass", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - "evidence": [ - "artifact services checked: 13", - "expected artifact commit: 48dfbf9", - "namespace: hwlab-dev", - "workloads planned: 13", - "kubectl executor: /usr/local/bin/kubectl", - "kubectl kubeconfig source: flag:--kubeconfig", - "kubectl kubeconfig: /etc/rancher/k3s/k3s.yaml", - "live health: pass", - "code agent provider desired-state: pass", - "code agent provider live env before apply: pass", - "template job replacements: 2", - "legacy simulator deployment cleanups: 0" - ], - "summary": "DEV dry-run preflight passed." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "DEV artifact catalog must contain CI publish evidence and registry digests", - "kubectl must be available for D601 hwlab-dev and must not target PROD", - "hwlab-cloud-api /health/live must report serviceId, dev environment, and DB env readiness without exposing secret values", - "hwlab-cloud-api must declare and preserve OPENAI_API_KEY from hwlab-code-agent-provider/openai-api-key plus the DEV Code Agent egress proxy env without reading Secret values", - "pods, services, configmaps, and image commit tags must be observable in hwlab-dev" - ], - "summary": "DEV deploy preconditions are satisfied." - }, - "devDeployApply": { - "mode": "apply", - "conclusion": { - "status": "ready", - "reason": "All DEV-only preconditions passed; human approval is still required before apply.", - "blockerCount": 0 - }, - "applyBoundary": { - "currentMode": "apply-requested", - "defaultNoWrite": false, - "mutationAttempted": true, - "mutationAllowed": true, - "applyRequiresFlags": [ - "--apply", - "--confirm-dev", - "--confirmed-non-production" - ], - "kubeconfigSource": "flag:--kubeconfig", - "writeScope": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -k deploy/k8s/dev, namespace hwlab-dev only; delete/recreate is allowed only for explicitly allowlisted suspended DEV template Jobs and stale legacy simulator Deployments", - "noWriteScope": "source/catalog/k8s validation, optional DEV health probe, KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl read, replacement planning, and server-side dry-run only", - "forbiddenActions": [ - "prod-deploy", - "secret-read", - "runtime-substitute", - "unidesk-restart", - "code-queue-restart", - "backend-core-restart", - "heavy-master-e2e", - "force-push" - ] - }, - "mutationAttempted": true, - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "prodDisabled": true - }, - "artifactPlan": { - "expectedArtifactCommit": "48dfbf9", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "sourceCommitId": "48dfbf9", - "serviceCommitIds": [ - "48dfbf9" - ], - "matchesSourceCommit": true, - "published": true, - "registryVerified": true, - "imageCount": 13, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "unpublishedServices": [] - }, - "workloadPlan": [ - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-cloud-api" - ], - "containers": [ - { - "name": "hwlab-cloud-api", - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-cloud-web" - ], - "containers": [ - { - "name": "hwlab-cloud-web", - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-agent-mgr" - ], - "containers": [ - { - "name": "hwlab-agent-mgr", - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9" - } - ] - }, - { - "kind": "Job", - "name": "hwlab-agent-worker-template", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-agent-worker" - ], - "containers": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-gateway" - ], - "containers": [ - { - "name": "hwlab-gateway", - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9" - } - ] - }, - { - "kind": "StatefulSet", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "replicas": 2, - "serviceIds": [ - "hwlab-gateway-simu" - ], - "containers": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9" - } - ] - }, - { - "kind": "StatefulSet", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "replicas": 2, - "serviceIds": [ - "hwlab-box-simu" - ], - "containers": [ - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-patch-panel" - ], - "containers": [ - { - "name": "hwlab-patch-panel", - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-router" - ], - "containers": [ - { - "name": "hwlab-router", - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-tunnel-client" - ], - "containers": [ - { - "name": "hwlab-tunnel-client", - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-edge-proxy" - ], - "containers": [ - { - "name": "hwlab-edge-proxy", - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9" - } - ] - }, - { - "kind": "Job", - "name": "hwlab-cli-template", - "namespace": "hwlab-dev", - "replicas": 0, - "serviceIds": [ - "hwlab-cli" - ], - "containers": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9" - } - ] - }, - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "replicas": 1, - "serviceIds": [ - "hwlab-agent-skills" - ], - "containers": [ - { - "name": "hwlab-agent-skills", - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9" - } - ] - } - ], - "servicePlan": [ - { - "kind": "Service", - "name": "hwlab-cloud-api", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-1", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-2", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-web", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 8080, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu-1", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu-2", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7001, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-cli", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cli", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7501, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-agent-skills", - "namespace": "hwlab-dev", - "serviceId": "hwlab-agent-skills", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7430, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-agent-mgr", - "namespace": "hwlab-dev", - "serviceId": "hwlab-agent-mgr", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7410, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu", - "namespace": "hwlab-dev", - "serviceId": "hwlab-gateway-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7101, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-box-simu", - "namespace": "hwlab-dev", - "serviceId": "hwlab-box-simu", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7201, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-patch-panel", - "namespace": "hwlab-dev", - "serviceId": "hwlab-patch-panel", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7301, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-router", - "namespace": "hwlab-dev", - "serviceId": "hwlab-router", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7401, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-tunnel-client", - "namespace": "hwlab-dev", - "serviceId": "hwlab-tunnel-client", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 7402, - "targetPort": "http" - } - ] - }, - { - "kind": "Service", - "name": "hwlab-edge-proxy", - "namespace": "hwlab-dev", - "serviceId": "hwlab-edge-proxy", - "type": "ClusterIP", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ] - } - ], - "templateJobReplacementPolicy": { - "status": "active", - "namespace": "hwlab-dev", - "scope": "DEV-only suspended template Jobs", - "allowedJobs": [ - { - "name": "hwlab-agent-worker-template", - "serviceId": "hwlab-agent-worker", - "reason": "suspended session worker template Job has immutable spec.template" - }, - { - "name": "hwlab-cli-template", - "serviceId": "hwlab-cli", - "reason": "suspended CLI template Job has immutable spec.template" - } - ] - }, - "templateJobReplacements": [ - { - "namespace": "hwlab-dev", - "jobName": "hwlab-agent-worker-template", - "allowed": true, - "desiredSuspended": true, - "liveExists": true, - "liveSuspended": true, - "oldImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:3df89fe", - "newImage": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "oldImages": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:3df89fe" - } - ], - "newImages": [ - { - "name": "hwlab-agent-worker", - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9" - } - ], - "replace": true, - "result": "replaced", - "reason": "Live suspended template Job was deleted and recreated by kubectl apply.", - "deleteCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-agent-worker-template --ignore-not-found=true", - "deleteStdout": "job.batch \"hwlab-agent-worker-template\" deleted from hwlab-dev namespace", - "deleteStderr": "" - }, - { - "namespace": "hwlab-dev", - "jobName": "hwlab-cli-template", - "allowed": true, - "desiredSuspended": true, - "liveExists": true, - "liveSuspended": true, - "oldImage": "127.0.0.1:5000/hwlab/hwlab-cli:3df89fe", - "newImage": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "oldImages": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:3df89fe" - } - ], - "newImages": [ - { - "name": "hwlab-cli", - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9" - } - ], - "replace": true, - "result": "replaced", - "reason": "Live suspended template Job was deleted and recreated by kubectl apply.", - "deleteCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-cli-template --ignore-not-found=true", - "deleteStdout": "job.batch \"hwlab-cli-template\" deleted from hwlab-dev namespace", - "deleteStderr": "" - } - ], - "legacySimulatorDeploymentCleanupPolicy": { - "status": "active", - "namespace": "hwlab-dev", - "scope": "DEV-only stale simulator Deployments replaced by indexed StatefulSets", - "allowedDeployments": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "replacementKind": "StatefulSet", - "reason": "M3 DEV requires exactly two indexed gateway-simu identities/sessions" - }, - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "replacementKind": "StatefulSet", - "reason": "M3 DEV requires exactly two indexed box-simu resource identities" - } - ] - }, - "legacySimulatorDeploymentCleanups": [ - { - "namespace": "hwlab-dev", - "deploymentName": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "allowed": true, - "desiredReplacementExists": true, - "desiredReplacementKind": "StatefulSet", - "liveExists": false, - "oldImage": null, - "newImage": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "oldImages": [], - "newImages": [ - { - "name": "hwlab-gateway-simu", - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9" - } - ], - "cleanup": false, - "result": "not_found", - "reason": "Live legacy Deployment is absent; no cleanup is needed." - }, - { - "namespace": "hwlab-dev", - "deploymentName": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "allowed": true, - "desiredReplacementExists": true, - "desiredReplacementKind": "StatefulSet", - "liveExists": false, - "oldImage": null, - "newImage": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "oldImages": [], - "newImages": [ - { - "name": "hwlab-box-simu", - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9" - } - ], - "cleanup": false, - "result": "not_found", - "reason": "Live legacy Deployment is absent; no cleanup is needed." - } - ], - "cloudWebRollout": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "after_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "44", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageMatchesDesired": true, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - }, - "runtimeEnvMatchesDesired": true, - "runtimeEnvDrift": [], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "cloudWebRolloutBeforeApply": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "before_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "43", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "imageMatchesDesired": false, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "drift", - "matchesDesired": false, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ], - "drift": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ] - }, - "runtimeEnvMatchesDesired": false, - "runtimeEnvDrift": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:3df89fe", - "status": "drift" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "3df89fe", - "status": "drift" - } - ], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "cloudWebRolloutAfterApply": { - "serviceId": "hwlab-cloud-web", - "namespace": "hwlab-dev", - "status": "observed", - "observationPhase": "after_apply", - "sourceCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageTag": "48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "repositoryDigest": null, - "publishState": "published", - "rolloutRevision": "44", - "liveImage": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "imageMatchesDesired": true, - "expectedRuntimeEnv": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "liveRuntimeEnv": { - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - }, - "runtimeEnvMatchesDesired": true, - "runtimeEnvDrift": [], - "verificationCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-web --timeout=180s", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-web -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig" - }, - "codeAgentProvider": { - "desiredState": { - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "status": "pass", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "deployPresent": true, - "workloadPresent": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "deployPresent": true, - "workloadPresent": true, - "redacted": true, - "source": "runtime-env" - } - ], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "deployPlaceholderPresent": true, - "workloadSecretRefPresent": true, - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "deployPresent": true, - "workloadPresent": true, - "deployMatchesDevProxy": true, - "workloadMatchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "missingDeployEnv": [], - "missingWorkloadEnv": [], - "deployMismatches": [], - "workloadMismatches": [], - "missingSecretRefs": [], - "missingEgressContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "providerConnected": false, - "fixtureEvidence": false - }, - "liveBeforeApply": { - "phase": "before_apply", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "status": "pass", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-api -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "expectedSecretRef": "hwlab-code-agent-provider/openai-api-key", - "expectedEgressTarget": "dev-egress-proxy", - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true, - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "present": true, - "redacted": true, - "source": "runtime-env" - } - ], - "missingEnv": [], - "mismatchedEnv": [], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "present": true, - "matchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "secretMaterialRead": false, - "providerConnected": false, - "deployment": "hwlab-cloud-api", - "container": "hwlab-cloud-api", - "validationScope": "deployment-env-and-secretKeyRef-metadata-only" - }, - "liveAfterApply": { - "phase": "after_apply", - "namespace": "hwlab-dev", - "serviceId": "hwlab-cloud-api", - "status": "pass", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployment hwlab-cloud-api -o json", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "expectedSecretRef": "hwlab-code-agent-provider/openai-api-key", - "expectedEgressTarget": "dev-egress-proxy", - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true, - "contractVersion": "v1", - "environment": "dev", - "provider": "codex-stdio", - "runtimeProvider": "openai-responses", - "backend": "hwlab-cloud-api/codex-mcp-stdio", - "model": "gpt-5.5", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CODE_AGENT_PROVIDER", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "HWLAB_CODE_AGENT_MODEL", - "present": true, - "redacted": false, - "source": "runtime-env" - }, - { - "name": "OPENAI_API_KEY", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "present": true, - "redacted": true, - "source": "runtime-env" - } - ], - "missingEnv": [], - "mismatchedEnv": [], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "present": true, - "redacted": true - }, - "egress": { - "env": "HWLAB_CODE_AGENT_OPENAI_BASE_URL", - "target": "dev-egress-proxy", - "present": true, - "matchesDevProxy": true, - "directPublicOpenAi": false, - "valueRedacted": true - }, - "secretMaterialRead": false, - "providerConnected": false, - "deployment": "hwlab-cloud-api", - "container": "hwlab-cloud-api", - "validationScope": "deployment-env-and-secretKeyRef-metadata-only" - }, - "validationGate": "no-secret provider env/secretKeyRef preservation", - "providerCallAttempted": false, - "secretValuesRead": false, - "kubernetesSecretDataRead": false, - "valuesRedacted": true - }, - "applyStep": { - "status": "pass", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -k deploy/k8s/dev", - "stdout": "namespace/hwlab-dev unchanged\nconfigmap/hwlab-dev-health-contract unchanged\nservice/hwlab-agent-mgr unchanged\nservice/hwlab-agent-skills unchanged\nservice/hwlab-box-simu unchanged\nservice/hwlab-box-simu-1 unchanged\nservice/hwlab-box-simu-2 unchanged\nservice/hwlab-cli unchanged\nservice/hwlab-cloud-api unchanged\nservice/hwlab-cloud-web unchanged\nservice/hwlab-edge-proxy unchanged\nservice/hwlab-gateway unchanged\nservice/hwlab-gateway-simu unchanged\nservice/hwlab-gateway-simu-1 unchanged\nservice/hwlab-gateway-simu-2 unchanged\nservice/hwlab-patch-panel unchanged\nservice/hwlab-router unchanged\nservice/hwlab-tunnel-client unchanged\ndeployment.apps/hwlab-agent-mgr configured\ndeployment.apps/hwlab-agent-skills configured\ndeployment.apps/hwlab-cloud-api configured\ndeployment.apps/hwlab-cloud-web configured\ndeployment.apps/hwlab-edge-proxy configured\ndeployment.apps/hwlab-gateway configured\ndeployment.apps/hwlab-patch-panel configured\ndeployment.apps/hwlab-router configured\ndeployment.apps/hwlab-tunnel-client configured\nstatefulset.apps/hwlab-box-simu configured\nstatefulset.apps/hwlab-gateway-simu configured\njob.batch/hwlab-agent-worker-template created\njob.batch/hwlab-cli-template created", - "stderr": "# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically.\n# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically.", - "mutationAttempted": true, - "expectedImmutableTemplateJobDryRun": false, - "expectedImmutableTemplateJobs": [] - }, - "manualCommands": { - "status": "ready", - "beforeHumanApproval": [ - "node scripts/dev-deploy-apply.mjs --dry-run --write-report", - "node scripts/validate-dev-gate-report.mjs" - ], - "afterHumanApproval": [ - "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report" - ], - "summary": "Run the dry-run plan and validator immediately before requesting human approval; live apply still requires the explicit DEV confirmation flags." - }, - "rollbackHint": { - "namespace": "hwlab-dev", - "strategy": "DEV-only Kubernetes rollback using deployment revision history; do not touch PROD or UniDesk services.", - "captureBeforeApply": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods,services,configmaps,deployments,jobs -o wide", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deployments -o json" - ], - "deploymentRollbackCommands": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-cloud-api", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-cloud-web", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-agent-mgr", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-gateway", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-patch-panel", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-router", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-tunnel-client", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-edge-proxy", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout undo deployment/hwlab-agent-skills" - ], - "jobCleanupCommands": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-agent-worker-template --ignore-not-found", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev delete job hwlab-cli-template --ignore-not-found" - ], - "postRollbackChecks": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev rollout status deployment/hwlab-cloud-api --timeout=120s", - "curl -fsS http://74.48.78.17:16667/health/live" - ] - }, - "remainingBlockers": [], - "artifactEvidence": [ - { - "serviceId": "hwlab-cloud-api", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "digest": "sha256:15a9dec43d0ad6b750ea4eda860c1c157c6502441732c1de20ef3b92696d0dfc", - "publishState": "published" - }, - { - "serviceId": "hwlab-cloud-web", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "digest": "sha256:fc1841a6e3e8d427d23b5a0187abbe9d73208e3ad2c638e31b9512aaf075699f", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-mgr", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "digest": "sha256:bdb3dffd0a82e7cf1c48de3d130cbd6e733c242dd5667be4ba098a46888a13d6", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-worker", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "digest": "sha256:902590cd05c9af8dcf347b74c05e1fb9fb399eefa54d91ff105dd2fa276ae785", - "publishState": "published" - }, - { - "serviceId": "hwlab-gateway", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "digest": "sha256:015916d05ccbd8448e3f32d754335f7c221aaaadf5ae2b63007d7698f7583d04", - "publishState": "published" - }, - { - "serviceId": "hwlab-gateway-simu", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "digest": "sha256:74793df73dfeb27fc45b1085ac1c29839f6545ccfea79d033dfddc89d6f3a553", - "publishState": "published" - }, - { - "serviceId": "hwlab-box-simu", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "digest": "sha256:89d80def71130db7e0f4d9a0ec1dadfcb5d8963bd1f13b71c67eab0a301efb94", - "publishState": "published" - }, - { - "serviceId": "hwlab-patch-panel", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "digest": "sha256:160a47204b2263e750cbf412f0d266047eeb6baf8b2be839e5d1987fc6082fea", - "publishState": "published" - }, - { - "serviceId": "hwlab-router", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "digest": "sha256:0ab02340575ec7e910427f8ea06db2b8f8095922e207ae5e11a64c065fa26df7", - "publishState": "published" - }, - { - "serviceId": "hwlab-tunnel-client", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "digest": "sha256:75af6caa96ec03d3f4130e512052f666d75b1e5470fa67f8f66f355e1068e43f", - "publishState": "published" - }, - { - "serviceId": "hwlab-edge-proxy", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "digest": "sha256:f8b954ad76ab0162e3df0edc26c115f6c697f8f5c4e9d93e24112285b57b9b05", - "publishState": "published" - }, - { - "serviceId": "hwlab-cli", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "digest": "sha256:ae6564b6b0f8ceaf35ab5d2e7f7c04ca8f5e51aa7daedb01e4a6aa31e4bcc16b", - "publishState": "published" - }, - { - "serviceId": "hwlab-agent-skills", - "deployCommitId": "48dfbf9", - "catalogCommitId": "48dfbf9", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "digest": "sha256:6fc9b4fda9bea32607774a24d72ecc7999007d910d17e11764abce2bdc35c03d", - "publishState": "published" - } - ], - "cloudApiDb": { - "status": "contract-ready", - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "manifestPresent": true, - "k8sPresent": true, - "redacted": true, - "source": "k8s-secret-ref", - "secretRef": { - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "redacted": true - } - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "manifestPresent": true, - "k8sPresent": true, - "redacted": false, - "source": "runtime-env", - "secretRef": null - } - ], - "missingManifest": [], - "missingK8s": [], - "missingSecretRefs": [], - "mismatchedNonSecretEnv": [], - "missingDnsContract": [], - "dns": { - "ready": true, - "source": "optional-public-dns-alias", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "requiredForReadiness": false, - "usedForProbe": false, - "servicePresent": false, - "secretMaterialRead": false, - "liveDbEvidence": false, - "missing": [], - "actual": {} - }, - "runtimeHealth": { - "status": "degraded", - "ready": false, - "configReady": true, - "connected": false, - "liveConnected": false, - "connectionChecked": false, - "connectionAttempted": false, - "connectionResult": "unknown", - "liveDbEvidence": false, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false - } - ], - "missingEnv": [], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "envInjected": true, - "secretPresent": "not_observed", - "secretKeyPresent": "not_observed", - "redacted": true - } - ], - "endpointSource": "secret-url-host", - "optionalPublicDnsAlias": { - "source": "optional-public-dns-alias", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "requiredForReadiness": false, - "usedForProbe": false, - "envPresent": false, - "readyGate": "not_readiness_authority" - }, - "connection": { - "attempted": false, - "result": "unknown", - "endpointRedacted": true, - "valueRedacted": true - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "readinessLayers": { - "dns": { - "status": "not_proven", - "result": "unknown" - }, - "tcp": { - "status": "not_proven", - "result": "unknown" - }, - "ssl": { - "status": "not_proven", - "result": "unknown" - }, - "auth": { - "status": "not_proven", - "result": "unknown" - }, - "schema": { - "status": "not_proven", - "result": "unknown" - }, - "migration": { - "status": "not_proven", - "result": "unknown" - }, - "durability": { - "status": "not_proven", - "result": "unknown" - } - }, - "blocker": null, - "fixtureEvidence": false - }, - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false, - "note": "This check records DB env, Secret reference, and Secret URL host authority only. Optional cloud-api-db alias presence is diagnostic and is not live DB evidence." - }, - "k8sManifest": { - "resourceCounts": { - "workloads": 13, - "services": 16, - "configMaps": 1 - }, - "images": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:48dfbf9", - "kind": "Deployment", - "name": "hwlab-cloud-api" - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "kind": "Deployment", - "name": "hwlab-cloud-web" - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:48dfbf9", - "kind": "Deployment", - "name": "hwlab-agent-mgr" - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:48dfbf9", - "kind": "Job", - "name": "hwlab-agent-worker-template" - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9", - "kind": "Deployment", - "name": "hwlab-gateway" - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:48dfbf9", - "kind": "StatefulSet", - "name": "hwlab-gateway-simu" - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:48dfbf9", - "kind": "StatefulSet", - "name": "hwlab-box-simu" - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:48dfbf9", - "kind": "Deployment", - "name": "hwlab-patch-panel" - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:48dfbf9", - "kind": "Deployment", - "name": "hwlab-router" - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:48dfbf9", - "kind": "Deployment", - "name": "hwlab-tunnel-client" - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:48dfbf9", - "kind": "Deployment", - "name": "hwlab-edge-proxy" - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:48dfbf9", - "kind": "Job", - "name": "hwlab-cli-template" - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:48dfbf9", - "kind": "Deployment", - "name": "hwlab-agent-skills" - } - ], - "runtimeIdentityEnv": [ - { - "serviceId": "hwlab-cloud-web", - "kind": "Deployment", - "name": "hwlab-cloud-web", - "container": "hwlab-cloud-web", - "expected": { - "HWLAB_COMMIT_ID": "48dfbf9", - "HWLAB_IMAGE": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "HWLAB_IMAGE_TAG": "48dfbf9" - }, - "status": "match", - "matchesDesired": true, - "fields": [ - { - "name": "HWLAB_COMMIT_ID", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE", - "expected": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "actual": "127.0.0.1:5000/hwlab/hwlab-cloud-web:48dfbf9", - "status": "match" - }, - { - "name": "HWLAB_IMAGE_TAG", - "expected": "48dfbf9", - "actual": "48dfbf9", - "status": "match" - } - ], - "drift": [] - } - ] - }, - "clusterObservation": { - "status": "pass", - "executor": "/usr/local/bin/kubectl", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "kubeconfigSource": "flag:--kubeconfig", - "commandPrefix": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl", - "readCommand": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get pods,services,configmaps,deployments,jobs -n hwlab-dev -o json", - "version": "clientVersion:\n buildDate: \"2026-05-12T09:54:35Z\"\n compiler: gc\n gitCommit: 756939600b9a7180fc2df6550a4585b638875e67\n gitTreeState: clean\n gitVersion: v1.36.1\n goVersion: go1.26.2\n major: \"1\"\n minor: \"36\"\n platform: linux/amd64\nkustomizeVersion: v5.8.1", - "resources": [ - { - "kind": "Pod", - "name": "hwlab-agent-mgr-6f4cc98885-6bl9w", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-agent-skills-69485c4dc4-mpgdb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-box-simu-0", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-box-simu-1", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-cloud-api-54cdcb57dc-xw9gb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-cloud-web-5bd7bf9c6-d67jc", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-edge-proxy-58984dcc7c-8bvqj", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-frpc-59f57d479c-tgjhl", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-gateway-simu-0", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-gateway-simu-1", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-patch-panel-55b9c584d7-7rzqb", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-router-76fd584bdb-t26cs", - "phase": "Running" - }, - { - "kind": "Pod", - "name": "hwlab-runtime-migrate-5332b2f8-mlg9p", - "phase": "Succeeded" - }, - { - "kind": "Pod", - "name": "hwlab-runtime-provision-5332b2f8-qdpq7", - "phase": "Succeeded" - }, - { - "kind": "Pod", - "name": "hwlab-tunnel-client-69d7d6d6d8-pv8b9", - "phase": "Running" - }, - { - "kind": "Service", - "name": "hwlab-agent-mgr" - }, - { - "kind": "Service", - "name": "hwlab-agent-skills" - }, - { - "kind": "Service", - "name": "hwlab-box-simu" - }, - { - "kind": "Service", - "name": "hwlab-box-simu-1" - }, - { - "kind": "Service", - "name": "hwlab-box-simu-2" - }, - { - "kind": "Service", - "name": "hwlab-cli" - }, - { - "kind": "Service", - "name": "hwlab-cloud-api" - }, - { - "kind": "Service", - "name": "hwlab-cloud-web" - }, - { - "kind": "Service", - "name": "hwlab-edge-proxy" - }, - { - "kind": "Service", - "name": "hwlab-gateway" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-1" - }, - { - "kind": "Service", - "name": "hwlab-gateway-simu-2" - }, - { - "kind": "Service", - "name": "hwlab-patch-panel" - }, - { - "kind": "Service", - "name": "hwlab-router" - }, - { - "kind": "Service", - "name": "hwlab-tunnel-client" - }, - { - "kind": "ConfigMap", - "name": "hwlab-dev-health-contract" - }, - { - "kind": "ConfigMap", - "name": "hwlab-frpc-config" - }, - { - "kind": "ConfigMap", - "name": "kube-root-ca.crt" - }, - { - "kind": "Deployment", - "name": "hwlab-agent-mgr", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-agent-skills", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-api", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-cloud-web", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-edge-proxy", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-frpc", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-gateway" - }, - { - "kind": "Deployment", - "name": "hwlab-patch-panel", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-router", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Deployment", - "name": "hwlab-tunnel-client", - "readyReplicas": 1, - "replicas": 1 - }, - { - "kind": "Job", - "name": "hwlab-agent-worker-template" - }, - { - "kind": "Job", - "name": "hwlab-cli-template" - }, - { - "kind": "Job", - "name": "hwlab-runtime-migrate-5332b2f8" - }, - { - "kind": "Job", - "name": "hwlab-runtime-provision-5332b2f8" - } - ] - }, - "liveProbe": { - "status": "pass", - "url": "http://74.48.78.17:16667/health/live", - "httpStatus": 200, - "body": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"ok\",\"ready\":true,\"revision\":\"3df89fe15100f3ef3c2e12e1a001b5370d54eff9\",\"readiness\":{\"contractVersion\":\"v3\",\"ready\":true,\"status\":\"ok\",\"blockerCodes\":[],\"blockers\":[],\"components\":{\"db\":\"ready\",\"codeAgent\":\"ready\",\"runtime\":\"ready\",\"provider\":\"ready\",\"dbDurable\":\"ready\",\"sessionRunner\":\"codex_stdio_ready\",\"codexStdio\":\"ready\"},\"provider\":{\"status\":\"ready\",\"ready\":true,\"configured\":true,\"provider\":\"codex-stdio\",\"model\":\"gpt-5.5\",\"backend" - } - }, - "blockers": [], - "notes": "DEV-only result. No PROD resources, secret values, force push, heavy e2e, browser e2e, or UniDesk runtime substitute were used." -} diff --git a/reports/dev-gate/dev-edge-health.json b/reports/dev-gate/dev-edge-health.json deleted file mode 100644 index 3001da5f..00000000 --- a/reports/dev-gate/dev-edge-health.json +++ /dev/null @@ -1,978 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-edge-health.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#36", - "taskId": "dev-edge-health", - "commitId": "c7de4745f491", - "acceptanceLevel": "dev_edge_health", - "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 DEV edge-health report; deprecated public 6666/6667 endpoints are not valid active evidence." - }, - "status": "blocked", - "generatedAt": "2026-05-22T13:45:42.982Z", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:16667", - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/dev-edge-health.md", - "deploy/frp/README.md", - "deploy/master-edge/README.md" - ], - "summary": "DEV edge health is pinned to the frozen HWLAB endpoint, frp reverse link, master edge proxy, and hwlab-dev services." - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-edge-health-smoke.mjs", - "node --check scripts/src/dev-edge-health-smoke-lib.mjs", - "node --test scripts/src/dev-edge-health-smoke-lib.test.mjs", - "node scripts/dev-edge-health-smoke.mjs --live --write-report" - ], - "localSmoke": { - "status": "pass", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "npm run check includes internal/cloud/server.test.mjs and validates /health plus /health/live." - ], - "summary": "Local cloud-api health checks pass and include service, commit, and image evidence fields." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - ], - "evidence": [ - "This edge-health task used the dedicated read-only smoke instead of the MVP e2e dry-run." - ], - "summary": "MVP dry-run remains the generic gate command; edge health uses a narrower route smoke." - }, - "devPreconditions": { - "status": "blocked", - "requirements": [ - "http://74.48.78.17:16667/health returns HWLAB DEV JSON with service, commit, and image evidence", - "frps control on 74.48.78.17:7000 accepts the D601 frp client", - "hwlab-dev hwlab-edge-proxy, hwlab-tunnel-client, hwlab-router, and hwlab-cloud-api are observable", - "No PROD endpoint, secret read, restart, or UniDesk runtime substitute is used" - ], - "summary": "DB endpoint DNS resolution failed from the cloud-api runtime" - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "cloud-api-db", - "status": "open", - "summary": "DB endpoint DNS resolution failed from the cloud-api runtime", - "classification": "dns_resolution_failed", - "sourceIssue": "pikasTech/HWLAB#49", - "impact": { - "blocks": [ - "DB live readiness", - "M4 live agent scheduling precondition", - "M5 DEV-LIVE acceptance" - ], - "separateFrom": [ - "pikasTech/HWLAB#143 provider Secret", - "M3 hardware trusted-loop proof" - ] - } - }, - { - "type": "agent_blocker", - "scope": "code-agent-provider-secret", - "status": "open", - "summary": "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence; #143 real provider-backed chat remains blocked", - "classification": "provider_secret_missing", - "sourceIssue": "pikasTech/HWLAB#143", - "impact": { - "blocks": [ - "#143 provider-backed Code Agent chat", - "M4 provider-backed agent execution", - "M5 DEV-LIVE acceptance" - ], - "separateFrom": [ - "pikasTech/HWLAB#49 DB live readiness", - "M3 hardware trusted-loop proof" - ] - } - } - ], - "edgeHealth": { - "generatedAt": "2026-05-22T13:45:42.982Z", - "mode": "live-read-only", - "endpoint": "http://74.48.78.17:16667", - "safety": { - "environment": "dev", - "prodTouched": false, - "secretsRead": false, - "restarts": false, - "unideskRuntimeSubstitute": false - }, - "contracts": { - "devEndpoint": "http://74.48.78.17:16667", - "frp": { - "frpcTargetsEdgeProxy": true, - "frpcTargetsCloudWeb": true, - "frpsTcpRemotePort16667": true, - "frpsTcpRemotePort16666": true, - "frpsUsesVhostEdgePort": false, - "configRisk": "none" - }, - "masterEdge": { - "endpoint": "http://74.48.78.17:16667", - "reverseLinkMode": "frp", - "reverseLinkClient": "hwlab-tunnel-client", - "publicPort": 16667 - }, - "deploy": { - "environment": "dev", - "namespace": "hwlab-dev", - "serviceCount": 13, - "edgeProxy": { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:7e29522", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_EDGE_LISTEN": "0.0.0.0:6667", - "HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" - } - }, - "tunnelClient": { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:7e29522", - "namespace": "hwlab-dev", - "healthPath": "/health/live", - "profile": "dev", - "replicas": 1, - "env": { - "HWLAB_TUNNEL_MODE": "frp-client", - "HWLAB_FRP_SERVER_ADDR": "74.48.78.17", - "HWLAB_FRP_PUBLIC_PORT": "16667", - "HWLAB_FRP_WEB_PUBLIC_PORT": "16666" - } - }, - "cloudApiDb": { - "status": "degraded", - "ready": false, - "configReady": true, - "connected": false, - "liveConnected": false, - "connectionChecked": false, - "connectionAttempted": false, - "connectionResult": "manifest_only_not_attempted", - "liveDbEvidence": false, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false, - "source": "runtime-env" - } - ], - "missingEnv": [], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "envInjected": false, - "secretPresent": "not_observed_by_manifest", - "secretKeyPresent": "declared", - "redacted": true - } - ], - "connection": { - "attempted": false, - "networkAttempted": false, - "result": "manifest_only_not_attempted", - "classification": "manifest_only", - "probeType": "tcp-connect", - "endpointRedacted": true, - "valueRedacted": true, - "timeoutMs": null, - "durationMs": 0, - "errorCode": null - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "blocker": "Manifest declares DB env/Secret references but does not prove runtime env injection or live DB connection", - "fixtureEvidence": false, - "manifestConfigReady": true, - "envInjected": false, - "secretMaterialRead": false, - "valuesRedacted": true, - "note": "Manifest-only DB env placeholder; this is not runtime env injection or live DB evidence." - }, - "codeAgentProvider": { - "status": "degraded", - "sourceIssue": "pikasTech/HWLAB#143", - "requiredEnv": [ - { - "name": "OPENAI_API_KEY", - "present": true, - "redacted": true, - "source": "k8s-secret-ref" - } - ], - "secretRef": { - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "present": true, - "envInjected": false, - "secretPresent": "not_observed_by_manifest", - "secretKeyPresent": "declared", - "secretValueRead": false, - "redacted": true - }, - "secretMaterialRead": false, - "valuesRedacted": true, - "note": "Manifest-only Code Agent provider Secret reference; this is not runtime Secret injection or provider connectivity evidence." - } - } - }, - "publicTcp": [ - { - "host": "74.48.78.17", - "port": 16667, - "status": "connected", - "durationMs": 206 - }, - { - "host": "74.48.78.17", - "port": 7000, - "status": "connected", - "durationMs": 218 - }, - { - "host": "74.48.78.17", - "port": 7402, - "status": "connected", - "durationMs": 207 - } - ], - "publicHttp": [ - { - "url": "http://74.48.78.17:16667/health", - "ok": true, - "status": 200, - "statusText": "OK", - "headers": { - "connection": "keep-alive", - "content-length": "661", - "content-type": "application/json; charset=utf-8", - "date": "Fri, 22 May 2026 13:45:42 GMT", - "keep-alive": "timeout=5" - }, - "json": { - "serviceId": "hwlab-edge-proxy", - "environment": "dev", - "status": "ok", - "service": { - "id": "hwlab-edge-proxy", - "role": "public-dev-ingress", - "healthPath": "/health", - "livePath": "/health/live" - }, - "commit": { - "id": "cb35ada6860653b27269b0a57991184118cbf4b1", - "source": "runtime-env" - }, - "image": { - "reference": "ghcr.io/pikastech/hwlab-edge-proxy:cb35ada", - "tag": "cb35ada", - "digest": "unknown" - }, - "endpoint": "http://74.48.78.17:16667", - "observedAt": "2026-05-22T13:45:42.602Z", - "details": { - "upstream": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "mode": "dev-edge-proxy" - } - }, - "bodyPreview": "{\n \"serviceId\": \"hwlab-edge-proxy\",\n \"environment\": \"dev\",\n \"status\": \"ok\",\n \"service\": {\n \"id\": \"hwlab-edge-proxy\",\n \"role\": \"public-dev-ingress\",\n \"healthPath\": \"/health\",\n \"livePath\": \"/health/live\"\n },\n \"commit\": {\n \"id\": \"cb35ada6860653b27269b0a57991184118cbf4b1\",\n \"source\": \"runtime-env\"\n },\n \"image\": {\n \"reference\": \"ghcr.io/pikastech/hwlab-edge-proxy:cb35ada\",\n \"tag\": \"cb35ada\",\n \"digest\": \"unknown\"\n },\n \"endpoint\": \"http://74.48.78.17:16667\",\n \"obse", - "durationMs": 837 - }, - { - "url": "http://74.48.78.17:16667/health/live", - "ok": true, - "status": 200, - "statusText": "OK", - "headers": { - "cache-control": "no-store", - "connection": "keep-alive", - "content-type": "application/json; charset=utf-8", - "date": "Fri, 22 May 2026 13:45:42 GMT", - "keep-alive": "timeout=5", - "transfer-encoding": "chunked" - }, - "json": { - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "status": "degraded", - "service": { - "id": "hwlab-cloud-api", - "role": "cloud-api", - "healthPath": "/health", - "livePath": "/health/live" - }, - "commit": { - "id": "c7de474", - "source": "runtime-env" - }, - "image": { - "reference": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "tag": "c7de474", - "digest": "unknown" - }, - "endpoint": "http://74.48.78.17:16667", - "observedAt": "2026-05-22T13:45:42.604Z", - "db": { - "contractVersion": "v1", - "environment": "dev", - "connected": false, - "liveConnected": false, - "liveDbEvidence": false, - "connectionChecked": true, - "connectionAttempted": true, - "connectionResult": "dns_error", - "configReady": true, - "ready": false, - "status": "degraded", - "mode": "live_connection_blocked", - "fields": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true, - "source": "k8s-secret-ref", - "required": true - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false, - "source": "runtime-env", - "required": true - } - ], - "missingEnv": [], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "envInjected": true, - "secretPresent": "not_observed_by_runtime", - "secretKeyPresent": "not_observed_by_runtime", - "redacted": true - } - ], - "connection": { - "attempted": true, - "networkAttempted": true, - "probeType": "tcp-connect", - "endpointRedacted": true, - "valueRedacted": true, - "timeoutMs": 1200, - "durationMs": 1, - "missingEnv": [], - "result": "dns_error", - "classification": "dns_resolution_failed", - "errorCode": "ENOTFOUND" - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "safety": { - "devOnly": true, - "prodAllowed": false, - "secretsRead": false, - "secretMaterialRead": false, - "valuesRedacted": true, - "endpointRedacted": true, - "liveDbEvidence": false, - "liveDbConnectedEvidence": false - }, - "blocker": "DB endpoint DNS resolution failed from the cloud-api runtime", - "evidence": "live_db_tcp_connection_blocked" - }, - "runtime": { - "adapter": "memory", - "durable": false, - "status": "degraded", - "reason": "live DB persistence is not connected; L1 runtime writes are process-local only", - "counts": { - "gatewaySessions": 0, - "boxResources": 0, - "boxCapabilities": 0, - "hardwareOperations": 0, - "auditEvents": 0, - "evidenceRecords": 0 - } - } - }, - "bodyPreview": "{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"c7de474\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474\",\"tag\":\"c7de474\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T13:45:42.604Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"liveConnected\":false,\"liveD", - "durationMs": 810 - } - ], - "kubernetes": { - "observable": true, - "namespace": "hwlab-dev", - "context": { - "exitCode": 0, - "value": "in-cluster", - "stderr": "" - }, - "services": [ - { - "name": "hwlab-cloud-api", - "type": "ClusterIP", - "clusterIP": "10.43.88.144", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ], - "selector": { - "app.kubernetes.io/name": "hwlab-cloud-api", - "app.kubernetes.io/part-of": "hwlab", - "hwlab.pikastech.local/environment": "dev", - "hwlab.pikastech.local/profile": "dev" - } - }, - { - "name": "hwlab-edge-proxy", - "type": "ClusterIP", - "clusterIP": "10.43.104.107", - "ports": [ - { - "name": "http", - "port": 6667, - "targetPort": "http" - } - ], - "selector": { - "app.kubernetes.io/name": "hwlab-edge-proxy", - "app.kubernetes.io/part-of": "hwlab", - "hwlab.pikastech.local/environment": "dev", - "hwlab.pikastech.local/profile": "dev" - } - }, - { - "name": "hwlab-tunnel-client", - "type": "ClusterIP", - "clusterIP": "10.43.129.225", - "ports": [ - { - "name": "http", - "port": 7402, - "targetPort": "http" - } - ], - "selector": { - "app.kubernetes.io/name": "hwlab-tunnel-client", - "app.kubernetes.io/part-of": "hwlab", - "hwlab.pikastech.local/environment": "dev", - "hwlab.pikastech.local/profile": "dev" - } - }, - { - "name": "hwlab-router", - "type": "ClusterIP", - "clusterIP": "10.43.6.240", - "ports": [ - { - "name": "http", - "port": 7401, - "targetPort": "http" - } - ], - "selector": { - "app.kubernetes.io/name": "hwlab-router", - "app.kubernetes.io/part-of": "hwlab", - "hwlab.pikastech.local/environment": "dev", - "hwlab.pikastech.local/profile": "dev" - } - } - ], - "endpoints": [ - { - "name": "hwlab-cloud-api", - "readyAddresses": [ - "10.42.0.226" - ], - "notReadyAddresses": [], - "ports": [ - { - "name": "http", - "port": 6667 - } - ] - }, - { - "name": "hwlab-edge-proxy", - "readyAddresses": [ - "10.42.0.203" - ], - "notReadyAddresses": [], - "ports": [ - { - "name": "http", - "port": 6667 - } - ] - }, - { - "name": "hwlab-tunnel-client", - "readyAddresses": [ - "10.42.0.207" - ], - "notReadyAddresses": [], - "ports": [ - { - "name": "http", - "port": 7402 - } - ] - }, - { - "name": "hwlab-router", - "readyAddresses": [ - "10.42.0.206" - ], - "notReadyAddresses": [], - "ports": [ - { - "name": "http", - "port": 7401 - } - ] - } - ], - "pods": [ - { - "name": "hwlab-agent-mgr-75544c7747-5dzt9", - "phase": "Running", - "serviceId": "hwlab-agent-mgr", - "containers": [ - { - "name": "hwlab-agent-mgr", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c16c733306a28ae9385087653b79f5bb2f28724d917a89bb296646f6a02c5318" - } - ] - }, - { - "name": "hwlab-agent-skills-849d9976f4-2nsmw", - "phase": "Running", - "serviceId": "hwlab-agent-skills", - "containers": [ - { - "name": "hwlab-agent-skills", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:246fb7bb48597a7179aab62cb091e8213e47b71b6871ba94e6028032992e916c" - } - ] - }, - { - "name": "hwlab-box-simu-55688cbcc7-pftxc", - "phase": "Running", - "serviceId": "hwlab-box-simu", - "containers": [ - { - "name": "hwlab-box-simu", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - } - ] - }, - { - "name": "hwlab-box-simu-55688cbcc7-sq5fx", - "phase": "Running", - "serviceId": "hwlab-box-simu", - "containers": [ - { - "name": "hwlab-box-simu", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - } - ] - }, - { - "name": "hwlab-cloud-api-74c969765d-kzvdd", - "phase": "Running", - "serviceId": "hwlab-cloud-api", - "containers": [ - { - "name": "hwlab-cloud-api", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:86fe92c0dd5d5883851b38805e6f5acc49d2876552599c18f85480cf0d3bf981" - } - ] - }, - { - "name": "hwlab-cloud-web-7d48b84f4b-n6hw6", - "phase": "Running", - "serviceId": "hwlab-cloud-web", - "containers": [ - { - "name": "hwlab-cloud-web", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:91cfacc1fc011a96bb47ff00d2ab09ee3c93003b7ec482308ec5ac35004e5627" - } - ] - }, - { - "name": "hwlab-edge-proxy-567f978cdd-8fwc8", - "phase": "Running", - "serviceId": "hwlab-edge-proxy", - "containers": [ - { - "name": "hwlab-edge-proxy", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:384832fc579063b9b0a940973690734bb402607720fd36f4662e4ee6fa5a57d2" - } - ] - }, - { - "name": "hwlab-gateway-simu-699fd486b-j9pk7", - "phase": "Running", - "serviceId": "hwlab-gateway-simu", - "containers": [ - { - "name": "hwlab-gateway-simu", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - } - ] - }, - { - "name": "hwlab-gateway-simu-699fd486b-phcgp", - "phase": "Running", - "serviceId": "hwlab-gateway-simu", - "containers": [ - { - "name": "hwlab-gateway-simu", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - } - ] - }, - { - "name": "hwlab-patch-panel-757f9f44d5-7np8r", - "phase": "Running", - "serviceId": "hwlab-patch-panel", - "containers": [ - { - "name": "hwlab-patch-panel", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:5b3aa6505492c2ed398d21cbd35a8754672114acd0fa97faa3351d4b6a6dfd5a" - } - ] - }, - { - "name": "hwlab-router-5d9cbdf89b-p7r94", - "phase": "Running", - "serviceId": "hwlab-router", - "containers": [ - { - "name": "hwlab-router", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-router:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:217b1fdb0f704c4183e9d8503644177f0c8bae674da0433985c19ef909f0e342" - } - ] - }, - { - "name": "hwlab-tunnel-client-65c7858c8-wjqsh", - "phase": "Running", - "serviceId": "hwlab-tunnel-client", - "containers": [ - { - "name": "hwlab-tunnel-client", - "ready": true, - "restartCount": 0, - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:9bcdd8a58132d7bc4dbfc71297f802fc0ab407a1b5c1bb1ea58e0791147a243b" - } - ] - } - ], - "dbSecret": { - "observable": true, - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "secretPresent": true, - "secretKeyPresent": true, - "secretValueRead": false, - "redacted": true, - "command": "kubectl describe secret <name> (key-presence-only)", - "error": null - }, - "codeAgentProviderSecret": { - "observable": true, - "sourceIssue": "pikasTech/HWLAB#143", - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "secretPresent": false, - "secretKeyPresent": false, - "secretValueRead": false, - "redacted": true, - "command": "kubectl get secret -o name", - "error": "Error from server (NotFound): secrets \"hwlab-code-agent-provider\" not found" - }, - "notes": [ - "kubectl Code Agent provider Secret presence probe failed: Error from server (NotFound): secrets \"hwlab-code-agent-provider\" not found" - ] - }, - "clusterDns": [ - { - "serviceId": "hwlab-cloud-api", - "host": "hwlab-cloud-api.hwlab-dev.svc.cluster.local", - "port": 6667, - "resolved": true, - "output": "10.43.88.144 hwlab-cloud-api.hwlab-dev.svc.cluster.local" - }, - { - "serviceId": "hwlab-router", - "host": "hwlab-router.hwlab-dev.svc.cluster.local", - "port": 7401, - "resolved": true, - "output": "10.43.6.240 hwlab-router.hwlab-dev.svc.cluster.local" - }, - { - "serviceId": "hwlab-tunnel-client", - "host": "hwlab-tunnel-client.hwlab-dev.svc.cluster.local", - "port": 7402, - "resolved": true, - "output": "10.43.129.225 hwlab-tunnel-client.hwlab-dev.svc.cluster.local" - }, - { - "serviceId": "hwlab-edge-proxy", - "host": "hwlab-edge-proxy.hwlab-dev.svc.cluster.local", - "port": 6667, - "resolved": true, - "output": "10.43.104.107 hwlab-edge-proxy.hwlab-dev.svc.cluster.local" - } - ], - "runtimeDbReadiness": { - "status": "blocked", - "configReady": true, - "envInjected": true, - "secret": { - "observable": true, - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "secretPresent": true, - "secretKeyPresent": true, - "secretValueRead": false, - "redacted": true, - "command": "kubectl describe secret <name> (key-presence-only)", - "error": null, - "secretRefPresent": true, - "envInjected": true - }, - "connectionAttempted": true, - "connectionResult": "dns_error", - "connectionClassification": "dns_resolution_failed", - "liveConnected": false, - "valuesRedacted": true, - "secretMaterialRead": false, - "liveDbEvidence": false, - "blocker": "DB endpoint DNS resolution failed from the cloud-api runtime", - "evidence": [ - { - "configReady": true, - "missingEnv": [], - "connectionAttempted": true, - "connectionResult": "dns_error", - "classification": "dns_resolution_failed", - "liveConnected": false, - "endpointRedacted": true, - "valueRedacted": true - } - ] - }, - "status": "blocker", - "classification": "app_health_blocker", - "blocker": "DB endpoint DNS resolution failed from the cloud-api runtime", - "diagnosis": { - "likelyLayer": "cloud-api-db", - "confidence": "high", - "likelyCause": "DB endpoint DNS resolution failed from the cloud-api runtime", - "evidence": [ - { - "configReady": true, - "missingEnv": [], - "connectionAttempted": true, - "connectionResult": "dns_error", - "classification": "dns_resolution_failed", - "liveConnected": false, - "endpointRedacted": true, - "valueRedacted": true - } - ], - "notProven": [ - "authenticated SQL query readiness" - ], - "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." - }, - "providerSecretReadiness": { - "status": "blocked", - "sourceIssue": "pikasTech/HWLAB#143", - "provider": "openai-responses", - "requiredEnv": "OPENAI_API_KEY", - "manifestRefPresent": true, - "secret": { - "observable": true, - "sourceIssue": "pikasTech/HWLAB#143", - "env": "OPENAI_API_KEY", - "secretName": "hwlab-code-agent-provider", - "secretKey": "openai-api-key", - "secretPresent": false, - "secretKeyPresent": false, - "secretValueRead": false, - "redacted": true, - "command": "kubectl get secret -o name", - "error": "Error from server (NotFound): secrets \"hwlab-code-agent-provider\" not found" - }, - "providerCallAttempted": false, - "providerConnected": false, - "secretMaterialRead": false, - "valuesRedacted": true, - "classification": "provider_secret_missing", - "blocker": "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence; #143 real provider-backed chat remains blocked", - "evidence": [ - { - "source": "kubernetes-secret-presence", - "manifestRefPresent": true, - "secretPresent": false, - "secretKeyPresent": false, - "secretValueRead": false, - "redacted": true - } - ] - }, - "parallelBlockers": [ - { - "id": "cloud-api-db-live", - "type": "runtime_blocker", - "scope": "cloud-api-db", - "status": "open", - "sourceIssue": "pikasTech/HWLAB#49", - "classification": "dns_resolution_failed", - "summary": "DB endpoint DNS resolution failed from the cloud-api runtime", - "impact": { - "blocks": [ - "DB live readiness", - "M4 live agent scheduling precondition", - "M5 DEV-LIVE acceptance" - ], - "separateFrom": [ - "pikasTech/HWLAB#143 provider Secret", - "M3 hardware trusted-loop proof" - ] - } - }, - { - "id": "code-agent-provider-secret", - "type": "agent_blocker", - "scope": "code-agent-provider-secret", - "status": "open", - "sourceIssue": "pikasTech/HWLAB#143", - "classification": "provider_secret_missing", - "summary": "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence; #143 real provider-backed chat remains blocked", - "impact": { - "blocks": [ - "#143 provider-backed Code Agent chat", - "M4 provider-backed agent execution", - "M5 DEV-LIVE acceptance" - ], - "separateFrom": [ - "pikasTech/HWLAB#49 DB live readiness", - "M3 hardware trusted-loop proof" - ] - } - } - ], - "milestoneImpact": { - "M3": { - "status": "separate_blocker", - "summary": "M3 still requires real DEV hardware trusted-loop operation/trace/audit/evidence; DB DNS failure and #143 provider Secret absence do not prove or clear M3." - }, - "M4": { - "status": "blocked", - "blockedBy": [ - "cloud-api-db-live", - "code-agent-provider-secret" - ], - "summary": "M4 live agent-loop remains blocked until DB live readiness and provider-backed agent prerequisites are independently green." - }, - "M5": { - "status": "blocked", - "blockedBy": [ - "cloud-api-db-live", - "code-agent-provider-secret" - ], - "summary": "M5 DEV-LIVE acceptance cannot be promoted from route health, env presence, or provider manifest refs while DB live and #143 provider Secret blockers remain open." - } - } - } -} diff --git a/reports/dev-gate/dev-gate-report.example.json b/reports/dev-gate/dev-gate-report.example.json deleted file mode 100644 index 65bf6c86..00000000 --- a/reports/dev-gate/dev-gate-report.example.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-gate-report.example.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#31", - "taskId": "dev-gate-report-contract", - "commitId": "5583e4b", - "acceptanceLevel": "dev_gate", - "devOnly": true, - "prodDisabled": true, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md" - ], - "summary": "The gate report contract is anchored to the frozen DEV acceptance matrix and the M0 audit." - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "blocked", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "No local smoke evidence is attached to this example contract." - ], - "summary": "The example contract does not bundle a local smoke result." - }, - "dryRun": { - "status": "not_run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - ], - "evidence": [ - "No dry-run output is attached to this example contract." - ], - "summary": "The example contract does not bundle a dry-run result." - }, - "devPreconditions": { - "status": "blocked", - "requirements": [ - "DEV route remains frozen at http://74.48.78.17:16667", - "Runtime evidence names frozen HWLAB service IDs", - "No PROD path, secret read, or real deployment is attempted" - ], - "summary": "A live DEV observation is still required before a real gate can be claimed." - }, - "blockers": [ - { - "type": "environment_blocker", - "scope": "localSmoke", - "status": "open", - "summary": "Example contract only; no local smoke run is attached." - }, - { - "type": "runtime_blocker", - "scope": "dryRun", - "status": "open", - "summary": "No dry-run output is attached." - }, - { - "type": "network_blocker", - "scope": "devPreconditions", - "status": "open", - "summary": "No live DEV precondition evidence is attached." - } - ], - "notes": "Contract-only example. Do not treat this file as a real DEV gate report.", - "reportLifecycle": { - "version": "v1", - "state": "active", - "activeEndpoint": "http://74.48.78.17:16667", - "activeBrowserEndpoint": "http://74.48.78.17:16666", - "deprecatedEndpoint": null, - "summary": "Active example report for the current DEV gate contract." - } -} diff --git a/reports/dev-gate/dev-m2-deploy-smoke-active.json b/reports/dev-gate/dev-m2-deploy-smoke-active.json deleted file mode 100644 index 1ac5dad6..00000000 --- a/reports/dev-gate/dev-m2-deploy-smoke-active.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-m2-deploy-smoke-active.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#23", - "taskId": "m2-dev-deploy-smoke", - "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-22T07:18:45.192Z", - "endpoint": "http://74.48.78.17:16667", - "frontendEndpoint": "http://74.48.78.17:16666", - "sourceContract": { - "status": "pass", - "documents": [ - "docs/m2-dev-deploy-smoke.md", - "docs/dev-acceptance-matrix.md", - "fixtures/dev-deploy-smoke/dev-deploy-smoke.json" - ], - "summary": "M2 DEV deploy smoke is rebaselined to frontend :16666 and API/edge :16667." - }, - "validationCommands": [ - "node --check scripts/m2-dev-deploy-smoke.mjs", - "node scripts/m2-dev-deploy-smoke.mjs --dry-run", - "node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --write-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "pass", - "commands": [ - "node scripts/m2-dev-deploy-smoke.mjs --dry-run" - ], - "evidence": [ - "[m2-smoke] validated 11 service contracts", - "[m2-smoke] mode=dry-run endpoint=http://74.48.78.17:16667", - "[m2-smoke] no real DEV/PROD request was made" - ], - "summary": "The fixture smoke validates the M2 route and artifact contract without contacting DEV." - }, - "dryRun": { - "status": "pass", - "commands": [ - "node scripts/m2-dev-deploy-smoke.mjs --dry-run" - ], - "evidence": [ - "fixture endpoint=http://74.48.78.17:16667", - "fixture frontendEndpoint=http://74.48.78.17:16666", - "legacy :6667 public evidence is marked ineligible for active green" - ], - "summary": "Dry-run evidence is contract-only and pinned to the current DEV public endpoints." - }, - "devPreconditions": { - "status": "pass", - "requirements": [ - "GET http://74.48.78.17:16667/health returns HWLAB DEV JSON", - "GET http://74.48.78.17:16667/health/live returns HWLAB DEV JSON", - "GET http://74.48.78.17:16666/ returns the HWLAB DEV frontend", - "No PROD, secret read, runtime restart, deployment, or heavyweight e2e action is performed" - ], - "commands": [ - "curl -fsS --max-time 10 http://74.48.78.17:16667/health", - "curl -fsS --max-time 10 http://74.48.78.17:16667/health/live", - "curl -fsS --max-time 10 http://74.48.78.17:16666/" - ], - "evidence": [ - "api-health: HTTP 200 accepted=yes identity=hwlab-edge-proxy url=http://74.48.78.17:16667/health", - "api-live: HTTP 200 accepted=yes identity=hwlab-cloud-api url=http://74.48.78.17:16667/health/live", - "frontend-root: HTTP 200 accepted=yes identity=HWLAB DEV MVP Gate url=http://74.48.78.17:16666/" - ], - "summary": "Read-only DEV public endpoint probes passed on :16666/:16667." - }, - "blockers": [], - "runtimeSmoke": { - "mode": "live-read-only", - "status": "pass", - "generatedAt": "2026-05-22T07:18:45.192Z", - "apiEndpoint": "http://74.48.78.17:16667", - "frontendEndpoint": "http://74.48.78.17:16666", - "safety": { - "environment": "dev", - "prodTouched": false, - "secretsRead": false, - "restarts": false, - "deployAttempted": false, - "heavyE2E": false, - "unideskRuntimeSubstitute": false - }, - "probes": [ - { - "id": "api-health", - "url": "http://74.48.78.17:16667/health", - "ok": true, - "status": 200, - "statusText": "OK", - "durationMs": 3916, - "contentType": "application/json; charset=utf-8", - "headers": { - "content-type": "application/json; charset=utf-8", - "content-length": "661", - "date": "Fri, 22 May 2026 07:18:43 GMT" - }, - "json": { - "serviceId": "hwlab-edge-proxy", - "environment": "dev", - "status": "ok", - "service": { - "id": "hwlab-edge-proxy", - "role": "public-dev-ingress", - "healthPath": "/health", - "livePath": "/health/live" - }, - "commit": { - "id": "cb35ada6860653b27269b0a57991184118cbf4b1", - "source": "runtime-env" - }, - "image": { - "reference": "ghcr.io/pikastech/hwlab-edge-proxy:cb35ada", - "tag": "cb35ada", - "digest": "unknown" - }, - "endpoint": "http://74.48.78.17:16667", - "observedAt": "2026-05-22T07:18:43.527Z", - "details": { - "upstream": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667", - "mode": "dev-edge-proxy" - } - }, - "title": null, - "bodyPreview": "{\n \"serviceId\": \"hwlab-edge-proxy\",\n \"environment\": \"dev\",\n \"status\": \"ok\",\n \"service\": {\n \"id\": \"hwlab-edge-proxy\",\n \"role\": \"public-dev-ingress\",\n \"healthPath\": \"/health\",\n \"livePath\": \"/health/live\"\n },\n \"commit\": {\n \"id\": \"cb35ada6860653b27269b0a57991184118cbf4b1\",\n \"source\": \"runtime-env\"\n },\n \"image\": {\n \"reference\": \"ghcr.io/pikastech/hwlab-edge-proxy:cb35ada\",\n \"tag\": \"cb35ada\",\n \"digest\": \"unknown\"\n },\n \"endpoint\": \"http://74.48.78.17:16667\",\n \"observedAt\": \"20" - }, - { - "id": "api-live", - "url": "http://74.48.78.17:16667/health/live", - "ok": true, - "status": 200, - "statusText": "OK", - "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 07:18:44 GMT" - }, - "json": { - "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": true, - "ready": false, - "status": "degraded", - "mode": "configured_without_live_connection", - "fields": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": true, - "redacted": true, - "source": "k8s-secret-ref", - "required": true - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": true, - "redacted": false, - "source": "runtime-env", - "required": true - } - ], - "missingEnv": [], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "redacted": true - } - ], - "safety": { - "devOnly": true, - "prodAllowed": false, - "secretsRead": false, - "valuesRedacted": true, - "liveDbEvidence": false - }, - "evidence": "env_presence_only_no_live_db" - }, - "runtime": { - "adapter": "memory", - "durable": false, - "status": "degraded", - "reason": "live DB persistence is not connected; L1 runtime writes are process-local only", - "counts": { - "gatewaySessions": 0, - "boxResources": 0, - "boxCapabilities": 0, - "hardwareOperations": 0, - "auditEvents": 0, - "evidenceRecords": 0 - } - } - }, - "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-22T07:18:44.622Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"connectionChecked\":false,\"configReady\":" - }, - { - "id": "frontend-root", - "url": "http://74.48.78.17:16666/", - "ok": true, - "status": 200, - "statusText": "OK", - "durationMs": 1027, - "contentType": "text/html; charset=utf-8", - "headers": { - "content-type": "text/html; charset=utf-8", - "content-length": "4230", - "date": "Fri, 22 May 2026 07:18:42 GMT" - }, - "json": null, - "title": "HWLAB DEV MVP Gate", - "bodyPreview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>HWLAB DEV MVP Gate\n \n \n \n \n
\n
\n
\n

HWLAB L6 Frontend / CLI MVP

\n

DEV MVP Gate

\n
\n
hwlab-patch-panel -> res_boxsimu_2:DI1 with operation, audit, and evidence ids." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/m3-hardware-loop.md", - "fixtures/mvp/m3-hardware-loop/topology.json" - ], - "summary": "Source contracts define DEV-only M3 simulator wiring and prohibit fixture output as live evidence." - }, - "validationCommands": [ - "node --check scripts/dev-m3-hardware-loop-smoke.mjs", - "node --check scripts/validate-dev-m3-cardinality.mjs", - "node scripts/validate-dev-m3-cardinality.mjs", - "node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "runtimeTarget": { - "endpoint": "http://74.48.78.17:16667", - "frontendEndpoint": "http://74.48.78.17:16666", - "namespace": "hwlab-dev", - "environment": "dev", - "requiredBoxSimulators": 2, - "requiredGatewaySimulators": 2, - "requiredPatchPanels": 1, - "requiredRoute": "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", - "realHardwareAllowed": false, - "prodAllowed": false - }, - "safetyGates": { - "liveFlagRequired": true, - "confirmDevRequired": true, - "expectNonProdRequired": true, - "confirmedNonProductionRequired": true, - "dryRunPlanSupported": true, - "dryRunCallsLiveEndpoints": false, - "prodForbidden": true, - "realHardwareForbidden": true, - "secretReadForbidden": true, - "forcePushForbidden": true, - "unideskRuntimeSubstitutionForbidden": true - }, - "liveChecks": [ - { - "id": "static-contract-parse", - "status": "pass", - "summary": "DEV M3 smoke contract files were readable before live probing.", - "evidence": [ - "docs/dev-acceptance-matrix.md", - "docs/m3-hardware-loop.md" - ] - }, - { - "id": "endpoint-freeze", - "status": "pass", - "summary": "DEV endpoint is frozen at http://74.48.78.17:16667.", - "evidence": [ - "http://74.48.78.17:16667" - ] - }, - { - "id": "dry-run-live-write-plan", - "status": "not_run", - "summary": "Plan-only mode enumerated live write prerequisites and endpoints without calling DEV endpoints or mutating DEV state.", - "evidence": [ - "endpointCount=17", - "mutatingEndpointCount=2", - "dryRunCallsLiveEndpoints=false" - ] - }, - { - "id": "dev-ingress-health", - "status": "pass", - "summary": "DEV ingress returned HWLAB identity at /health/live.", - "evidence": [ - "{\"url\":\"http://74.48.78.17:16667/health/live\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:36.427Z\",\"observedAt\":\"2026-05-22T13:44:37.010Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"status\":\"degraded\",\"service\":{\"id\":\"hwlab-cloud-api\",\"role\":\"cloud-api\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"c7de474\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474\",\"tag\":\"c7de474\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T13:44:36.806Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"liveConnected\":false,\"liveDbEvidence\":false,\"connectionChecked\":true,\"connectionAttempted\":true,\"connectionResult\":\"dns_error\",\"configReady\":true,\"ready\":false,\"status\":\"degraded\",\"mode\":\"live_connection_blocked\",\"fields\":[{\"name\":\"HWLAB_CLOUD_DB_URL\",\"present\":true,\"redacted\":true,\"source\":\"k8s-secret-ref\",\"required\":true},{\"name\":\"HWLAB_CLOUD_DB_SSL_MODE\",\"present\":true,\"redacted\":false,\"source\":\"runtime-env\",\"required\":true}],\"missingEnv\":[],\"secretRefs\":[{\"env\":\"HWLAB_CLOUD_DB_URL\",\"secretName\":\"hwlab-cloud-api-dev-db\",\"secretKey\":\"database-url\",\"present\":true,\"envInjected\":true,\"secretPresent\":\"not_observed_by_runtime\",\"secretKeyPresent\":\"not_observed_by_runtime\",\"redacted\":true}],\"connection\":{\"attempted\":true,\"networkAttempted\":true,\"probeType\":\"tcp-connect\",\"endpointRedacted\":true,\"valueRedacted\":true,\"timeoutMs\":1200,\"durationMs\":1,\"missingEnv\":[],\"result\":\"dns_error\",\"classification\":\"dns_resolution_failed\",\"errorCode\":\"ENOTFOUND\"},\"redaction\":{\"valuesRedacted\":true,\"endpointRedacted\":true,\"valueRedacted\":true,\"secretMaterialRead\":false,\"secretRefsOnly\":true},\"safety\":{\"devOnly\":true,\"prodAllowed\":false,\"secretsRead\":false,\"secretMaterialRead\":false,\"valuesRedacted\":true,\"endpointRedacted\":true,\"liveDbEvidence\":false,\"liveDbConnectedEvidence\":false},\"blocker\":\"DB endpoint DNS resolution failed from the cloud-api runtime\",\"evidence\":\"live_db_tcp_connection_blocked\"},\"runtime\":{\"adapter\":\"memory\",\"durable\":false,\"status\":\"degraded\",\"reason\":\"live DB persistence is not connected; L1 runtime writes are process-local only\",\"counts\":{\"gatewaySessions\":0,\"boxResources\":0,\"boxCapabilities\":0,\"hardwareOperations\":0,\"auditEvents\":0,\"evidenceRecords\":0}}}}", - "{\"url\":\"http://74.48.78.17:16667/health\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:37.011Z\",\"observedAt\":\"2026-05-22T13:44:37.943Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-edge-proxy\",\"environment\":\"dev\",\"status\":\"ok\",\"service\":{\"id\":\"hwlab-edge-proxy\",\"role\":\"public-dev-ingress\",\"healthPath\":\"/health\",\"livePath\":\"/health/live\"},\"commit\":{\"id\":\"cb35ada6860653b27269b0a57991184118cbf4b1\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"ghcr.io/pikastech/hwlab-edge-proxy:cb35ada\",\"tag\":\"cb35ada\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T13:44:37.190Z\",\"details\":{\"upstream\":\"http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667\",\"mode\":\"dev-edge-proxy\"}}}", - "{\"url\":\"http://74.48.78.17:16667/live\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:37.943Z\",\"observedAt\":\"2026-05-22T13:44:38.325Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-cloud-api\",\"status\":\"live\"}}", - "{\"url\":\"http://74.48.78.17:16667/v1\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:38.325Z\",\"observedAt\":\"2026-05-22T13:44:38.710Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-cloud-api\",\"adapter\":\"rest\",\"status\":\"degraded\",\"rpcBridge\":\"POST /v1/rpc/{method}\",\"codeAgent\":{\"endpoint\":\"POST /v1/agent/chat\",\"status\":\"available\",\"schema\":[\"conversationId\",\"sessionId\",\"messageId\",\"status\",\"createdAt\",\"updatedAt\",\"traceId\",\"provider\",\"model\",\"backend\",\"error.message\"]},\"methods\":[\"system.health\",\"cloud.adapter.describe\",\"gateway.session.register\",\"box.resource.register\",\"box.capability.report\",\"hardware.operation.request\",\"hardware.invoke.shell\",\"audit.event.write\",\"audit.event.query\",\"evidence.record.write\",\"evidence.record.query\"],\"auditFields\":[\"requestId\",\"actor\",\"source\",\"operation\",\"target\",\"result\",\"timestamp\"],\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"liveConnected\":false,\"liveDbEvidence\":false,\"connectionChecked\":true,\"connectionAttempted\":true,\"connectionResult\":\"dns_error\",\"configReady\":true,\"ready\":false,\"status\":\"degraded\",\"mode\":\"live_connection_blocked\",\"fields\":[{\"name\":\"HWLAB_CLOUD_DB_URL\",\"present\":true,\"redacted\":true,\"source\":\"k8s-secret-ref\",\"required\":true},{\"name\":\"HWLAB_CLOUD_DB_SSL_MODE\",\"present\":true,\"redacted\":false,\"source\":\"runtime-env\",\"required\":true}],\"missingEnv\":[],\"secretRefs\":[{\"env\":\"HWLAB_CLOUD_DB_URL\",\"secretName\":\"hwlab-cloud-api-dev-db\",\"secretKey\":\"database-url\",\"present\":true,\"envInjected\":true,\"secretPresent\":\"not_observed_by_runtime\",\"secretKeyPresent\":\"not_observed_by_runtime\",\"redacted\":true}],\"connection\":{\"attempted\":true,\"networkAttempted\":true,\"probeType\":\"tcp-connect\",\"endpointRedacted\":true,\"valueRedacted\":true,\"timeoutMs\":1200,\"durationMs\":2,\"missingEnv\":[],\"result\":\"dns_error\",\"classification\":\"dns_resolution_failed\",\"errorCode\":\"ENOTFOUND\"},\"redaction\":{\"valuesRedacted\":true,\"endpointRedacted\":true,\"valueRedacted\":true,\"secretMaterialRead\":false,\"secretRefsOnly\":true},\"safety\":{\"devOnly\":true,\"prodAllowed\":false,\"secretsRead\":false,\"secretMaterialRead\":false,\"valuesRedacted\":true,\"endpointRedacted\":true,\"liveDbEvidence\":false,\"liveDbConnectedEvidence\":false},\"blocker\":\"DB endpoint DNS resolution failed from the cloud-api runtime\",\"evidence\":\"live_db_tcp_connection_blocked\"},\"runtime\":{\"adapter\":\"memory\",\"durable\":false,\"status\":\"degraded\",\"reason\":\"live DB persistence is not connected; L1 runtime writes are process-local only\",\"counts\":{\"gatewaySessions\":0,\"boxResources\":0,\"boxCapabilities\":0,\"hardwareOperations\":0,\"auditEvents\":0,\"evidenceRecords\":0}}}}", - "{\"url\":\"http://74.48.78.17:16666/health/live\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:38.710Z\",\"observedAt\":\"2026-05-22T13:44:39.934Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-cloud-web\",\"environment\":\"dev\",\"status\":\"ok\",\"artifactKind\":\"cloud-web\",\"revision\":\"c7de474\"}}", - "{\"url\":\"http://74.48.78.17:16666/health\",\"method\":\"GET\",\"startedAt\":\"2026-05-22T13:44:39.934Z\",\"observedAt\":\"2026-05-22T13:44:40.314Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"serviceId\":\"hwlab-cloud-web\",\"environment\":\"dev\",\"status\":\"ok\",\"artifactKind\":\"cloud-web\",\"revision\":\"c7de474\"}}", - "{\"url\":\"http://74.48.78.17:16667/json-rpc\",\"method\":\"POST\",\"startedAt\":\"2026-05-22T13:44:40.314Z\",\"observedAt\":\"2026-05-22T13:44:40.709Z\",\"reached\":true,\"ok\":true,\"status\":200,\"statusText\":\"OK\",\"json\":{\"jsonrpc\":\"2.0\",\"id\":\"req_dev_m3_a6ad2ece-8c10-4938-bdbb-717ae63948bb\",\"result\":{\"status\":\"degraded\",\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"liveConnected\":false,\"liveDbEvidence\":false,\"connectionChecked\":true,\"connectionAttempted\":true,\"connectionResult\":\"dns_error\",\"configReady\":true,\"ready\":false,\"status\":\"degraded\",\"mode\":\"live_connection_blocked\",\"fields\":[{\"name\":\"HWLAB_CLOUD_DB_URL\",\"present\":true,\"redacted\":true,\"source\":\"k8s-secret-ref\",\"required\":true},{\"name\":\"HWLAB_CLOUD_DB_SSL_MODE\",\"present\":true,\"redacted\":false,\"source\":\"runtime-env\",\"required\":true}],\"missingEnv\":[],\"secretRefs\":[{\"env\":\"HWLAB_CLOUD_DB_URL\",\"secretName\":\"hwlab-cloud-api-dev-db\",\"secretKey\":\"database-url\",\"present\":true,\"envInjected\":true,\"secretPresent\":\"not_observed_by_runtime\",\"secretKeyPresent\":\"not_observed_by_runtime\",\"redacted\":true}],\"connection\":{\"attempted\":true,\"networkAttempted\":true,\"probeType\":\"tcp-connect\",\"endpointRedacted\":true,\"valueRedacted\":true,\"timeoutMs\":1200,\"durationMs\":1,\"missingEnv\":[],\"result\":\"dns_error\",\"classification\":\"dns_resolution_failed\",\"errorCode\":\"ENOTFOUND\"},\"redaction\":{\"valuesRedacted\":true,\"endpointRedacted\":true,\"valueRedacted\":true,\"secretMaterialRead\":false,\"secretRefsOnly\":true},\"safety\":{\"devOnly\":true,\"prodAllowed\":false,\"secretsRead\":false,\"secretMaterialRead\":false,\"valuesRedacted\":true,\"endpointRedacted\":true,\"liveDbEvidence\":false,\"liveDbConnectedEvidence\":false},\"blocker\":\"DB endpoint DNS resolution failed from the cloud-api runtime\",\"evidence\":\"live_db_tcp_connection_blocked\"},\"runtime\":{\"adapter\":\"memory\",\"durable\":false,\"status\":\"degraded\",\"reason\":\"live DB persistence is not connected; L1 runtime writes are process-local only\",\"counts\":{\"gatewaySessions\":0,\"boxResources\":0,\"boxCapabilities\":0,\"hardwareOperations\":0,\"auditEvents\":0,\"evidenceRecords\":0}}},\"meta\":{\"traceId\":\"trc_dev_m3_2393bb6a-6df3-4ad1-8794-b45530869d22\",\"serviceId\":\"hwlab-cloud-api\",\"environment\":\"dev\",\"actorId\":\"system_code_queue_d601\"}}}" - ] - }, - { - "id": "two-box-simu-online", - "status": "blocked", - "blockerClass": "runtime_blocker", - "summary": "Read-only direct target probes did not prove two distinct live DEV box-simu resources res_boxsimu_1 and res_boxsimu_2.", - "evidence": [ - "{\"source\":\"kubernetes-endpointslices\",\"counts\":{\"boxSimu\":2,\"gatewaySimu\":2,\"patchPanel\":1,\"distinctBoxResources\":1,\"distinctGatewayIdentities\":1},\"selected\":{\"boxSimu1\":{\"id\":\"box-simu-1-candidate-1\",\"serviceId\":\"hwlab-box-simu\",\"baseUrl\":\"http://10.42.0.200:7201\",\"targetRef\":\"hwlab-box-simu-55688cbcc7-pftxc\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":\"boxsimu_1\",\"resourceId\":\"res_boxsimu_1\",\"gatewayId\":null,\"gatewaySessionId\":\"gws_mvp_simu\",\"patchPanelState\":null},\"boxSimu2\":null,\"gateways\":[{\"id\":\"gateway-simu-1-candidate-1\",\"serviceId\":\"hwlab-gateway-simu\",\"baseUrl\":\"http://10.42.0.204:7101\",\"targetRef\":\"hwlab-gateway-simu-699fd486b-phcgp\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":null,\"resourceId\":null,\"gatewayId\":\"gateway-a\",\"gatewaySessionId\":\"gws_gateway-a\",\"patchPanelState\":null}],\"patchPanel\":{\"id\":\"patch-panel-candidate-1\",\"serviceId\":\"hwlab-patch-panel\",\"baseUrl\":\"http://10.42.0.205:7301\",\"targetRef\":\"hwlab-patch-panel-757f9f44d5-7np8r\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":null,\"resourceId\":null,\"gatewayId\":null,\"gatewaySessionId\":\"gws_gwsimu_east\",\"patchPanelState\":\"active\"}}}" - ] - }, - { - "id": "two-gateway-simu-online", - "status": "blocked", - "blockerClass": "runtime_blocker", - "summary": "Read-only direct target probes did not prove two distinct live DEV gateway-simu identities.", - "evidence": [ - "{\"source\":\"kubernetes-endpointslices\",\"counts\":{\"boxSimu\":2,\"gatewaySimu\":2,\"patchPanel\":1,\"distinctBoxResources\":1,\"distinctGatewayIdentities\":1},\"selected\":{\"boxSimu1\":{\"id\":\"box-simu-1-candidate-1\",\"serviceId\":\"hwlab-box-simu\",\"baseUrl\":\"http://10.42.0.200:7201\",\"targetRef\":\"hwlab-box-simu-55688cbcc7-pftxc\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":\"boxsimu_1\",\"resourceId\":\"res_boxsimu_1\",\"gatewayId\":null,\"gatewaySessionId\":\"gws_mvp_simu\",\"patchPanelState\":null},\"boxSimu2\":null,\"gateways\":[{\"id\":\"gateway-simu-1-candidate-1\",\"serviceId\":\"hwlab-gateway-simu\",\"baseUrl\":\"http://10.42.0.204:7101\",\"targetRef\":\"hwlab-gateway-simu-699fd486b-phcgp\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":null,\"resourceId\":null,\"gatewayId\":\"gateway-a\",\"gatewaySessionId\":\"gws_gateway-a\",\"patchPanelState\":null}],\"patchPanel\":{\"id\":\"patch-panel-candidate-1\",\"serviceId\":\"hwlab-patch-panel\",\"baseUrl\":\"http://10.42.0.205:7301\",\"targetRef\":\"hwlab-patch-panel-757f9f44d5-7np8r\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":null,\"resourceId\":null,\"gatewayId\":null,\"gatewaySessionId\":\"gws_gwsimu_east\",\"patchPanelState\":\"active\"}}}" - ] - }, - { - "id": "patch-panel-healthy", - "status": "pass", - "summary": "DEV patch-panel direct target returned live status.", - "evidence": [ - "{\"id\":\"patch-panel-candidate-1\",\"serviceId\":\"hwlab-patch-panel\",\"baseUrl\":\"http://10.42.0.205:7301\",\"targetRef\":\"hwlab-patch-panel-757f9f44d5-7np8r\",\"ok\":true,\"statusCode\":200,\"healthCode\":200,\"boxId\":null,\"resourceId\":null,\"gatewayId\":null,\"gatewaySessionId\":\"gws_gwsimu_east\",\"patchPanelState\":\"active\"}" - ] - }, - { - "id": "wiring-do1-di1-applied", - "status": "blocked", - "blockerClass": "runtime_blocker", - "summary": "DEV patch-panel wiring is active but does not contain the required res_boxsimu_1:DO1 -> res_boxsimu_2:DI1 route.", - "evidence": [ - "{\"url\":\"http://10.42.0.205:7301/wiring\",\"ok\":true,\"requiredConnection\":{\"fromResourceId\":\"res_boxsimu_1\",\"fromPort\":\"DO1\",\"toResourceId\":\"res_boxsimu_2\",\"toPort\":\"DI1\"},\"activeObserved\":[\"res_boxsim_alpha:uart0->res_boxsim_beta:uart0\",\"res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0\"],\"configuredObserved\":[\"res_boxsim_alpha:uart0->res_boxsim_beta:uart0\",\"res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0\"],\"hasRequiredActiveConnection\":false,\"hasRequiredConfiguredConnection\":false,\"configSource\":\"internal:mvp-topology\",\"syncableConnectionCount\":0}" - ] - }, - { - "id": "direct-call-do-write-di-read", - "status": "not_run", - "summary": "Not attempted because read-only DEV M3 preconditions were blocked; no box-simu loopback or SOURCE/DRY-RUN substitute was used.", - "evidence": [ - "No live DEV write was sent." - ] - }, - { - "id": "audit-evidence-traceable", - "status": "not_run", - "summary": "Not attempted because the patch-panel-bound live operation was not safe to trigger.", - "evidence": [ - "operationId=not_observed", - "traceId=not_observed", - "auditId=not_observed", - "evidenceId=not_observed" - ] - } - ], - "readOnlySupplementalEvidence": [ - { - "id": "deploy-skeleton-m3-cardinality", - "status": "manifest-ready", - "blockerClass": null, - "source": "deploy/k8s/base/workloads.yaml", - "summary": "Checked-in DEV deploy skeleton declares M3 cardinality: two box simulators, two gateway simulators, and one patch panel.", - "evidence": { - "required": { - "hwlab-box-simu": 2, - "hwlab-gateway-simu": 2, - "hwlab-patch-panel": 1, - "wiring": "box-simu-1 DO1 -> box-simu-2 DI1" - }, - "observed": { - "hwlab-box-simu": { - "replicas": 2, - "env": { - "HWLAB_BOX_ID": "boxsimu_1,boxsimu_2" - } - }, - "hwlab-gateway-simu": { - "replicas": 2, - "env": { - "HWLAB_BOX_RESOURCES": "res_boxsimu_1,res_boxsimu_2" - } - }, - "hwlab-patch-panel": { - "replicas": 1, - "env": {} - } - } - }, - "validationCommand": "node scripts/validate-dev-m3-cardinality.mjs", - "requiredFollowUp": "Static DEV manifest cardinality is source-ready; live DEV M3 evidence still depends on edge/runtime reachability and direct DEV simulator targets." - } - ], - "localRuntimeObservations": [ - { - "id": "docker-hwlab-containers", - "status": "not_observed", - "command": "docker ps --format {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}", - "summary": "No running docker container name/image/port line contained hwlab-." - }, - { - "id": "kubectl-hwlab-dev", - "status": "observed", - "command": "kubectl get deploy,po,svc -n hwlab-dev -o wide", - "summary": "NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR deployment.apps/hwlab-agent-mgr 1/1 1 1 13h hwlab-agent-mgr 127.0.0.1:5000/hwlab/hwlab-agent-mgr:cb35ada app.kubernetes.io/name=hwlab-agent-mgr,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-agent-skills 1/1 1 1 10h hwlab-agent-skills 127.0.0.1:5000/hwlab/hwlab-agent-skills:cb35ada app.kubernetes.io/name=hwlab-agent-skills,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-box-simu 2/2 2 2 13h hwlab-box-simu 127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada app.kubernetes.io/name=hwlab-box-simu,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-cloud-api 1/1 1 1 13h hwlab-cloud-api 127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474 app.kubernetes.io/name=hwlab-cloud-api,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-cloud-web 1/1 1 1 13h hwlab-cloud-web 127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474 app.kubernetes.io/name=hwlab-cloud-web,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-edge-proxy 1/1 1 1 13h hwlab-edge-proxy 127.0.0.1:5000/hwlab/hwlab-edge-proxy:cb35ada app.kubernetes.io/name=hwlab-edge-proxy,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-frpc 1/1 1 1 13h frpc 127.0.0.1:5000/hwlab/frpc:v0.68.1 app.kubernetes.io/name=hwlab-frpc deployment.apps/hwlab-gateway 0/0 0 0 13h hwlab-gateway 127.0.0.1:5000/hwlab/hwlab-gateway:cb35ada app.kubernetes.io/name=hwlab-gateway,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-gateway-simu 2/2 2 2 13h hwlab-gateway-simu 127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada app.kubernetes.io/name=hwlab-gateway-simu,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.apps/hwlab-patch-panel 1/1 1 1 13h hwlab-patch-panel 127.0.0.1:5000/hwlab/hwlab-patch-panel:cb35ada app.kubernetes.io/name=hwlab-patch-panel,app.kubernetes.io/part-of=hwlab,hwlab.pikastech.local/environment=dev,hwlab.pikastech.local/profile=dev deployment.app" - }, - { - "id": "runner-k3s-kubeconfig-readonly", - "status": "observed", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods -o name", - "summary": "Runner /etc/rancher/k3s/k3s.yaml is not readable through fs access, but read-only kubectl observation succeeds; file readability, kubectl reachability, and service discovery are recorded separately. runnerKubeconfigReadable=false; exitCode=0; stderr=empty." - } - ], - "d601Observability": { - "runnerKubeconfigReadable": false, - "runnerKubeconfigProbeOk": true, - "runnerKubeconfigProbeExitCode": 0, - "runnerKubeconfigProbeStderr": "empty", - "d601PublicEndpointsReachable": true, - "d601K3sUnavailable": false, - "classification": "read_only_kubectl_available_kubeconfig_file_unreadable", - "sourceIssue": "pikasTech/HWLAB#46", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods -o name", - "stdoutSummary": "13 pod name(s) observed", - "summary": "Runner /etc/rancher/k3s/k3s.yaml is not readable through fs access, but read-only kubectl observation succeeds; file readability, kubectl reachability, and service discovery are recorded separately.", - "inferenceRule": "runnerKubeconfigReadable=false must not imply d601K3sUnavailable=true; public 16666/16667 endpoint reachability is tracked separately.", - "secretValuesRead": false, - "secretResourcesRead": false - }, - "liveOperation": { - "status": "not_run", - "operationId": "not_observed", - "traceId": "not_observed", - "auditId": "not_observed", - "evidenceId": "not_observed", - "summary": "No live DEV operation was attempted because read-only direct target checks did not prove the required patch-panel-owned M3 route." - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "m3-box-simu-identity", - "status": "open", - "classification": "direct_target_identity_gap", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1." - }, - { - "type": "runtime_blocker", - "scope": "m3-gateway-simu-identity", - "status": "open", - "classification": "direct_target_identity_gap", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV exposes two gateway-simu endpoints, but live gateway identities are not distinct; observed identities=gateway-a:gws_gateway-a." - }, - { - "type": "runtime_blocker", - "scope": "m3-patch-panel-wiring", - "status": "open", - "classification": "direct_target_m3_wiring_missing", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV patch-panel is callable, but live wiring does not contain res_boxsimu_1:DO1 -> res_boxsimu_2:DI1; active=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0; configured=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0." - }, - { - "type": "safety_blocker", - "scope": "m3-live-write-authorization", - "status": "open", - "classification": "dry_run_plan_only", - "summary": "Plan-only mode is intentionally non-mutating; run the bounded live smoke only after explicit DEV/non-PROD approval and after read-only preconditions identify the exact HWLAB targets." - } - ], - "summary": { - "status": "blocked", - "classification": "direct_target_identity_gap", - "observedAt": "2026-05-22T13:44:36.422Z", - "result": "DEV ingress and direct targets were reachable, but M3 DEV-LIVE was not triggered because required identities or patch-panel wiring were missing." - }, - "directTargetDiscovery": { - "environmentMissing": [ - "HWLAB_DEV_BOX_SIMU_1_URL", - "HWLAB_DEV_BOX_SIMU_2_URL", - "HWLAB_DEV_GATEWAY_SIMU_1_URL", - "HWLAB_DEV_GATEWAY_SIMU_2_URL", - "HWLAB_DEV_PATCH_PANEL_URL" - ], - "kubernetes": { - "status": "observed", - "source": "kubernetes-endpointslices", - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get endpointslices.discovery.k8s.io -o json", - "exitCode": 0, - "services": { - "hwlab-box-simu": { - "endpointSlice": "hwlab-box-simu-qssf6", - "port": 7201, - "count": 2, - "endpoints": [ - { - "url": "http://10.42.0.200:7201", - "address": "10.42.0.200", - "port": 7201, - "targetRef": "hwlab-box-simu-55688cbcc7-pftxc" - }, - { - "url": "http://10.42.0.208:7201", - "address": "10.42.0.208", - "port": 7201, - "targetRef": "hwlab-box-simu-55688cbcc7-sq5fx" - } - ] - }, - "hwlab-gateway-simu": { - "endpointSlice": "hwlab-gateway-simu-dmm4m", - "port": 7101, - "count": 2, - "endpoints": [ - { - "url": "http://10.42.0.204:7101", - "address": "10.42.0.204", - "port": 7101, - "targetRef": "hwlab-gateway-simu-699fd486b-phcgp" - }, - { - "url": "http://10.42.0.209:7101", - "address": "10.42.0.209", - "port": 7101, - "targetRef": "hwlab-gateway-simu-699fd486b-j9pk7" - } - ] - }, - "hwlab-patch-panel": { - "endpointSlice": "hwlab-patch-panel-nl2px", - "port": 7301, - "count": 1, - "endpoints": [ - { - "url": "http://10.42.0.205:7301", - "address": "10.42.0.205", - "port": 7301, - "targetRef": "hwlab-patch-panel-757f9f44d5-7np8r" - } - ] - } - }, - "summary": "Observed endpoint slices for hwlab-box-simu, hwlab-gateway-simu, hwlab-patch-panel." - }, - "source": "kubernetes-endpointslices", - "counts": { - "boxSimu": 2, - "gatewaySimu": 2, - "patchPanel": 1, - "distinctBoxResources": 1, - "distinctGatewayIdentities": 1 - }, - "selected": { - "boxSimu1": { - "id": "box-simu-1-candidate-1", - "serviceId": "hwlab-box-simu", - "baseUrl": "http://10.42.0.200:7201", - "targetRef": "hwlab-box-simu-55688cbcc7-pftxc", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": "boxsimu_1", - "resourceId": "res_boxsimu_1", - "gatewayId": null, - "gatewaySessionId": "gws_mvp_simu", - "patchPanelState": null - }, - "boxSimu2": null, - "gateways": [ - { - "id": "gateway-simu-1-candidate-1", - "serviceId": "hwlab-gateway-simu", - "baseUrl": "http://10.42.0.204:7101", - "targetRef": "hwlab-gateway-simu-699fd486b-phcgp", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": null, - "resourceId": null, - "gatewayId": "gateway-a", - "gatewaySessionId": "gws_gateway-a", - "patchPanelState": null - } - ], - "patchPanel": { - "id": "patch-panel-candidate-1", - "serviceId": "hwlab-patch-panel", - "baseUrl": "http://10.42.0.205:7301", - "targetRef": "hwlab-patch-panel-757f9f44d5-7np8r", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": null, - "resourceId": null, - "gatewayId": null, - "gatewaySessionId": "gws_gwsimu_east", - "patchPanelState": "active" - } - }, - "candidates": [ - { - "id": "box-simu-1-candidate-1", - "serviceId": "hwlab-box-simu", - "baseUrl": "http://10.42.0.200:7201", - "targetRef": "hwlab-box-simu-55688cbcc7-pftxc", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": "boxsimu_1", - "resourceId": "res_boxsimu_1", - "gatewayId": null, - "gatewaySessionId": "gws_mvp_simu", - "patchPanelState": null - }, - { - "id": "box-simu-1-candidate-2", - "serviceId": "hwlab-box-simu", - "baseUrl": "http://10.42.0.208:7201", - "targetRef": "hwlab-box-simu-55688cbcc7-sq5fx", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": "boxsimu_1", - "resourceId": "res_boxsimu_1", - "gatewayId": null, - "gatewaySessionId": "gws_mvp_simu", - "patchPanelState": null - }, - { - "id": "gateway-simu-1-candidate-1", - "serviceId": "hwlab-gateway-simu", - "baseUrl": "http://10.42.0.204:7101", - "targetRef": "hwlab-gateway-simu-699fd486b-phcgp", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": null, - "resourceId": null, - "gatewayId": "gateway-a", - "gatewaySessionId": "gws_gateway-a", - "patchPanelState": null - }, - { - "id": "gateway-simu-1-candidate-2", - "serviceId": "hwlab-gateway-simu", - "baseUrl": "http://10.42.0.209:7101", - "targetRef": "hwlab-gateway-simu-699fd486b-j9pk7", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": null, - "resourceId": null, - "gatewayId": "gateway-a", - "gatewaySessionId": "gws_gateway-a", - "patchPanelState": null - }, - { - "id": "patch-panel-candidate-1", - "serviceId": "hwlab-patch-panel", - "baseUrl": "http://10.42.0.205:7301", - "targetRef": "hwlab-patch-panel-757f9f44d5-7np8r", - "ok": true, - "statusCode": 200, - "healthCode": 200, - "boxId": null, - "resourceId": null, - "gatewayId": null, - "gatewaySessionId": "gws_gwsimu_east", - "patchPanelState": "active" - } - ], - "patchPanelWiring": { - "url": "http://10.42.0.205:7301/wiring", - "ok": true, - "requiredConnection": { - "fromResourceId": "res_boxsimu_1", - "fromPort": "DO1", - "toResourceId": "res_boxsimu_2", - "toPort": "DI1" - }, - "activeObserved": [ - "res_boxsim_alpha:uart0->res_boxsim_beta:uart0", - "res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0" - ], - "configuredObserved": [ - "res_boxsim_alpha:uart0->res_boxsim_beta:uart0", - "res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0" - ], - "hasRequiredActiveConnection": false, - "hasRequiredConfiguredConnection": false, - "configSource": "internal:mvp-topology", - "syncableConnectionCount": 0 - }, - "summary": "Direct DEV M3 targets were probed read-only, but required identities or patch-panel M3 wiring were not satisfied." - }, - "dryRunPlan": { - "mode": "plan-only", - "evidenceLevel": "DRY-RUN", - "route": "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", - "dryRunCallsLiveEndpoints": false, - "liveWriteWillRun": false, - "liveWriteRequiresHumanApproval": true, - "liveCommand": "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod", - "legacyLiveCommand": "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", - "liveWritePreconditions": [ - "operator has authorized a bounded DEV M3 live smoke window", - "command includes --live, --confirm-dev, and --expect-non-prod", - "PROD, real hardware, secret reads, service restarts, and force pushes remain forbidden", - "DEV ingress identifies HWLAB DEV on the frozen 16666/16667 boundary", - "two distinct hwlab-box-simu targets report res_boxsimu_1 and res_boxsimu_2", - "two distinct hwlab-gateway-simu targets report distinct DEV gateway sessions", - "one hwlab-patch-panel reports active wiring for res_boxsimu_1:DO1 -> res_boxsimu_2:DI1", - "cross-device propagation is owned by hwlab-patch-panel; box loopback, SOURCE, LOCAL, fixture, and DRY-RUN output cannot be marked DEV-LIVE" - ], - "endpointPlan": [ - { - "phase": "dev-ingress-precondition", - "targetId": "dev-api-edge", - "serviceId": "hwlab-cloud-api", - "method": "GET", - "path": "/health/live", - "url": "http://74.48.78.17:16667/health/live", - "urlSource": "frozen-dev-endpoint", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "dev-ingress-precondition", - "targetId": "dev-api-edge-json-rpc", - "serviceId": "hwlab-cloud-api", - "method": "POST", - "path": "/json-rpc", - "url": "http://74.48.78.17:16667/json-rpc", - "urlSource": "frozen-dev-endpoint", - "mutatesDevState": false, - "calledInDryRun": false, - "requestShape": "system.health" - }, - { - "phase": "dev-ingress-precondition", - "targetId": "dev-frontend", - "serviceId": "hwlab-cloud-web", - "method": "GET", - "path": "/health/live", - "url": "http://74.48.78.17:16666/health/live", - "urlSource": "frozen-dev-frontend-endpoint", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "box-simu-1", - "serviceId": "hwlab-box-simu", - "method": "GET", - "path": "/health/live", - "url": "$HWLAB_DEV_BOX_SIMU_1_URL/health/live", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_1_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "box-simu-1", - "serviceId": "hwlab-box-simu", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_BOX_SIMU_1_URL/status", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_1_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "box-simu-2", - "serviceId": "hwlab-box-simu", - "method": "GET", - "path": "/health/live", - "url": "$HWLAB_DEV_BOX_SIMU_2_URL/health/live", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_2_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "box-simu-2", - "serviceId": "hwlab-box-simu", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_BOX_SIMU_2_URL/status", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_2_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "gateway-simu-1", - "serviceId": "hwlab-gateway-simu", - "method": "GET", - "path": "/health/live", - "url": "$HWLAB_DEV_GATEWAY_SIMU_1_URL/health/live", - "urlSource": "required-env:HWLAB_DEV_GATEWAY_SIMU_1_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "gateway-simu-1", - "serviceId": "hwlab-gateway-simu", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_GATEWAY_SIMU_1_URL/status", - "urlSource": "required-env:HWLAB_DEV_GATEWAY_SIMU_1_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "gateway-simu-2", - "serviceId": "hwlab-gateway-simu", - "method": "GET", - "path": "/health/live", - "url": "$HWLAB_DEV_GATEWAY_SIMU_2_URL/health/live", - "urlSource": "required-env:HWLAB_DEV_GATEWAY_SIMU_2_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "gateway-simu-2", - "serviceId": "hwlab-gateway-simu", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_GATEWAY_SIMU_2_URL/status", - "urlSource": "required-env:HWLAB_DEV_GATEWAY_SIMU_2_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "patch-panel", - "serviceId": "hwlab-patch-panel", - "method": "GET", - "path": "/health/live", - "url": "$HWLAB_DEV_PATCH_PANEL_URL/health/live", - "urlSource": "required-env:HWLAB_DEV_PATCH_PANEL_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "direct-target-precondition", - "targetId": "patch-panel", - "serviceId": "hwlab-patch-panel", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_PATCH_PANEL_URL/status", - "urlSource": "required-env:HWLAB_DEV_PATCH_PANEL_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "patch-panel-route-precondition", - "targetId": "patch-panel", - "serviceId": "hwlab-patch-panel", - "method": "GET", - "path": "/wiring", - "url": "$HWLAB_DEV_PATCH_PANEL_URL/wiring", - "urlSource": "required-env:HWLAB_DEV_PATCH_PANEL_URL", - "mutatesDevState": false, - "calledInDryRun": false - }, - { - "phase": "live-write-source-do1", - "targetId": "box-simu-1", - "serviceId": "hwlab-box-simu", - "method": "POST", - "path": "/ports/write", - "url": "$HWLAB_DEV_BOX_SIMU_1_URL/ports/write", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_1_URL", - "mutatesDevState": true, - "calledInDryRun": false - }, - { - "phase": "live-write-patch-panel-route", - "targetId": "patch-panel", - "serviceId": "hwlab-patch-panel", - "method": "POST", - "path": "/signals/route", - "url": "$HWLAB_DEV_PATCH_PANEL_URL/signals/route", - "urlSource": "required-env:HWLAB_DEV_PATCH_PANEL_URL", - "mutatesDevState": true, - "calledInDryRun": false - }, - { - "phase": "live-read-target-di1", - "targetId": "box-simu-2", - "serviceId": "hwlab-box-simu", - "method": "GET", - "path": "/status", - "url": "$HWLAB_DEV_BOX_SIMU_2_URL/status", - "urlSource": "required-env:HWLAB_DEV_BOX_SIMU_2_URL", - "mutatesDevState": false, - "calledInDryRun": false - } - ], - "evidenceFields": [ - "operationId", - "traceId", - "auditId", - "evidenceId", - "sourceResourceId=res_boxsimu_1", - "sourcePort=DO1", - "routeOwner=hwlab-patch-panel", - "routeResponse.propagatedBy=hwlab-patch-panel", - "targetResourceId=res_boxsimu_2", - "targetPort=DI1", - "targetState.ports.DI1.propagatedBy=hwlab-patch-panel" - ], - "refusalPolicy": [ - "no arguments defaults to refusal", - "--dry-run/--plan cannot be combined with --live/--allow-live", - "live mode refuses without --confirm-dev and --expect-non-prod", - "a dry-run, source, local, fixture, or read-only blocker report leaves liveOperation.status as not_run or blocked, never pass" - ] - } -} diff --git a/reports/dev-gate/dev-m4-agent-loop.json b/reports/dev-gate/dev-m4-agent-loop.json deleted file mode 100644 index b91beabb..00000000 --- a/reports/dev-gate/dev-m4-agent-loop.json +++ /dev/null @@ -1,593 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-m4-agent-loop.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "commitId": "d881a50", - "acceptanceLevel": "dev_m4_agent_loop", - "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 DEV M4 agent-loop preflight report; it does not substitute for M3 DEV-LIVE acceptance." - }, - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-acceptance-matrix.md", - "docs/dev-m4-agent-loop.md" - ], - "summary": "The DEV M4 report is anchored to the frozen acceptance matrix and the M4 loop doc." - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-m4-agent-loop-smoke.mjs", - "node --check scripts/src/dev-m4-agent-loop-smoke-lib.mjs", - "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run", - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods,svc,deploy,job,cm -o wide", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev create -f - --dry-run=server -o json" - ], - "localSmoke": { - "status": "pass", - "commands": [ - "node scripts/m4-agent-loop-smoke.mjs" - ], - "evidence": [ - "Local runtime skeleton smoke confirms create/start/trace/finish/cleanup coverage.", - "Workspace isolation and explicit skills commit wiring are covered by fixture assertions." - ], - "summary": "Local contract smoke passes on the repo fixture." - }, - "dryRun": { - "status": "pass", - "commands": [ - "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run" - ], - "evidence": [ - "hwlab-agent-mgr:session=agt_m4_agent_loop_01:created=queued:final=cleanup", - "hwlab-agent-worker:session=wkr_agt_m4_agent_loop_01:status=cleanup", - "workspace:vol_agt_m4_agent_loop_01:cleaned=true:existsAfterCleanup=false", - "hwlab-agent-skills:commit=6509a35:version=v1", - "trace:trc_agt_m4_agent_loop_01:events=5", - "evidence:evi_agt_m4_agent_loop_01:kind=report", - "cleanup:cln_agt_m4_agent_loop_01:removed=true" - ], - "summary": "Local dry-run covered agent manager, worker, workspace isolation, skills commit, trace, evidence, and cleanup without live DEV mutation." - }, - "componentEvidence": { - "agentManagerWorker": { - "level": "DRY-RUN", - "status": "pass", - "services": [ - "hwlab-agent-mgr", - "hwlab-agent-worker" - ], - "summary": "Manager created a scoped local session and worker completed the bounded dry-run task." - }, - "workspaceIsolation": { - "level": "DRY-RUN", - "status": "pass", - "evidence": [ - "primary=prj_m4_agent_loop/agt_m4_agent_loop_01", - "isolation=prj_m4_agent_loop_alt/agt_m4_agent_loop_02" - ], - "summary": "Workspace identity is per agent session and fixture isolation remains source/local evidence only." - }, - "skillsCommit": { - "level": "DRY-RUN", - "status": "pass", - "serviceId": "hwlab-agent-skills", - "commitId": "6509a35", - "version": "v1", - "summary": "Agent skills are accepted only with explicit commitId and version wiring." - }, - "traceEvidenceCleanup": { - "level": "DRY-RUN", - "status": "pass", - "summary": "Dry-run produced trace/evidence records and removed the temporary worker workspace." - }, - "dbLive": { - "level": "DEV-LIVE", - "status": "pass", - "summary": "Current read-only /health/live evidence reports DB ready=true, connected=true, and liveDbEvidence=true; DB live is no longer the active M4 blocker." - }, - "m3HardwareLoopDependency": { - "level": "BLOCKED", - "status": "not_run", - "requiredPath": "DO1 -> hwlab-patch-panel -> DI1", - "summary": "M4 can only support M3 through cloud-api/hardware API and the patch-panel trusted path; no direct box/gateway path is used." - }, - "runtimeDurableAdapter": { - "level": "BLOCKED", - "status": "blocked", - "summary": "Live scheduling is stopped at runtime durable adapter readiness; DB live evidence alone is not runtime durability evidence." - } - }, - "dependencyState": { - "dbLive": { - "status": "pass", - "summary": "cloud-api /health/live reports DB ready=true, connected=true, and liveDbEvidence=true." - }, - "m3HardwareLoop": { - "status": "not_run", - "requiredEvidence": "Traceable DO1 -> hwlab-patch-panel -> DI1 operation with auditId and evidenceId.", - "summary": "M3 hardware loop was not reached while an earlier M4 precondition remained blocked." - }, - "hardwareDispatchBoundary": { - "status": "pass", - "allowedPath": "cloud-api /rpc hardware.operation.request -> hwlab-patch-panel trusted route", - "forbiddenPaths": [ - "direct hwlab-box-simu access", - "direct hwlab-gateway-simu access", - "agent-side fixture promotion to DEV-LIVE" - ] - }, - "runtimeDurableAdapter": { - "status": "blocked", - "requiredEvidence": "Postgres runtime adapter schema, migration ledger, and read-query readiness with liveRuntimeEvidence=true.", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton is missing or not checked; read-query durability is not proven." - } - }, - "d601K3sNative": { - "status": "blocked", - "mode": "dev-read-only-plus-server-dry-run", - "generatedAt": "2026-05-22T12:12:47.156Z", - "commands": [ - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get namespace hwlab-dev -o json", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get pods,svc,deploy,job,cm -o wide", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get deploy hwlab-agent-mgr hwlab-agent-skills -o json", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get job hwlab-agent-worker-template -o json", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get svc hwlab-agent-mgr hwlab-agent-skills -o json", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev get endpoints hwlab-agent-mgr hwlab-agent-skills -o json", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i create jobs -n hwlab-dev", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i delete jobs -n hwlab-dev", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl auth can-i get secrets -n hwlab-dev", - "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev create -f - --dry-run=server -o json" - ], - "safety": { - "namespace": "hwlab-dev", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "readOnly": true, - "prodTouched": false, - "secretResourcesRead": false, - "secretMaterialPrinted": false, - "servicesRestarted": false, - "workerJobPersisted": false, - "longRunningAgentStarted": false, - "note": "The smoke checks Secret RBAC only with kubectl auth can-i; it never reads Secret resources or kubeconfig contents." - }, - "clusterReadable": true, - "namespace": { - "name": "hwlab-dev", - "readable": true, - "error": null - }, - "rbac": { - "createJobs": true, - "deleteJobs": true, - "getSecrets": true, - "secretReadNotPerformed": true - }, - "agentManager": { - "deployment": { - "name": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:d881a50", - "readyReplicas": 1, - "replicas": 1, - "available": "True", - "ready": true, - "env": { - "values": { - "HWLAB_AGENT_SESSION_MODE": "control-plane" - }, - "valueFrom": [] - } - }, - "endpointCount": 1, - "health": { - "statusCode": 200, - "status": "degraded", - "serviceId": "hwlab-agent-mgr", - "missingSkills": [ - "manager.skillCommitId", - "manager.skillVersion" - ], - "error": null - } - }, - "agentSkills": { - "deployment": { - "name": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:d881a50", - "readyReplicas": 1, - "replicas": 1, - "available": "True", - "ready": true, - "env": { - "values": { - "HWLAB_SKILLS_COMMIT_ID": "d881a50" - }, - "valueFrom": [] - } - }, - "endpointCount": 1, - "health": { - "statusCode": 200, - "status": "ok", - "serviceId": "hwlab-agent-skills", - "revision": "d881a50", - "error": null - } - }, - "workerJob": { - "template": { - "name": "hwlab-agent-worker-template", - "suspend": true, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:d881a50", - "restartPolicy": "Never", - "env": { - "values": { - "HWLAB_AGENT_SESSION_MODE": "session-scoped" - }, - "valueFrom": [] - } - }, - "serverDryRun": { - "status": "pass", - "accepted": true, - "command": "KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-dev create -f - --dry-run=server -o json", - "persisted": false, - "generatedName": "hwlab-agent-worker-smoke-dryrun-20260522", - "injectedEnvNames": [ - "HWLAB_AGENT_SESSION_ID", - "HWLAB_AGENT_SESSION_MODE", - "HWLAB_PROJECT_ID", - "HWLAB_SKILL_COMMIT_ID", - "HWLAB_SKILL_VERSION", - "HWLAB_WORKSPACE_ROOT" - ], - "dryRunKind": "Job", - "dryRunSuspend": true, - "summary": "Server-side admission accepted a suspended worker Job generated from the template with session/workspace/skills env injected; no Job was persisted." - } - }, - "skillsInjection": { - "status": "blocked", - "source": "k3s deployment env plus generated worker dry-run manifest", - "serviceCommitId": "d881a50", - "serviceVersion": null, - "workerDryRunCommitId": "d881a50", - "workerDryRunVersion": "", - "workerDryRunSource": "derived-from-live-dev-skills-env", - "missing": [ - "hwlab-agent-skills.HWLAB_SKILLS_VERSION", - "worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV" - ] - }, - "traceEvidenceCleanup": { - "status": "dry-run-only", - "summary": "Live worker execution was not started. Trace/evidence/cleanup closure is covered by the bounded local dry-run and remains explicitly non-M3 DEV-LIVE." - }, - "evidence": [ - "k3s:namespace=hwlab-dev:read=true", - "k3s:deploy/hwlab-agent-mgr:ready=1/1:available=True", - "k3s:deploy/hwlab-agent-skills:ready=1/1:available=True", - "k3s:svc/hwlab-agent-mgr:endpoints=1", - "k3s:svc/hwlab-agent-skills:endpoints=1", - "agent-mgr:/health/live:degraded:missing=manager.skillCommitId,manager.skillVersion", - "agent-skills:/health/live:ok:d881a50", - "worker-template:suspend=true:image=127.0.0.1:5000/hwlab/hwlab-agent-worker:d881a50", - "worker-job:server-dry-run=true:persisted=false", - "skills-injection:commit=d881a50:version=missing", - "secret-rbac:get=yes:secretResourcesRead=false" - ], - "blockers": [ - { - "type": "agent_blocker", - "scope": "agent-mgr-health", - "status": "open", - "summary": "hwlab-agent-mgr /health/live is degraded.", - "classification": "agent runtime", - "nextFix": "Inject explicit skill commit and version into agent-mgr health configuration, then redeploy DEV." - }, - { - "type": "agent_blocker", - "scope": "skills-commit-version-injection", - "status": "open", - "summary": "DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV.", - "classification": "skills injection", - "nextFix": "Add HWLAB_SKILLS_VERSION to hwlab-agent-skills and ensure scheduled worker jobs receive HWLAB_SKILL_COMMIT_ID plus HWLAB_SKILL_VERSION." - } - ], - "minimumFixRecommendations": [ - "Inject explicit skill commit and version into agent-mgr health configuration, then redeploy DEV.", - "Add HWLAB_SKILLS_VERSION to hwlab-agent-skills and ensure scheduled worker jobs receive HWLAB_SKILL_COMMIT_ID plus HWLAB_SKILL_VERSION." - ] - }, - "publicEntrypoints": { - "status": "pass", - "commands": [ - "curl -fsS --max-time 10 http://74.48.78.17:16667/health", - "curl -fsS --max-time 10 http://74.48.78.17:16667/health/live", - "curl -fsS -I --max-time 10 http://74.48.78.17:16666/" - ], - "api": { - "endpoint": "http://74.48.78.17:16667", - "health": { - "statusCode": 200, - "serviceId": "hwlab-edge-proxy", - "environment": "dev", - "status": "ok", - "error": null - }, - "live": { - "statusCode": 200, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "status": "degraded", - "db": { - "status": "ok", - "connected": true, - "liveConnected": true, - "ready": true, - "configReady": true, - "connectionChecked": true, - "connectionAttempted": true, - "connectionResult": "connected", - "endpointSource": "secret-url-host", - "liveDbEvidence": true, - "evidence": "live_db_tcp_connection_ready" - }, - "error": null, - "codeAgent": { - "endpoint": "POST /v1/agent/chat", - "status": "available", - "ready": true, - "provider": "openai-responses", - "schema": [ - "conversationId", - "sessionId", - "messageId", - "status", - "createdAt", - "updatedAt", - "traceId", - "provider", - "model", - "backend", - "error.message" - ] - }, - "runtime": { - "adapter": "postgres", - "durable": false, - "durableRequested": true, - "durableCapable": false, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_query_blocked", - "reason": "Postgres runtime adapter is configured, but durable read-query readiness is blocked; migration 0001_cloud_core_skeleton is missing or not checked.", - "liveRuntimeEvidence": false, - "fixtureEvidence": false, - "connection": { - "queryAttempted": true, - "queryResult": "query_blocked", - "endpointRedacted": true, - "valueRedacted": true, - "errorCode": "not_observed" - }, - "schema": { - "checked": true, - "ready": true, - "missingTables": [], - "missingColumns": [] - }, - "migration": { - "checked": false, - "ready": false, - "missing": true, - "requiredMigrationId": "0001_cloud_core_skeleton", - "requiredSchemaVersion": "runtime-durable-postgres-v1", - "appliedMigrationId": null, - "appliedSchemaVersion": null - }, - "gates": { - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": false, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_migration_blocked" - }, - "durability": { - "checked": true, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_query_blocked" - } - }, - "durabilityContract": { - "ready": false, - "status": "blocked", - "blockedLayer": "durability_query", - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "dbLiveEvidenceIsDurabilityEvidence": false, - "secretMaterialRead": false - }, - "counts": {} - }, - "readiness": { - "contractVersion": "v3", - "ready": false, - "status": "degraded", - "blockerCodes": [ - "runtime_durable_adapter_query_blocked" - ], - "components": { - "db": "ready", - "codeAgent": "ready", - "runtime": "blocked" - }, - "durability": { - "status": "blocked", - "ready": false, - "adapter": "postgres", - "durable": false, - "durableRequested": true, - "durableCapable": false, - "runtimeStatus": "blocked", - "runtimeReady": false, - "liveRuntimeEvidence": false, - "dbLiveEvidenceObserved": true, - "dbLiveEvidenceIsDurabilityEvidence": false, - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "blockedLayer": "durability_query", - "blocker": "runtime_durable_adapter_query_blocked", - "gates": { - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": false, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_migration_blocked" - }, - "durability": { - "checked": true, - "ready": false, - "status": "blocked", - "blocker": "runtime_durable_adapter_query_blocked" - } - }, - "queryAttempted": true, - "queryResult": "query_blocked", - "secretMaterialRead": false - } - }, - "blockerCodes": [ - "runtime_durable_adapter_query_blocked" - ] - } - }, - "frontend": { - "endpoint": "http://74.48.78.17:16666", - "statusCode": 200, - "contentType": "text/html; charset=utf-8", - "contentLength": "11180", - "error": null - }, - "summary": "Public DEV API http://74.48.78.17:16667 health=200 live=200 with DB live ready and runtime durable adapter blocked; frontend http://74.48.78.17:16666/=200." - }, - "safetyEvidence": { - "devOnly": true, - "namespace": "hwlab-dev", - "kubeconfig": "/etc/rancher/k3s/k3s.yaml", - "prodTouched": false, - "prodNamespaceTouched": false, - "secretMaterialRead": false, - "secretResourcesRead": false, - "servicesRestarted": false, - "longRunningAgentStarted": false, - "workerJobPersisted": false, - "notes": [ - "kubectl commands were scoped to hwlab-dev and /etc/rancher/k3s/k3s.yaml.", - "Secret RBAC was checked with kubectl auth can-i only; no Secret resource or secret value was read.", - "Worker createability used server-side dry-run with suspend=true and did not persist a Job." - ] - }, - "devPreconditions": { - "status": "blocked", - "classification": "runtime_durable_adapter_query_blocked", - "requirements": [ - "DEV route remains frozen at http://74.48.78.17:16667", - "DEV browser route remains frozen at http://74.48.78.17:16666", - "D601 native k3s access uses KUBECONFIG=/etc/rancher/k3s/k3s.yaml and namespace hwlab-dev", - "No secret reads, real deployment, or long-running agent task is attempted", - "DB live readiness and runtime durable adapter readiness are separate gates; DB live alone cannot promote M4", - "Agent manager, worker, and skills are reachable only through the cloud API boundary", - "M4 hardware assistance must go through cloud-api/hardware API and patch-panel; direct box/gateway access is forbidden" - ], - "summary": "Blocked at runtime durable adapter readiness before scheduling a DEV agent task." - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "runtime-durable-adapter", - "status": "open", - "sourceIssue": "pikasTech/HWLAB#164", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; adapter=postgres; durable=false; ready=false; migration=0001_cloud_core_skeleton checked=false ready=false missing=true; queryResult=query_blocked.", - "classification": "runtime_durable_adapter_query_blocked" - }, - { - "status": "open", - "type": "agent_blocker", - "scope": "agent-mgr-health", - "summary": "hwlab-agent-mgr /health/live is degraded.", - "classification": "agent runtime", - "nextFix": "Inject explicit skill commit and version into agent-mgr health configuration, then redeploy DEV." - }, - { - "status": "open", - "type": "agent_blocker", - "scope": "skills-commit-version-injection", - "summary": "DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV.", - "classification": "skills injection", - "nextFix": "Add HWLAB_SKILLS_VERSION to hwlab-agent-skills and ensure scheduled worker jobs receive HWLAB_SKILL_COMMIT_ID plus HWLAB_SKILL_VERSION." - } - ], - "livePreflight": { - "status": "blocked", - "classification": "runtime_durable_adapter_query_blocked", - "commands": [ - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "k3s:namespace=hwlab-dev:read=true", - "k3s:deploy/hwlab-agent-mgr:ready=1/1:available=True", - "k3s:deploy/hwlab-agent-skills:ready=1/1:available=True", - "k3s:svc/hwlab-agent-mgr:endpoints=1", - "k3s:svc/hwlab-agent-skills:endpoints=1", - "agent-mgr:/health/live:degraded:missing=manager.skillCommitId,manager.skillVersion", - "agent-skills:/health/live:ok:d881a50", - "worker-template:suspend=true:image=127.0.0.1:5000/hwlab/hwlab-agent-worker:d881a50", - "worker-job:server-dry-run=true:persisted=false", - "skills-injection:commit=d881a50:version=missing", - "secret-rbac:get=yes:secretResourcesRead=false", - "Public DEV API http://74.48.78.17:16667 health=200 live=200 with DB live ready and runtime durable adapter blocked; frontend http://74.48.78.17:16666/=200.", - "health:hwlab-edge-proxy:dev:ok", - "live:hwlab-cloud-api:degraded:runtime-durable-adapter-blocked:runtime_durable_adapter_query_blocked", - "db-live:ready=true:connected=true:liveDbEvidence=true", - "runtime-durable-adapter:adapter=postgres:blocker=runtime_durable_adapter_query_blocked:migration=0001_cloud_core_skeleton:migrationChecked=false:migrationReady=false:queryResult=query_blocked" - ], - "summary": "Blocked at runtime durable adapter readiness before scheduling a DEV agent task." - }, - "notes": "DEV M4 agent loop report. SOURCE/LOCAL/DRY-RUN evidence is not DEV-LIVE, and no secret material is read." -} diff --git a/reports/dev-gate/dev-m5-gate-aggregator-v2.json b/reports/dev-gate/dev-m5-gate-aggregator-v2.json deleted file mode 100644 index 05c048d8..00000000 --- a/reports/dev-gate/dev-m5-gate-aggregator-v2.json +++ /dev/null @@ -1,1539 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-m5-gate-aggregator-v2.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-m5-gate-aggregator-v2.json", - "reportVersion": "v2", - "reportKind": "dev-m5-gate-aggregator", - "issue": "pikasTech/HWLAB#58", - "supports": [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#9", - "pikasTech/HWLAB#23", - "pikasTech/HWLAB#26", - "pikasTech/HWLAB#31", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#34", - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#36", - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#46", - "pikasTech/HWLAB#64" - ], - "generatedAt": "2026-05-23T06:17:15.938Z", - "generatedFromCommit": "66586ccf109e", - "environment": "dev", - "endpoint": "http://74.48.78.17:16667", - "frontendEndpoint": "http://74.48.78.17:16666", - "activeEndpoints": { - "frontend": "http://74.48.78.17:16666/", - "apiLive": "http://74.48.78.17:16667/health/live" - }, - "deprecatedEndpoints": [ - { - "endpoint": "http://74.48.78.17:6666", - "status": "historical/deprecated", - "activeGreenEligible": false - }, - { - "endpoint": "http://74.48.78.17:6667", - "status": "historical/deprecated", - "activeGreenEligible": false - } - ], - "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 M5 evidence aggregator report; source, local, and dry-run evidence are not DEV-LIVE acceptance." - }, - "safety": { - "reportOnly": true, - "noDeploy": true, - "noProd": true, - "noSecretRead": true, - "noRuntimeRestart": true, - "noLiveProbe": true, - "noHeavyE2E": true, - "noUniDeskRuntimeSubstitute": true - }, - "sourceReports": { - "devPreflight": { - "path": "reports/dev-gate/dev-preflight-report.json", - "issue": "pikasTech/HWLAB#34", - "taskId": "dev-gate-preflight", - "lifecycleState": "active", - "status": "blocked", - "commitId": "f64182b54fe6" - }, - "devDeploy": { - "path": "reports/dev-gate/dev-deploy-report.json", - "issue": "pikasTech/HWLAB#33", - "taskId": "dev-deploy-apply", - "lifecycleState": "active", - "status": "pass", - "commitId": "7e29522" - }, - "devArtifacts": { - "path": "reports/dev-gate/dev-artifacts.json", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "lifecycleState": "active", - "status": "published", - "commitId": "7de6edd" - }, - "devEdgeHealth": { - "path": "reports/dev-gate/dev-edge-health.json", - "issue": "pikasTech/HWLAB#36", - "taskId": "dev-edge-health", - "lifecycleState": "active", - "status": "blocked", - "commitId": "c7de4745f491" - }, - "devM2Smoke": { - "path": "reports/dev-gate/dev-m2-deploy-smoke-active.json", - "issue": "pikasTech/HWLAB#23", - "taskId": "m2-dev-deploy-smoke", - "lifecycleState": "active", - "status": "pass", - "commitId": "8e89409dda5d" - }, - "devM3Hardware": { - "path": "reports/dev-gate/dev-m3-hardware-loop.json", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "lifecycleState": "active", - "status": "blocked", - "commitId": "c7de4745f491" - }, - "devM4Agent": { - "path": "reports/dev-gate/dev-m4-agent-loop.json", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "lifecycleState": "active", - "status": "blocked", - "commitId": "d881a50" - }, - "devM5Gate": { - "path": "reports/dev-gate/dev-mvp-gate-report.json", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "lifecycleState": "active", - "status": "blocked", - "commitId": "86d769525caf" - }, - "d601Observability": { - "path": "reports/d601-k3s-readonly-observability.json", - "issue": "pikasTech/HWLAB#46", - "taskId": "d601-k3s-readonly-observability", - "lifecycleState": "active", - "status": "blocked", - "commitId": "unknown" - } - }, - "overall": { - "status": "blocked", - "green": false, - "reason": "Frontend revision 1e8805664970839b72be40c34636b08f6d18b131 is usable in degraded/read-only mode and EDGE/ROUTE DEV-LIVE evidence exists on :16666/:16667, but M5 remains blocked by artifact source drift, runtime durable adapter or skills injection readiness, missing M3 trusted loop operation evidence, and blocked M4 agent-loop preflight." - }, - "latestFrontendDevFact": { - "revision": "1e8805664970839b72be40c34636b08f6d18b131", - "sources": [ - "docs/dev-deploy-apply.md", - "docs/cloud-web-workbench.md", - "web/hwlab-cloud-web/index.html", - "web/hwlab-cloud-web/styles.css" - ], - "evidence": [ - "Cloud Web /health/live observed revision 1e8805664970839b72be40c34636b08f6d18b131", - "Cloud Workbench public browser endpoint is usable in degraded/read-only mode only", - "Frontend load/revision evidence cannot satisfy DB, M3 hardware-loop, M4 agent-loop, or M5 MVP e2e acceptance" - ], - "commands": [ - "node web/hwlab-cloud-web/scripts/check.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source" - ], - "summary": "#99/#108 Cloud Workbench revision 1e8805664970839b72be40c34636b08f6d18b131 is the latest DEV frontend visibility fact; API/runtime durability remains degraded, so it is degraded/read-only UI evidence only.", - "issue": "pikasTech/HWLAB#99", - "supports": [ - "pikasTech/HWLAB#99", - "pikasTech/HWLAB#108", - "pikasTech/HWLAB#78" - ], - "endpoint": "http://74.48.78.17:16666/", - "evidenceLevel": "DEV-LIVE", - "promotesM3M4M5": false - }, - "dod": { - "status": "blocked", - "green": false, - "checks": [ - { - "id": "m0-source-contract", - "status": "pass", - "evidenceLevel": "SOURCE", - "summary": "M0 contract checks are source-level evidence only." - }, - { - "id": "m1-local-smoke", - "status": "pass", - "evidenceLevel": "LOCAL", - "summary": "M1 local smoke is not a live DEV substitute." - }, - { - "id": "artifact-publish-digests", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "artifactState=contract-skeleton, ciPublished=false, registryVerified=false, sha256=0, not_published=13, targetCovered=false" - }, - { - "id": "d601-k3s-observability", - "status": "blocked", - "evidenceLevel": "DEV-LIVE", - "summary": "D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false." - }, - { - "id": "dev-edge-frp-16667", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; this is route evidence, not DB/M3/M4/M5 acceptance." - }, - { - "id": "cloud-api-db-ready", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "cloud-api DB status=ok; ready=true; connected=true; endpointSource=secret-url-host; liveDbEvidence=true." - }, - { - "id": "m3-hardware-trusted-loop", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "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": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "M4 agent loop live path is blocked before accepted agent scheduling/evidence closure." - }, - { - "id": "m5-mvp-dev-live", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "M5 dry-run passed; bounded DEV-LIVE MVP e2e has not passed." - } - ] - }, - "currentDevLayering": { - "frontendRevision": { - "label": "Frontend DEV revision", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "http://74.48.78.17:16666/ serves the Cloud Workbench frontend revision 1e8805664970839b72be40c34636b08f6d18b131; deployed UI is usable in degraded/read-only mode only and this is browser/frontend evidence only.", - "evidence": [ - "Cloud Web /health/live observed revision 1e8805664970839b72be40c34636b08f6d18b131", - "Cloud Workbench public browser endpoint is usable in degraded/read-only mode only", - "Frontend load/revision evidence cannot satisfy DB, M3 hardware-loop, M4 agent-loop, or M5 MVP e2e acceptance" - ], - "nextRequired": "Keep frontend revision proof separate from DB live readiness, M3 hardware-loop evidence, M4 agent-loop evidence, and M5 acceptance." - }, - "edgeRoute": { - "label": "EDGE/ROUTE live", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "http://74.48.78.17:16666/, http://74.48.78.17:16667/health, and http://74.48.78.17:16667/health/live returned observed HWLAB DEV route responses in the active M2 read-only smoke.", - "evidence": [ - "http://74.48.78.17:16667/health -> HTTP 200 identity=hwlab-edge-proxy status=ok", - "http://74.48.78.17:16667/health/live -> HTTP 200 identity=hwlab-cloud-api status=degraded", - "http://74.48.78.17:16666/ -> HTTP 200 identity=HWLAB DEV MVP Gate status=ok" - ], - "nextRequired": "Keep this separated from DB readiness, M3/M4 loop evidence, and M5 acceptance." - }, - "dbLive": { - "label": "DB live/degraded", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "cloud-api DB status=ok; configReady=true; ready=true; connected=true; endpointSource=secret-url-host; liveDbEvidence=true.", - "evidence": [ - "http://74.48.78.17:16667/health/live -> HTTP 200 serviceId=hwlab-cloud-api status=degraded db.ready=true db.connected=true db.liveDbEvidence=true runtime.blocker=runtime_durable_adapter_query_blocked runtime.migration=0001_cloud_core_skeleton", - "dev-edge-health active report remains older DB-live evidence and is superseded for M4/M5 blocker naming by current d881a50 M4 preflight evidence" - ], - "nextRequired": "Provide live DB connection evidence through redacted health output; route reachability alone is insufficient." - }, - "runtimeDurableAdapter": { - "label": "Runtime durable adapter", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "runtime adapter=postgres; durableRequested=true; durable=false; ready=false; blocker=runtime_durable_adapter_query_blocked; migration=0001_cloud_core_skeleton checked=false ready=false; queryResult=query_blocked.", - "evidence": [ - "requiredEvidence=runtime_adapter_schema_migration_read_query", - "blockedLayer=durability_query", - "migration=0001_cloud_core_skeleton", - "queryAttempted=true" - ], - "nextRequired": "Repair the postgres durable runtime adapter query/migration readiness and rerun health/M4 preflight without treating DB live as durability proof." - }, - "agentSkillsInjection": { - "label": "Agent skills injection", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV.", - "evidence": [ - "skills.commit=d881a50", - "skills.version=missing", - "worker.commit=d881a50", - "worker.version=", - "missing=hwlab-agent-skills.HWLAB_SKILLS_VERSION,worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV,manager.skillCommitId,manager.skillVersion" - ], - "nextRequired": "Inject explicit skill commit and version into hwlab-agent-mgr, hwlab-agent-skills, and scheduled worker jobs before claiming M4/M5 agent evidence." - }, - "codeAgentProvider": { - "label": "Code Agent provider", - "status": "pass", - "evidenceLevel": "DEV-LIVE", - "summary": "code-agent provider=openai-responses; status=available; ready=true.", - "evidence": [ - "provider=openai-responses", - "status=available" - ], - "nextRequired": "Keep provider-backed Code Agent readiness separate from DB live and runtime durable adapter readiness." - }, - "d601RunnerObservability": { - "label": "D601 runner observability", - "status": "blocked", - "evidenceLevel": "DEV-LIVE", - "summary": "D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false.", - "evidence": [ - "runnerKubeconfigReadable=false", - "runnerKubeconfigProbeExitCode=0", - "runnerKubeconfigProbeStderr=empty", - "d601PublicEndpointsReachable=true", - "d601K3sUnavailable=false" - ], - "nextRequired": "Treat #46 runner kubeconfig/readonly gaps separately from D601 service health; rerun read-only observability after the mount or permission path is repaired." - }, - "m3HardwareTrustedLoop": { - "label": "M3 hardware trusted loop", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "No live DEV operation was attempted because read-only direct target checks did not prove the required patch-panel-owned M3 route. Blocker: DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1.", - "evidence": [ - "operationId=not_observed", - "traceId=not_observed", - "auditId=not_observed", - "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": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "Blocked at runtime durable adapter readiness before scheduling a DEV agent task.", - "evidence": [ - "k3s:namespace=hwlab-dev:read=true", - "k3s:deploy/hwlab-agent-mgr:ready=1/1:available=True", - "k3s:deploy/hwlab-agent-skills:ready=1/1:available=True", - "k3s:svc/hwlab-agent-mgr:endpoints=1", - "k3s:svc/hwlab-agent-skills:endpoints=1", - "agent-mgr:/health/live:degraded:missing=manager.skillCommitId,manager.skillVersion", - "agent-skills:/health/live:ok:d881a50", - "worker-template:suspend=true:image=127.0.0.1:5000/hwlab/hwlab-agent-worker:d881a50", - "worker-job:server-dry-run=true:persisted=false", - "skills-injection:commit=d881a50:version=missing", - "secret-rbac:get=yes:secretResourcesRead=false", - "Public DEV API http://74.48.78.17:16667 health=200 live=200 with DB live ready and runtime durable adapter blocked; frontend http://74.48.78.17:16666/=200.", - "health:hwlab-edge-proxy:dev:ok", - "live:hwlab-cloud-api:degraded:runtime-durable-adapter-blocked:runtime_durable_adapter_query_blocked", - "db-live:ready=true:connected=true:liveDbEvidence=true", - "runtime-durable-adapter:adapter=postgres:blocker=runtime_durable_adapter_query_blocked:migration=0001_cloud_core_skeleton:migrationChecked=false:migrationReady=false:queryResult=query_blocked" - ], - "nextRequired": "Do not schedule or claim the agent loop as live until DB live, runtime durable adapter, skills injection, and required evidence preconditions pass." - }, - "artifactDesiredStateSource": { - "label": "artifact/desired-state source", - "status": "blocked", - "evidenceLevel": "BLOCKED", - "summary": "artifact targetCovered=false; artifactSource=7e29522b65c8; target=f64182b54fe6; desiredApplyMode=dry-run; mutationAttempted=false.", - "evidence": [ - "artifactState=contract-skeleton", - "sourceStates=13 source-present, 0 intentionally-disabled", - "desiredState=ready dry-run-only" - ], - "nextRequired": "Refresh artifact/source coverage for current origin/main and keep desired-state apply separate from read-only route proof." - } - }, - "milestoneLevelClassification": [ - { - "milestone": "M0", - "currentLevel": "SOURCE", - "status": "pass", - "strongestEvidenceLevel": "SOURCE", - "liveEvidence": "missing_or_blocked", - "summary": "Source contract is green at source level only." - }, - { - "milestone": "M1", - "currentLevel": "LOCAL", - "status": "pass", - "strongestEvidenceLevel": "LOCAL", - "liveEvidence": "missing_or_blocked", - "summary": "Local smoke is green; it is not DEV-LIVE." - }, - { - "milestone": "M2", - "currentLevel": "DEV-LIVE", - "status": "blocked", - "strongestEvidenceLevel": "DEV-LIVE", - "liveEvidence": "pass", - "summary": "Current public frontend/API route evidence is DEV-LIVE for route/front-end reachability only." - }, - { - "milestone": "M3", - "currentLevel": "BLOCKED", - "status": "blocked", - "strongestEvidenceLevel": "LOCAL", - "liveEvidence": "missing_or_blocked", - "summary": "DEV-LIVE hardware trusted loop is blocked; local/source shape is not acceptance." - }, - { - "milestone": "M4", - "currentLevel": "BLOCKED", - "status": "blocked", - "strongestEvidenceLevel": "DRY-RUN", - "liveEvidence": "missing_or_blocked", - "summary": "DEV-LIVE agent loop is blocked at runtime durable adapter or skills injection readiness; local smoke is not acceptance." - }, - { - "milestone": "M5", - "currentLevel": "BLOCKED", - "status": "blocked", - "strongestEvidenceLevel": "DRY-RUN", - "liveEvidence": "missing_or_blocked", - "summary": "Dry-run is green, but bounded DEV-LIVE MVP e2e is blocked." - } - ], - "milestoneBlockerClassification": [ - { - "milestone": "M3", - "status": "blocked", - "currentLevel": "BLOCKED", - "blockerClass": "direct-target-identity-gap", - "dependency": "Two distinct live box-simu resources and two distinct gateway-simu identities are required before M3 can run.", - "evidence": [ - "operationId=not_observed", - "traceId=not_observed", - "auditId=not_observed", - "evidenceId=not_observed", - "runnerKubeconfigReadable=false", - "d601PublicEndpointsReachable=true", - "d601K3sUnavailable=false", - "directTargetSource=kubernetes-endpointslices", - "directTargetCounts={\"boxSimu\":2,\"gatewaySimu\":2,\"patchPanel\":1,\"distinctBoxResources\":1,\"distinctGatewayIdentities\":1}", - "patchPanelM3Wiring=false" - ], - "nextRequired": "Fix DEV simulator instance identity so two box-simu pods report res_boxsimu_1/res_boxsimu_2 and two gateway-simu pods report distinct gateway identities.", - "nonPromotionReason": "Public frontend, route, and artifact evidence do not prove the required hardware loop." - }, - { - "milestone": "M4", - "status": "blocked", - "currentLevel": "BLOCKED", - "blockerClass": "runtime_durable_adapter_query_blocked", - "dependency": "Cloud API /health/live must prove the postgres durable runtime adapter schema, 0001_cloud_core_skeleton migration ledger, and read-query readiness before live agent scheduling/evidence closure.", - "evidence": [ - "db.status=ok", - "db.ready=true", - "db.connected=true", - "db.endpointSource=secret-url-host", - "db.liveDbEvidence=true", - "runtime.adapter=postgres", - "runtime.blocker=runtime_durable_adapter_query_blocked", - "runtime.migration=0001_cloud_core_skeleton", - "runtime.migration.ready=false", - "runtime.queryResult=query_blocked", - "skills.missing=hwlab-agent-skills.HWLAB_SKILLS_VERSION,worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV,manager.skillCommitId,manager.skillVersion" - ], - "nextRequired": "Repair the runtime durable adapter query/migration readiness and rerun the M4 live preflight without scheduling a DEV agent task before preconditions pass.", - "nonPromotionReason": "Frontend revision and read-only route reachability do not prove DB-backed durable agent runtime readiness or skill version injection." - }, - { - "milestone": "M5", - "status": "blocked", - "currentLevel": "BLOCKED", - "blockerClass": "composite-runtime-durable-m3-m4-live", - "dependency": "M5 needs DB live readiness, runtime durable adapter readiness, skills commit/version injection, M3 trusted-loop DEV evidence, M4 live preflight/evidence closure, and current source/artifact coverage.", - "evidence": [ - "M5 dry-run is green", - "db.ready=true", - "runtime.blocker=runtime_durable_adapter_query_blocked", - "skills.status=blocked", - "m3.live=false", - "m4.live=false" - ], - "nextRequired": "After DB/runtime durable adapter/skills/M3/M4 blockers are cleared, run only the bounded DEV MVP live gate command with explicit DEV/non-PROD confirmations.", - "nonPromotionReason": "No frontend, route-only, local, or dry-run evidence is allowed to stand in for bounded DEV-LIVE MVP e2e acceptance." - } - ], - "milestones": [ - { - "id": "M0", - "status": "pass", - "highestVisibleLevel": "SOURCE", - "liveEvidence": "missing_or_blocked", - "evidenceCount": 1, - "blockerCount": 0, - "summary": "contract source is available; highest visible level is SOURCE; status is pass." - }, - { - "id": "M1", - "status": "pass", - "highestVisibleLevel": "LOCAL", - "liveEvidence": "missing_or_blocked", - "evidenceCount": 1, - "blockerCount": 0, - "summary": "local smoke is available; highest visible level is LOCAL; status is pass." - }, - { - "id": "M2", - "status": "blocked", - "highestVisibleLevel": "DEV-LIVE", - "liveEvidence": "pass", - "evidenceCount": 7, - "blockerCount": 4, - "summary": "deploy/runtime readiness is blocked before live DEV; highest visible level is DEV-LIVE; status is blocked." - }, - { - "id": "M3", - "status": "blocked", - "highestVisibleLevel": "LOCAL", - "liveEvidence": "missing_or_blocked", - "evidenceCount": 3, - "blockerCount": 5, - "summary": "hardware loop has source/local shape but no live operation; highest visible level is LOCAL; status is blocked." - }, - { - "id": "M4", - "status": "blocked", - "highestVisibleLevel": "DRY-RUN", - "liveEvidence": "missing_or_blocked", - "evidenceCount": 3, - "blockerCount": 3, - "summary": "agent loop has local smoke but live preflight is blocked; highest visible level is DRY-RUN; status is blocked." - }, - { - "id": "M5", - "status": "blocked", - "highestVisibleLevel": "DRY-RUN", - "liveEvidence": "missing_or_blocked", - "evidenceCount": 2, - "blockerCount": 11, - "summary": "dry-run is green but live MVP gate is blocked; highest visible level is DRY-RUN; status is blocked." - } - ], - "evidence": [ - { - "milestone": "M0", - "issue": "pikasTech/HWLAB#31", - "level": "SOURCE", - "status": "pass", - "category": "contract", - "lifecycleState": "active", - "sources": [ - "docs/m0-contract-audit.md", - "protocol/README.md", - "protocol/evidence-chain.md", - "protocol/schemas/evidence-record.schema.json", - "protocol/examples/m0-contract/service-ids.json" - ], - "commands": [ - "node scripts/validate-contract.mjs", - "node scripts/validate-m0-contract.mjs", - "node scripts/validate-evidence-chain.mjs" - ], - "summary": "Frozen service IDs, JSON-RPC, audit, topology, evidence, and DEV-only deploy contracts are source-ready." - }, - { - "milestone": "M1", - "issue": "pikasTech/HWLAB#7", - "level": "LOCAL", - "status": "pass", - "category": "local-smoke", - "lifecycleState": "active", - "sources": [ - "docs/m1-local-smoke.md", - "fixtures/mvp/runtime.json", - "scripts/m1-contract-smoke.mjs" - ], - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "summary": "Local skeleton smoke covers cloud API, simulators, patch-panel routing, CLI dry-run boundary, and no DEV/PROD mutation." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#99", - "taskId": "cloud-web-workbench-frontend", - "lifecycleState": "active", - "level": "DEV-LIVE", - "status": "pass", - "category": "frontend-dev-revision", - "sources": [ - "docs/dev-deploy-apply.md", - "docs/cloud-web-workbench.md", - "web/hwlab-cloud-web/index.html", - "web/hwlab-cloud-web/styles.css" - ], - "commands": [ - "node web/hwlab-cloud-web/scripts/check.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source" - ], - "evidence": [ - "Cloud Web /health/live observed revision 1e8805664970839b72be40c34636b08f6d18b131", - "Cloud Workbench public browser endpoint is usable in degraded/read-only mode only", - "Frontend load/revision evidence cannot satisfy DB, M3 hardware-loop, M4 agent-loop, or M5 MVP e2e acceptance" - ], - "summary": "#99/#108 Cloud Workbench revision 1e8805664970839b72be40c34636b08f6d18b131 is the latest DEV frontend visibility fact; API/runtime durability remains degraded, so it is degraded/read-only UI evidence only." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#34", - "taskId": "dev-gate-preflight", - "reportPath": "reports/dev-gate/dev-preflight-report.json", - "commitId": "f64182b54fe6", - "lifecycleState": "active", - "level": "SOURCE", - "status": "pass", - "category": "deploy-manifest", - "commands": [ - "node --check scripts/dev-gate-preflight.mjs", - "node --check scripts/src/dev-gate-preflight.mjs", - "node --check scripts/src/registry-capabilities.mjs", - "node --check scripts/refresh-artifact-catalog.mjs", - "node scripts/dev-gate-preflight.mjs", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "evidence": [ - "source-contract-static=pass", - "artifact catalog state=contract-skeleton", - "catalog commit=c7de474" - ], - "summary": "Deploy manifests, FRP/master-edge contracts, and safety boundary are source-readable and scoped to hwlab-dev." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#33", - "taskId": "dev-deploy-apply", - "reportPath": "reports/dev-gate/dev-deploy-report.json", - "commitId": "7e29522", - "lifecycleState": "active", - "level": "DRY-RUN", - "status": "pass", - "category": "deploy-apply", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - "evidence": [ - "artifact services checked: 13", - "expected artifact commit: 7e29522", - "namespace: hwlab-dev", - "workloads planned: 13", - "kubectl executor: /usr/local/bin/kubectl", - "live health: pass", - "template job replacements: 2" - ], - "summary": "DEV dry-run preflight passed." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "reportPath": "reports/dev-gate/dev-artifacts.json", - "commitId": "7de6edd", - "lifecycleState": "active", - "level": "DRY-RUN", - "status": "published", - "category": "artifact-publish", - "commands": [ - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-artifact-services.mjs", - "node --check scripts/src/registry-capabilities.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", - "node scripts/dev-artifact-publish.mjs --preflight --no-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "evidence": [ - "services=13", - "baseImagePreflight=ready", - "published=13/13" - ], - "summary": "Artifact publish preflight exists but is blocked before any real image publish." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#23", - "taskId": "m2-dev-deploy-smoke", - "reportPath": "reports/dev-gate/dev-m2-deploy-smoke-active.json", - "commitId": "8e89409dda5d", - "lifecycleState": "active", - "level": "DEV-LIVE", - "status": "pass", - "category": "public-entrypoints-read-only", - "commands": [ - "curl -fsS --max-time 10 http://74.48.78.17:16667/health", - "curl -fsS --max-time 10 http://74.48.78.17:16667/health/live", - "curl -fsS --max-time 10 http://74.48.78.17:16666/" - ], - "evidence": [ - "http://74.48.78.17:16667/health -> HTTP 200 identity=hwlab-edge-proxy status=ok", - "http://74.48.78.17:16667/health/live -> HTTP 200 identity=hwlab-cloud-api status=degraded", - "http://74.48.78.17:16666/ -> HTTP 200 identity=HWLAB DEV MVP Gate status=ok" - ], - "summary": "Read-only probes prove the frozen public DEV entrypoints on :16666/:16667 are reachable; this is EDGE/ROUTE evidence only." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#36", - "taskId": "dev-edge-health", - "reportPath": "reports/dev-gate/dev-edge-health.json", - "commitId": "c7de4745f491", - "lifecycleState": "active", - "level": "DEV-LIVE", - "status": "blocker", - "category": "edge-frp-contract", - "commands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-edge-health-smoke.mjs", - "node --check scripts/src/dev-edge-health-smoke-lib.mjs", - "node --test scripts/src/dev-edge-health-smoke-lib.test.mjs", - "node scripts/dev-edge-health-smoke.mjs --live --write-report" - ], - "evidence": [ - "mode=live-read-only", - "classification=app_health_blocker", - "cloudApiDb=degraded" - ], - "summary": "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; remaining edge report blocker is DB readiness, not route/frp reachability." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#46", - "taskId": "d601-k3s-readonly-observability", - "reportPath": "reports/d601-k3s-readonly-observability.json", - "commitId": "unknown", - "lifecycleState": "active", - "level": "DEV-LIVE", - "status": "blocked", - "category": "d601-observability", - "commands": [ - "node --check scripts/d601-k3s-readonly-observability.mjs", - "node --check scripts/src/d601-k3s-readonly-observability.mjs", - "node scripts/d601-k3s-readonly-observability.mjs" - ], - "evidence": [ - "runnerKubeconfigReadable=false", - "runnerKubeconfigProbeExitCode=0", - "runnerKubeconfigProbeStderr=empty", - "d601PublicEndpointsReachable=true", - "d601K3sUnavailable=false" - ], - "summary": "D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false." - }, - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "reportPath": "reports/dev-gate/dev-m3-hardware-loop.json", - "commitId": "c7de4745f491", - "lifecycleState": "active", - "level": "SOURCE", - "status": "manifest-ready", - "category": "hardware-loop-cardinality", - "commands": [ - "node scripts/validate-dev-m3-cardinality.mjs" - ], - "evidence": [ - "deploy-skeleton-m3-cardinality=manifest-ready" - ], - "summary": "Static DEV manifest cardinality declares two box simulators, two gateway simulators, and one patch panel." - }, - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "commitId": "86d769525caf", - "lifecycleState": "active", - "level": "LOCAL", - "status": "blocked", - "category": "hardware-loop-local", - "commands": [ - "node scripts/m3-hardware-loop-smoke.mjs", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "Local M3 hardware loop smoke passed", - "Live DEV M3 smoke reached 16667 ingress and direct simulator/patch-panel Pod URLs", - "Live DEV M3 blocked: patch-panel active DO1 -> DI1 connection missing" - ], - "summary": "Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology." - }, - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "reportPath": "reports/dev-gate/dev-m3-hardware-loop.json", - "commitId": "c7de4745f491", - "lifecycleState": "active", - "level": "BLOCKED", - "status": "not_run", - "category": "hardware-loop-live", - "commands": [ - "node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod" - ], - "evidence": [ - "operationId=not_observed", - "traceId=not_observed", - "auditId=not_observed", - "evidenceId=not_observed" - ], - "summary": "No live DEV operation was attempted because read-only direct target checks did not prove the required patch-panel-owned M3 route. Blocker: DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "commitId": "d881a50", - "lifecycleState": "active", - "level": "LOCAL", - "status": "pass", - "category": "agent-loop-local", - "commands": [ - "node scripts/m4-agent-loop-smoke.mjs" - ], - "evidence": [ - "Local runtime skeleton smoke confirms create/start/trace/finish/cleanup coverage.", - "Workspace isolation and explicit skills commit wiring are covered by fixture assertions." - ], - "summary": "Local contract smoke passes on the repo fixture." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "commitId": "d881a50", - "lifecycleState": "active", - "level": "DRY-RUN", - "status": "pass", - "category": "agent-loop-dry-run", - "commands": [ - "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run" - ], - "evidence": [ - "hwlab-agent-mgr:session=agt_m4_agent_loop_01:created=queued:final=cleanup", - "hwlab-agent-worker:session=wkr_agt_m4_agent_loop_01:status=cleanup", - "workspace:vol_agt_m4_agent_loop_01:cleaned=true:existsAfterCleanup=false", - "hwlab-agent-skills:commit=6509a35:version=v1", - "trace:trc_agt_m4_agent_loop_01:events=5", - "evidence:evi_agt_m4_agent_loop_01:kind=report", - "cleanup:cln_agt_m4_agent_loop_01:removed=true" - ], - "summary": "Local dry-run covered agent manager, worker, workspace isolation, skills commit, trace, evidence, and cleanup without live DEV mutation." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "commitId": "d881a50", - "lifecycleState": "active", - "level": "BLOCKED", - "status": "blocked", - "category": "agent-loop-live-preflight", - "commands": [ - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "k3s:namespace=hwlab-dev:read=true", - "k3s:deploy/hwlab-agent-mgr:ready=1/1:available=True", - "k3s:deploy/hwlab-agent-skills:ready=1/1:available=True", - "k3s:svc/hwlab-agent-mgr:endpoints=1", - "k3s:svc/hwlab-agent-skills:endpoints=1", - "agent-mgr:/health/live:degraded:missing=manager.skillCommitId,manager.skillVersion", - "agent-skills:/health/live:ok:d881a50", - "worker-template:suspend=true:image=127.0.0.1:5000/hwlab/hwlab-agent-worker:d881a50", - "worker-job:server-dry-run=true:persisted=false", - "skills-injection:commit=d881a50:version=missing", - "secret-rbac:get=yes:secretResourcesRead=false", - "Public DEV API http://74.48.78.17:16667 health=200 live=200 with DB live ready and runtime durable adapter blocked; frontend http://74.48.78.17:16666/=200.", - "health:hwlab-edge-proxy:dev:ok", - "live:hwlab-cloud-api:degraded:runtime-durable-adapter-blocked:runtime_durable_adapter_query_blocked", - "db-live:ready=true:connected=true:liveDbEvidence=true", - "runtime-durable-adapter:adapter=postgres:blocker=runtime_durable_adapter_query_blocked:migration=0001_cloud_core_skeleton:migrationChecked=false:migrationReady=false:queryResult=query_blocked" - ], - "summary": "Blocked at runtime durable adapter readiness before scheduling a DEV agent task." - }, - { - "milestone": "M5", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "commitId": "86d769525caf", - "lifecycleState": "active", - "level": "DRY-RUN", - "status": "pass", - "category": "mvp-e2e-dry-run", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs health", - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/m5-mvp-e2e-dry-run.mjs" - ], - "evidence": [ - "hwlab-cli health -> status: ok, environment: dev, endpoint: http://74.48.78.17:16667, projects: 2", - "hwlab-cli dry-run -> 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls", - "M5 MVP E2E dry-run passed: 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls" - ], - "summary": "The M5 orchestration dry-run is green, but it remains fixture-only and does not establish a live DEV gate." - }, - { - "milestone": "M5", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "commitId": "86d769525caf", - "lifecycleState": "active", - "level": "BLOCKED", - "status": "blocked", - "category": "mvp-e2e-live", - "commands": [ - "curl -fsS --max-time 8 http://74.48.78.17:16666/", - "curl -fsS --max-time 8 http://74.48.78.17:16667/health/live", - "curl -fsS --max-time 8 http://74.48.78.17:16667/health", - "HWLAB_DEV_BOX_SIMU_1_URL=http://10.42.0.200:7201 HWLAB_DEV_BOX_SIMU_2_URL=http://10.42.0.208:7201 HWLAB_DEV_GATEWAY_SIMU_1_URL=http://10.42.0.209:7101 HWLAB_DEV_GATEWAY_SIMU_2_URL=http://10.42.0.204:7101 HWLAB_DEV_PATCH_PANEL_URL=http://10.42.0.205:7301 node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "http://74.48.78.17:16666/ -> HTTP 200 text/html, title=HWLAB DEV MVP Gate", - "http://74.48.78.17:16667/health -> HTTP 200 serviceId=hwlab-edge-proxy status=ok commit=d881a50", - "http://74.48.78.17:16667/health/live -> HTTP 200 serviceId=hwlab-cloud-api status=degraded db.ready=true db.connected=true db.liveDbEvidence=true runtime.blocker=runtime_durable_adapter_query_blocked runtime.migration=0001_cloud_core_skeleton", - "dev-edge-health active report remains older DB-live evidence and is superseded for M4/M5 blocker naming by current d881a50 M4 preflight evidence", - "dev-m3-hardware-loop active report -> blocked, classification=hardware loop, summary=patch-panel active DO1 -> DI1 connection missing", - "dev-m4-agent-loop active report -> blocked, classification=runtime_durable_adapter_query_blocked, summary=runtime durable adapter query/migration readiness is blocked; skills commit/version injection is incomplete" - ], - "summary": "Public DEV route/frp is reachable on 16666/16667, but live MVP remains blocked by runtime_durable_adapter_query_blocked, skills commit/version injection, and the M3 hardware loop topology; no DB-live-only blocker is counted as current M4/M5 evidence." - } - ], - "levels": { - "SOURCE": [ - { - "milestone": "M0", - "issue": "pikasTech/HWLAB#31", - "lifecycleState": "active", - "status": "pass", - "category": "contract", - "summary": "Frozen service IDs, JSON-RPC, audit, topology, evidence, and DEV-only deploy contracts are source-ready." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#34", - "taskId": "dev-gate-preflight", - "lifecycleState": "active", - "status": "pass", - "category": "deploy-manifest", - "reportPath": "reports/dev-gate/dev-preflight-report.json", - "summary": "Deploy manifests, FRP/master-edge contracts, and safety boundary are source-readable and scoped to hwlab-dev." - }, - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "lifecycleState": "active", - "status": "manifest-ready", - "category": "hardware-loop-cardinality", - "reportPath": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "Static DEV manifest cardinality declares two box simulators, two gateway simulators, and one patch panel." - } - ], - "LOCAL": [ - { - "milestone": "M1", - "issue": "pikasTech/HWLAB#7", - "lifecycleState": "active", - "status": "pass", - "category": "local-smoke", - "summary": "Local skeleton smoke covers cloud API, simulators, patch-panel routing, CLI dry-run boundary, and no DEV/PROD mutation." - }, - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "lifecycleState": "active", - "status": "blocked", - "category": "hardware-loop-local", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "summary": "Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "lifecycleState": "active", - "status": "pass", - "category": "agent-loop-local", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "Local contract smoke passes on the repo fixture." - } - ], - "DRY-RUN": [ - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#33", - "taskId": "dev-deploy-apply", - "lifecycleState": "active", - "status": "pass", - "category": "deploy-apply", - "reportPath": "reports/dev-gate/dev-deploy-report.json", - "summary": "DEV dry-run preflight passed." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#35", - "taskId": "dev-artifact-publish", - "lifecycleState": "active", - "status": "published", - "category": "artifact-publish", - "reportPath": "reports/dev-gate/dev-artifacts.json", - "summary": "Artifact publish preflight exists but is blocked before any real image publish." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "lifecycleState": "active", - "status": "pass", - "category": "agent-loop-dry-run", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "Local dry-run covered agent manager, worker, workspace isolation, skills commit, trace, evidence, and cleanup without live DEV mutation." - }, - { - "milestone": "M5", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "lifecycleState": "active", - "status": "pass", - "category": "mvp-e2e-dry-run", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "summary": "The M5 orchestration dry-run is green, but it remains fixture-only and does not establish a live DEV gate." - } - ], - "DEV-LIVE": [ - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#99", - "taskId": "cloud-web-workbench-frontend", - "lifecycleState": "active", - "status": "pass", - "category": "frontend-dev-revision", - "summary": "#99/#108 Cloud Workbench revision 1e8805664970839b72be40c34636b08f6d18b131 is the latest DEV frontend visibility fact; API/runtime durability remains degraded, so it is degraded/read-only UI evidence only." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#23", - "taskId": "m2-dev-deploy-smoke", - "lifecycleState": "active", - "status": "pass", - "category": "public-entrypoints-read-only", - "reportPath": "reports/dev-gate/dev-m2-deploy-smoke-active.json", - "summary": "Read-only probes prove the frozen public DEV entrypoints on :16666/:16667 are reachable; this is EDGE/ROUTE evidence only." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#36", - "taskId": "dev-edge-health", - "lifecycleState": "active", - "status": "blocker", - "category": "edge-frp-contract", - "reportPath": "reports/dev-gate/dev-edge-health.json", - "summary": "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; remaining edge report blocker is DB readiness, not route/frp reachability." - }, - { - "milestone": "M2", - "issue": "pikasTech/HWLAB#46", - "taskId": "d601-k3s-readonly-observability", - "lifecycleState": "active", - "status": "blocked", - "category": "d601-observability", - "reportPath": "reports/d601-k3s-readonly-observability.json", - "summary": "D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false." - } - ], - "BLOCKED": [ - { - "milestone": "M3", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "lifecycleState": "active", - "status": "not_run", - "category": "hardware-loop-live", - "reportPath": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "No live DEV operation was attempted because read-only direct target checks did not prove the required patch-panel-owned M3 route. Blocker: DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1." - }, - { - "milestone": "M4", - "issue": "pikasTech/HWLAB#37", - "taskId": "dev-m4-agent-loop", - "lifecycleState": "active", - "status": "blocked", - "category": "agent-loop-live-preflight", - "reportPath": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "Blocked at runtime durable adapter readiness before scheduling a DEV agent task." - }, - { - "milestone": "M5", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "lifecycleState": "active", - "status": "blocked", - "category": "mvp-e2e-live", - "reportPath": "reports/dev-gate/dev-mvp-gate-report.json", - "summary": "Public DEV route/frp is reachable on 16666/16667, but live MVP remains blocked by runtime_durable_adapter_query_blocked, skills commit/version injection, and the M3 hardware loop topology; no DB-live-only blocker is counted as current M4/M5 evidence." - }, - { - "priority": "P1", - "order": 3, - "type": "runtime_blocker", - "scope": "artifact-catalog", - "sourceIssue": "pikasTech/HWLAB#34", - "source": "reports/dev-gate/dev-preflight-report.json", - "summary": "deploy/artifact-catalog.dev.json does not prove published artifacts for origin/main f64182b; ciPublished=false, registryVerified=false, not_published=13.", - "nextTask": "Run the DEV artifact publish workflow, then record only real sha256 digests with `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`; if publish is still blocked, keep not_published via `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`." - }, - { - "priority": "P1", - "order": 3, - "type": "contract_blocker", - "scope": "artifact-source-commit", - "sourceIssue": "pikasTech/HWLAB#34", - "source": "reports/dev-gate/dev-preflight-report.json", - "summary": "source commit origin/main f64182b 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`." - }, - { - "priority": "P1", - "order": 3, - "type": "runtime_blocker", - "scope": "dev-artifact-publish", - "sourceIssue": "pikasTech/HWLAB#34", - "source": "reports/dev-gate/dev-preflight-report.json", - "summary": "reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for origin/main f64182b; 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." - }, - { - "priority": "P1", - "order": 4, - "type": "observability_blocker", - "scope": "runner-kubeconfig-readonly-gap", - "sourceIssue": "pikasTech/HWLAB#46", - "source": "reports/d601-k3s-readonly-observability.json", - "summary": "The runner cannot read /etc/rancher/k3s/k3s.yaml; classify this as a runner permission/mount gap, not a D601 global outage. Alternate read-only cluster probes and public DEV endpoint probes are reported separately.", - "nextTask": "Provide the approved read-only runner kubeconfig mount or document the intended alternate KUBECONFIG path, then rerun the read-only report." - }, - { - "priority": "P1", - "order": 5, - "type": "agent_blocker", - "scope": "agent-mgr-health", - "sourceIssue": "pikasTech/HWLAB#37", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "hwlab-agent-mgr /health/live is degraded." - }, - { - "priority": "P1", - "order": 5, - "type": "runtime_blocker", - "scope": "runtime-durable-adapter", - "sourceIssue": "pikasTech/HWLAB#164", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; adapter=postgres; durable=false; ready=false; migration=0001_cloud_core_skeleton checked=false ready=false missing=true; queryResult=query_blocked." - }, - { - "priority": "P1", - "order": 5, - "type": "agent_blocker", - "scope": "skills-commit-version-injection", - "sourceIssue": "pikasTech/HWLAB#37", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "summary": "DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV." - }, - { - "priority": "P0", - "order": 6, - "type": "runtime_blocker", - "scope": "m3-box-simu-identity", - "sourceIssue": "pikasTech/HWLAB#64", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1." - }, - { - "priority": "P0", - "order": 6, - "type": "runtime_blocker", - "scope": "m3-gateway-simu-identity", - "sourceIssue": "pikasTech/HWLAB#64", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "DEV exposes two gateway-simu endpoints, but live gateway identities are not distinct; observed identities=gateway-a:gws_gateway-a." - }, - { - "priority": "P0", - "order": 6, - "type": "runtime_blocker", - "scope": "m3-patch-panel-wiring", - "sourceIssue": "pikasTech/HWLAB#64", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "DEV patch-panel is callable, but live wiring does not contain res_boxsimu_1:DO1 -> res_boxsimu_2:DI1; active=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0; configured=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0." - }, - { - "priority": "P0", - "order": 6, - "type": "runtime_blocker", - "scope": "m3-hardware-loop-runtime", - "sourceIssue": "pikasTech/HWLAB#39", - "source": "reports/dev-gate/dev-mvp-gate-report.json", - "summary": "Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing." - }, - { - "priority": "P3", - "order": 99, - "type": "safety_blocker", - "scope": "m3-live-write-authorization", - "sourceIssue": "pikasTech/HWLAB#38", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "summary": "Plan-only mode is intentionally non-mutating; run the bounded live smoke only after explicit DEV/non-PROD approval and after read-only preconditions identify the exact HWLAB targets." - } - ] - }, - "blockers": [ - { - "id": "dev-gate-preflight:artifact-catalog", - "priority": "P1", - "type": "runtime_blocker", - "scope": "artifact-catalog", - "status": "open", - "source": "reports/dev-gate/dev-preflight-report.json", - "sourceIssue": "pikasTech/HWLAB#34", - "summary": "deploy/artifact-catalog.dev.json does not prove published artifacts for origin/main f64182b; ciPublished=false, registryVerified=false, not_published=13.", - "nextTask": "Run the DEV artifact publish workflow, then record only real sha256 digests with `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`; if publish is still blocked, keep not_published via `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`.", - "unblockOrder": 3, - "unblocks": [ - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#39" - ], - "rationale": "The gate cannot promote deploy or runtime observations without immutable image provenance and digests." - }, - { - "id": "dev-gate-preflight:artifact-source-commit", - "priority": "P1", - "type": "contract_blocker", - "scope": "artifact-source-commit", - "status": "open", - "source": "reports/dev-gate/dev-preflight-report.json", - "sourceIssue": "pikasTech/HWLAB#34", - "summary": "source commit origin/main f64182b 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`.", - "unblockOrder": 3, - "unblocks": [ - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#39" - ], - "rationale": "The gate cannot promote deploy or runtime observations without immutable image provenance and digests." - }, - { - "id": "dev-gate-preflight:dev-artifact-publish", - "priority": "P1", - "type": "runtime_blocker", - "scope": "dev-artifact-publish", - "status": "open", - "source": "reports/dev-gate/dev-preflight-report.json", - "sourceIssue": "pikasTech/HWLAB#34", - "summary": "reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for origin/main f64182b; 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.", - "unblockOrder": 3, - "unblocks": [ - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#39" - ], - "rationale": "The gate cannot promote deploy or runtime observations without immutable image provenance and digests." - }, - { - "id": "d601-k3s-readonly-observability:runner-kubeconfig-readonly-gap", - "priority": "P1", - "type": "observability_blocker", - "scope": "runner-kubeconfig-readonly-gap", - "status": "open", - "source": "reports/d601-k3s-readonly-observability.json", - "sourceIssue": "pikasTech/HWLAB#46", - "summary": "The runner cannot read /etc/rancher/k3s/k3s.yaml; classify this as a runner permission/mount gap, not a D601 global outage. Alternate read-only cluster probes and public DEV endpoint probes are reported separately.", - "nextTask": "Provide the approved read-only runner kubeconfig mount or document the intended alternate KUBECONFIG path, then rerun the read-only report.", - "unblockOrder": 4, - "unblocks": [ - "pikasTech/HWLAB#34", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#36", - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#46", - "pikasTech/HWLAB#64" - ], - "rationale": "Runner read-only observability must be repaired without treating the runner gap as proof that D601 k3s or public DEV endpoints are unavailable." - }, - { - "id": "dev-m4-agent-loop:agent-mgr-health", - "priority": "P1", - "type": "agent_blocker", - "scope": "agent-mgr-health", - "status": "open", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "sourceIssue": "pikasTech/HWLAB#37", - "summary": "hwlab-agent-mgr /health/live is degraded.", - "unblockOrder": 5, - "unblocks": [ - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#164" - ], - "rationale": "M4/M5 agent evidence needs deployed manager, skills, and worker paths to expose explicit skills commit and version; session-mode-only worker templates are not enough." - }, - { - "id": "dev-m4-agent-loop:runtime-durable-adapter", - "priority": "P1", - "type": "runtime_blocker", - "scope": "runtime-durable-adapter", - "status": "open", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "sourceIssue": "pikasTech/HWLAB#164", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; adapter=postgres; durable=false; ready=false; migration=0001_cloud_core_skeleton checked=false ready=false missing=true; queryResult=query_blocked.", - "unblockOrder": 5, - "unblocks": [ - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#164" - ], - "rationale": "M4 and M5 cannot claim live agent or MVP evidence until the cloud-api postgres runtime adapter proves schema, migration, and read-query durability; DB live evidence alone is not durability evidence." - }, - { - "id": "dev-m4-agent-loop:skills-commit-version-injection", - "priority": "P1", - "type": "agent_blocker", - "scope": "skills-commit-version-injection", - "status": "open", - "source": "reports/dev-gate/dev-m4-agent-loop.json", - "sourceIssue": "pikasTech/HWLAB#37", - "summary": "DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV.", - "unblockOrder": 5, - "unblocks": [ - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#164" - ], - "rationale": "M4/M5 agent evidence needs deployed manager, skills, and worker paths to expose explicit skills commit and version; session-mode-only worker templates are not enough." - }, - { - "id": "dev-m3-hardware-loop:m3-box-simu-identity", - "priority": "P0", - "type": "runtime_blocker", - "scope": "m3-box-simu-identity", - "status": "open", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1.", - "unblockOrder": 6, - "unblocks": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#64" - ], - "rationale": "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers." - }, - { - "id": "dev-m3-hardware-loop:m3-gateway-simu-identity", - "priority": "P0", - "type": "runtime_blocker", - "scope": "m3-gateway-simu-identity", - "status": "open", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV exposes two gateway-simu endpoints, but live gateway identities are not distinct; observed identities=gateway-a:gws_gateway-a.", - "unblockOrder": 6, - "unblocks": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#64" - ], - "rationale": "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers." - }, - { - "id": "dev-m3-hardware-loop:m3-patch-panel-wiring", - "priority": "P0", - "type": "runtime_blocker", - "scope": "m3-patch-panel-wiring", - "status": "open", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "sourceIssue": "pikasTech/HWLAB#64", - "summary": "DEV patch-panel is callable, but live wiring does not contain res_boxsimu_1:DO1 -> res_boxsimu_2:DI1; active=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0; configured=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0.", - "unblockOrder": 6, - "unblocks": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#64" - ], - "rationale": "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers." - }, - { - "id": "dev-mvp-gate-report:m3-hardware-loop-runtime", - "priority": "P0", - "type": "runtime_blocker", - "scope": "m3-hardware-loop-runtime", - "status": "open", - "source": "reports/dev-gate/dev-mvp-gate-report.json", - "sourceIssue": "pikasTech/HWLAB#39", - "summary": "Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing.", - "unblockOrder": 6, - "unblocks": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#64" - ], - "rationale": "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers." - }, - { - "id": "dev-m3-hardware-loop:m3-live-write-authorization", - "priority": "P3", - "type": "safety_blocker", - "scope": "m3-live-write-authorization", - "status": "open", - "source": "reports/dev-gate/dev-m3-hardware-loop.json", - "sourceIssue": "pikasTech/HWLAB#38", - "summary": "Plan-only mode is intentionally non-mutating; run the bounded live smoke only after explicit DEV/non-PROD approval and after read-only preconditions identify the exact HWLAB targets.", - "unblockOrder": 99, - "unblocks": [ - "pikasTech/HWLAB#39" - ], - "rationale": "Residual blocker that must be classified before claiming a green DEV gate." - } - ], - "nextSteps": [ - { - "order": 1, - "blockerOrder": 3, - "priority": "P1", - "scopes": [ - "artifact-catalog", - "artifact-source-commit", - "dev-artifact-publish" - ], - "sourceIssues": [ - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#34", - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#39" - ], - "rationale": "The gate cannot promote deploy or runtime observations without immutable image provenance and digests.", - "action": "Run the DEV artifact publish workflow, then record only real sha256 digests with `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`; if publish is still blocked, keep not_published via `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`.", - "evidenceRequired": "Artifact publish report with ciPublished=true, registryVerified=true, and sha256 digest for each frozen service ID." - }, - { - "order": 2, - "blockerOrder": 4, - "priority": "P1", - "scopes": [ - "runner-kubeconfig-readonly-gap" - ], - "sourceIssues": [ - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#34", - "pikasTech/HWLAB#36", - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#46", - "pikasTech/HWLAB#64" - ], - "rationale": "Runner read-only observability must be repaired without treating the runner gap as proof that D601 k3s or public DEV endpoints are unavailable.", - "action": "Provide the approved read-only runner kubeconfig mount or document the intended alternate KUBECONFIG path, then rerun the read-only report.", - "evidenceRequired": "Read-only report with runnerKubeconfigReadable, runnerKubeconfigProbeExitCode/stderr, d601PublicEndpointsReachable, and d601K3sUnavailable recorded separately, plus direct M3 service target discovery before any DO write." - }, - { - "order": 3, - "blockerOrder": 5, - "priority": "P1", - "scopes": [ - "agent-mgr-health", - "runtime-durable-adapter", - "skills-commit-version-injection" - ], - "sourceIssues": [ - "pikasTech/HWLAB#164", - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#39" - ], - "rationale": "M4/M5 agent evidence needs deployed manager, skills, and worker paths to expose explicit skills commit and version; session-mode-only worker templates are not enough.", - "action": "Inject explicit skills commit and version into agent manager, skills service, and worker jobs, then rerun the M4 read-only/live preflight.", - "evidenceRequired": "Read-only hwlab-agent-mgr/hwlab-agent-skills health plus worker dry-run manifest showing skillCommitId/skillVersion and HWLAB_SKILL_COMMIT_ID/HWLAB_SKILL_VERSION injected." - }, - { - "order": 4, - "blockerOrder": 6, - "priority": "P0", - "scopes": [ - "m3-box-simu-identity", - "m3-gateway-simu-identity", - "m3-patch-panel-wiring", - "m3-hardware-loop-runtime" - ], - "sourceIssues": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#64" - ], - "rationale": "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers.", - "action": "Fix DEV box-simu instance identity so direct endpoints expose distinct res_boxsimu_1 and res_boxsimu_2 resources.", - "evidenceRequired": "Read-only direct box-simu /health/live and /status output showing distinct res_boxsimu_1 and res_boxsimu_2 resources." - }, - { - "order": 5, - "blockerOrder": 99, - "priority": "P3", - "scopes": [ - "m3-live-write-authorization" - ], - "sourceIssues": [ - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39" - ], - "rationale": "Residual blocker that must be classified before claiming a green DEV gate.", - "action": "Resolve the blocker and attach source/local/dry-run/DEV-live evidence at the correct level.", - "evidenceRequired": "A committed report with the exact evidence level and command used." - } - ], - "validationCommands": [ - "node --check scripts/dev-evidence-blocker-aggregator.mjs", - "node --check scripts/src/dev-evidence-blocker-aggregator.mjs", - "node scripts/dev-evidence-blocker-aggregator.mjs --check", - "node scripts/dev-evidence-blocker-aggregator.mjs --markdown", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ] -} diff --git a/reports/dev-gate/dev-m5-gate-aggregator-v2.md b/reports/dev-gate/dev-m5-gate-aggregator-v2.md deleted file mode 100644 index 88e6c96a..00000000 --- a/reports/dev-gate/dev-m5-gate-aggregator-v2.md +++ /dev/null @@ -1,118 +0,0 @@ -# HWLAB M5 DEV Gate Aggregator v2 - -Status: blocked -Generated from: `66586ccf109e` -Scope: DEV only, report-only -Active frontend: `http://74.48.78.17:16666/` -Active API/live: `http://74.48.78.17:16667/health/live` -Deprecated public endpoints: `http://74.48.78.17:6666`, `http://74.48.78.17:6667` - -## Summary - -Frontend revision 1e8805664970839b72be40c34636b08f6d18b131 is usable in degraded/read-only mode and EDGE/ROUTE DEV-LIVE evidence exists on :16666/:16667, but M5 remains blocked by artifact source drift, runtime durable adapter or skills injection readiness, missing M3 trusted loop operation evidence, and blocked M4 agent-loop preflight. - -## Frontend DEV Fact - -The latest #99/#108 frontend DEV visibility fact is revision `1e8805664970839b72be40c34636b08f6d18b131` at `http://74.48.78.17:16666/`. It means the deployed UI is usable in degraded/read-only mode only; this is DEV-LIVE browser/frontend evidence and does not promote M3, M4, or M5. - -## Current DEV Layering - -| Layer | Status | Evidence level | Current conclusion | Required next proof | -| --- | --- | --- | --- | --- | -| Frontend DEV revision | pass | DEV-LIVE | http://74.48.78.17:16666/ serves the Cloud Workbench frontend revision 1e8805664970839b72be40c34636b08f6d18b131; deployed UI is usable in degraded/read-only mode only and this is browser/frontend evidence only. | Keep frontend revision proof separate from DB live readiness, M3 hardware-loop evidence, M4 agent-loop evidence, and M5 acceptance. | -| EDGE/ROUTE live | pass | DEV-LIVE | http://74.48.78.17:16666/, http://74.48.78.17:16667/health, and http://74.48.78.17:16667/health/live returned observed HWLAB DEV route responses in the active M2 read-only smoke. | Keep this separated from DB readiness, M3/M4 loop evidence, and M5 acceptance. | -| DB live/degraded | pass | DEV-LIVE | cloud-api DB status=ok; configReady=true; ready=true; connected=true; endpointSource=secret-url-host; liveDbEvidence=true. | Provide live DB connection evidence through redacted health output; route reachability alone is insufficient. | -| Runtime durable adapter | blocked | BLOCKED | runtime adapter=postgres; durableRequested=true; durable=false; ready=false; blocker=runtime_durable_adapter_query_blocked; migration=0001_cloud_core_skeleton checked=false ready=false; queryResult=query_blocked. | Repair the postgres durable runtime adapter query/migration readiness and rerun health/M4 preflight without treating DB live as durability proof. | -| Agent skills injection | blocked | BLOCKED | DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV. | Inject explicit skill commit and version into hwlab-agent-mgr, hwlab-agent-skills, and scheduled worker jobs before claiming M4/M5 agent evidence. | -| Code Agent provider | pass | DEV-LIVE | code-agent provider=openai-responses; status=available; ready=true. | Keep provider-backed Code Agent readiness separate from DB live and runtime durable adapter readiness. | -| D601 runner observability | blocked | DEV-LIVE | D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false. | Treat #46 runner kubeconfig/readonly gaps separately from D601 service health; rerun read-only observability after the mount or permission path is repaired. | -| M3 hardware trusted loop | blocked | BLOCKED | No live DEV operation was attempted because read-only direct target checks did not prove the required patch-panel-owned M3 route. Blocker: DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1. | Only a real DEV res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 observation with operation/trace/audit/evidence can clear M3. | -| M4 agent loop | blocked | BLOCKED | Blocked at runtime durable adapter readiness before scheduling a DEV agent task. | Do not schedule or claim the agent loop as live until DB live, runtime durable adapter, skills injection, and required evidence preconditions pass. | -| artifact/desired-state source | blocked | BLOCKED | artifact targetCovered=false; artifactSource=7e29522b65c8; target=f64182b54fe6; desiredApplyMode=dry-run; mutationAttempted=false. | Refresh artifact/source coverage for current origin/main and keep desired-state apply separate from read-only route proof. | - -## M0-M5 Level Classification - -| Milestone | Current classification | Status | Strongest evidence | Live evidence | Summary | -| --- | --- | --- | --- | --- | --- | -| M0 | SOURCE | pass | SOURCE | missing_or_blocked | Source contract is green at source level only. | -| M1 | LOCAL | pass | LOCAL | missing_or_blocked | Local smoke is green; it is not DEV-LIVE. | -| M2 | DEV-LIVE | blocked | DEV-LIVE | pass | Current public frontend/API route evidence is DEV-LIVE for route/front-end reachability only. | -| M3 | BLOCKED | blocked | LOCAL | missing_or_blocked | DEV-LIVE hardware trusted loop is blocked; local/source shape is not acceptance. | -| M4 | BLOCKED | blocked | DRY-RUN | missing_or_blocked | DEV-LIVE agent loop is blocked at runtime durable adapter or skills injection readiness; local smoke is not acceptance. | -| M5 | BLOCKED | blocked | DRY-RUN | missing_or_blocked | Dry-run is green, but bounded DEV-LIVE MVP e2e is blocked. | - -## Milestones - -| Milestone | Status | Highest visible level | Live evidence | Summary | -| --- | --- | --- | --- | --- | -| M0 | pass | SOURCE | missing_or_blocked | contract source is available; highest visible level is SOURCE; status is pass. | -| M1 | pass | LOCAL | missing_or_blocked | local smoke is available; highest visible level is LOCAL; status is pass. | -| M2 | blocked | DEV-LIVE | pass | deploy/runtime readiness is blocked before live DEV; highest visible level is DEV-LIVE; status is blocked. | -| M3 | blocked | LOCAL | missing_or_blocked | hardware loop has source/local shape but no live operation; highest visible level is LOCAL; status is blocked. | -| M4 | blocked | DRY-RUN | missing_or_blocked | agent loop has local smoke but live preflight is blocked; highest visible level is DRY-RUN; status is blocked. | -| M5 | blocked | DRY-RUN | missing_or_blocked | dry-run is green but live MVP gate is blocked; highest visible level is DRY-RUN; status is blocked. | - -## #9 DoD Checks - -| Check | Status | Evidence level | Summary | -| --- | --- | --- | --- | -| m0-source-contract | pass | SOURCE | M0 contract checks are source-level evidence only. | -| m1-local-smoke | pass | LOCAL | M1 local smoke is not a live DEV substitute. | -| artifact-publish-digests | blocked | BLOCKED | artifactState=contract-skeleton, ciPublished=false, registryVerified=false, sha256=0, not_published=13, targetCovered=false | -| d601-k3s-observability | blocked | DEV-LIVE | D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline. Alternate read-only cluster probes are readable. runnerKubeconfigReadable=false, runnerKubeconfigProbeExitCode=0, runnerKubeconfigProbeStderr=empty, d601PublicEndpointsReachable=true, d601K3sUnavailable=false. | -| dev-edge-frp-16667 | pass | DEV-LIVE | Committed edge report proves read-only public HTTP on :16667 /health and /health/live; this is route evidence, not DB/M3/M4/M5 acceptance. | -| cloud-api-db-ready | pass | DEV-LIVE | cloud-api DB status=ok; ready=true; connected=true; endpointSource=secret-url-host; liveDbEvidence=true. | -| m3-hardware-trusted-loop | blocked | BLOCKED | M3 trusted loop is blocked until res_boxsimu_1:DO1 -> patch-panel -> res_boxsimu_2:DI1 is proven with operation/trace/audit/evidence. | -| m4-agent-loop-live | blocked | BLOCKED | M4 agent loop live path is blocked before accepted agent scheduling/evidence closure. | -| m5-mvp-dev-live | blocked | BLOCKED | M5 dry-run passed; bounded DEV-LIVE MVP e2e has not passed. | - -## M3/M4/M5 Blocker Classification - -| Milestone | Status | Current level | Blocker class | Dependency | Required next proof | -| --- | --- | --- | --- | --- | --- | -| M3 | blocked | BLOCKED | direct-target-identity-gap | Two distinct live box-simu resources and two distinct gateway-simu identities are required before M3 can run. | Fix DEV simulator instance identity so two box-simu pods report res_boxsimu_1/res_boxsimu_2 and two gateway-simu pods report distinct gateway identities. | -| M4 | blocked | BLOCKED | runtime_durable_adapter_query_blocked | Cloud API /health/live must prove the postgres durable runtime adapter schema, 0001_cloud_core_skeleton migration ledger, and read-query readiness before live agent scheduling/evidence closure. | Repair the runtime durable adapter query/migration readiness and rerun the M4 live preflight without scheduling a DEV agent task before preconditions pass. | -| M5 | blocked | BLOCKED | composite-runtime-durable-m3-m4-live | M5 needs DB live readiness, runtime durable adapter readiness, skills commit/version injection, M3 trusted-loop DEV evidence, M4 live preflight/evidence closure, and current source/artifact coverage. | After DB/runtime durable adapter/skills/M3/M4 blockers are cleared, run only the bounded DEV MVP live gate command with explicit DEV/non-PROD confirmations. | - -## Blockers - -| Priority | Order | Type | Scope | Summary | -| --- | ---: | --- | --- | --- | -| P1 | 3 | runtime_blocker | artifact-catalog | deploy/artifact-catalog.dev.json does not prove published artifacts for origin/main f64182b; ciPublished=false, registryVerified=false, not_published=13. | -| P1 | 3 | contract_blocker | artifact-source-commit | source commit origin/main f64182b is not covered by artifact source 7e29522; target changes since the artifact source touch artifact build inputs. | -| P1 | 3 | runtime_blocker | dev-artifact-publish | reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for origin/main f64182b; current status is published with 13/13 required services published and source states resolved: 13 source-present, 0 intentionally-disabled. | -| P1 | 4 | observability_blocker | runner-kubeconfig-readonly-gap | The runner cannot read /etc/rancher/k3s/k3s.yaml; classify this as a runner permission/mount gap, not a D601 global outage. Alternate read-only cluster probes and public DEV endpoint probes are reported separately. | -| P1 | 5 | agent_blocker | agent-mgr-health | hwlab-agent-mgr /health/live is degraded. | -| P1 | 5 | runtime_blocker | runtime-durable-adapter | cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; adapter=postgres; durable=false; ready=false; migration=0001_cloud_core_skeleton checked=false ready=false missing=true; queryResult=query_blocked. | -| P1 | 5 | agent_blocker | skills-commit-version-injection | DEV skills injection is incomplete: missing hwlab-agent-skills.HWLAB_SKILLS_VERSION, worker-dry-run.HWLAB_SKILL_VERSION_FROM_DEV. | -| P0 | 6 | runtime_blocker | m3-box-simu-identity | DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=res_boxsimu_1. | -| P0 | 6 | runtime_blocker | m3-gateway-simu-identity | DEV exposes two gateway-simu endpoints, but live gateway identities are not distinct; observed identities=gateway-a:gws_gateway-a. | -| P0 | 6 | runtime_blocker | m3-patch-panel-wiring | DEV patch-panel is callable, but live wiring does not contain res_boxsimu_1:DO1 -> res_boxsimu_2:DI1; active=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0; configured=res_boxsim_alpha:uart0->res_boxsim_beta:uart0, res_boxsim_alpha:gpio0->res_boxsim_beta:gpio0. | -| P0 | 6 | runtime_blocker | m3-hardware-loop-runtime | Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing. | -| P3 | 99 | safety_blocker | m3-live-write-authorization | Plan-only mode is intentionally non-mutating; run the bounded live smoke only after explicit DEV/non-PROD approval and after read-only preconditions identify the exact HWLAB targets. | - -## Next Unblock Order - -1. Run the DEV artifact publish workflow, then record only real sha256 digests with `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`; if publish is still blocked, keep not_published via `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`. - Evidence required: Artifact publish report with ciPublished=true, registryVerified=true, and sha256 digest for each frozen service ID. -2. Provide the approved read-only runner kubeconfig mount or document the intended alternate KUBECONFIG path, then rerun the read-only report. - Evidence required: Read-only report with runnerKubeconfigReadable, runnerKubeconfigProbeExitCode/stderr, d601PublicEndpointsReachable, and d601K3sUnavailable recorded separately, plus direct M3 service target discovery before any DO write. -3. Inject explicit skills commit and version into agent manager, skills service, and worker jobs, then rerun the M4 read-only/live preflight. - Evidence required: Read-only hwlab-agent-mgr/hwlab-agent-skills health plus worker dry-run manifest showing skillCommitId/skillVersion and HWLAB_SKILL_COMMIT_ID/HWLAB_SKILL_VERSION injected. -4. Fix DEV box-simu instance identity so direct endpoints expose distinct res_boxsimu_1 and res_boxsimu_2 resources. - Evidence required: Read-only direct box-simu /health/live and /status output showing distinct res_boxsimu_1 and res_boxsimu_2 resources. -5. Resolve the blocker and attach source/local/dry-run/DEV-live evidence at the correct level. - Evidence required: A committed report with the exact evidence level and command used. - -## Validation - -- `node --check scripts/dev-evidence-blocker-aggregator.mjs` -- `node --check scripts/src/dev-evidence-blocker-aggregator.mjs` -- `node scripts/dev-evidence-blocker-aggregator.mjs --check` -- `node scripts/dev-evidence-blocker-aggregator.mjs --markdown` -- `node --check scripts/validate-dev-gate-report.mjs` -- `node scripts/validate-dev-gate-report.mjs` - -## Boundary - -This report reads committed reports and fixtures only. It does not deploy, call DEV, call PROD, read secrets, restart runtime, run heavy e2e, or substitute UniDesk runtime for HWLAB runtime. diff --git a/reports/dev-gate/dev-mvp-gate-report.json b/reports/dev-gate/dev-mvp-gate-report.json deleted file mode 100644 index c634dbbb..00000000 --- a/reports/dev-gate/dev-mvp-gate-report.json +++ /dev/null @@ -1,283 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-mvp-gate-report.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#39", - "taskId": "dev-mvp-gate-report", - "commitId": "86d769525caf1db513b93cb67c2c8ee8863dd4b1", - "acceptanceLevel": "dev_mvp_gate", - "devOnly": true, - "prodDisabled": true, - "gateStatus": "blocked", - "sourceContract": { - "status": "pass", - "documents": [ - "docs/dev-gate-report.md", - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/m1-local-smoke.md", - "docs/m2-dev-deploy-smoke.md", - "docs/m3-hardware-loop.md", - "docs/m4-agent-loop.md", - "docs/m5-mvp-e2e.md", - "docs/runtime-boundary-guard.md", - "docs/topology-constraints.md", - "docs/artifact-catalog.md", - "docs/schema-drift-map.md", - "protocol/README.md", - "protocol/audit.md", - "protocol/evidence-chain.md", - "protocol/mvp-e2e-contract.md" - ], - "summary": "The DEV gate is anchored to the frozen matrix, gate contract, and M0-M5 milestone docs on origin/main." - }, - "validationCommands": [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "localSmoke": { - "status": "pass", - "commands": [ - "node scripts/validate-contract.mjs", - "node scripts/validate-m0-contract.mjs", - "node scripts/validate-artifact-catalog.mjs", - "node scripts/validate-runtime-boundary.mjs", - "node scripts/validate-schema-drift-map.mjs", - "node scripts/validate-evidence-chain.mjs", - "node scripts/m1-contract-smoke.mjs", - "node scripts/m2-dev-deploy-smoke.mjs --dry-run", - "node scripts/m3-hardware-loop-smoke.mjs", - "node scripts/m4-agent-loop-smoke.mjs" - ], - "evidence": [ - "validated 15 JSON files and 13 service ids", - "validated M0 contract examples: 7 JSON files, 13 service ids", - "validated 13 DEV artifact catalog services at 24eb3bf", - "validated HWLAB runtime boundary guard for 13 services; forbidden substitutes: unidesk-backend, provider-gateway, microservice-proxy", - "validated schema drift map with 59 aligned fields and 12 gaps", - "validated 2 evidence chain fixtures and 2 evidence records", - "[m1-smoke] passed", - "[m2-smoke] validated 11 service contracts", - "[m2-smoke] mode=dry-run endpoint=http://74.48.78.17:16667", - "[m2-smoke] no real DEV/PROD request was made", - "M3 hardware loop smoke passed", - "[m4-smoke] ok m4-agent-automation-loop-local-contract-smoke" - ], - "summary": "M0-M4 contract and smoke checks pass locally without contacting the live DEV ingress." - }, - "dryRun": { - "status": "pass", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs health", - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/m5-mvp-e2e-dry-run.mjs" - ], - "evidence": [ - "hwlab-cli health -> status: ok, environment: dev, endpoint: http://74.48.78.17:16667, projects: 2", - "hwlab-cli dry-run -> 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls", - "M5 MVP E2E dry-run passed: 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls" - ], - "summary": "The M5 orchestration dry-run is green, but it remains fixture-only and does not establish a live DEV gate." - }, - "devPreconditions": { - "status": "blocked", - "classification": "runtime durable adapter; skills injection; hardware loop", - "requirements": [ - "http://74.48.78.17:16666/ returns the DEV browser entry", - "http://74.48.78.17:16667/health returns HWLAB DEV edge JSON", - "http://74.48.78.17:16667/health/live returns HWLAB cloud-api JSON with DB live readiness and runtime durability separated", - "Runtime durable adapter proves postgres schema, migration 0001_cloud_core_skeleton, and read-query readiness before M4/M5 can promote", - "Agent manager, skills service, and worker jobs expose explicit skill commit/version before M4/M5 can promote", - "M3 hardware loop proves box-simu-1 DO1 -> box-simu-2 DI1 via patch-panel", - "Keep the run DEV-only and avoid PROD, heavy e2e, secrets, restarts, or UniDesk substitution" - ], - "commands": [ - "curl -fsS --max-time 8 http://74.48.78.17:16666/", - "curl -fsS --max-time 8 http://74.48.78.17:16667/health/live", - "curl -fsS --max-time 8 http://74.48.78.17:16667/health", - "HWLAB_DEV_BOX_SIMU_1_URL=http://10.42.0.200:7201 HWLAB_DEV_BOX_SIMU_2_URL=http://10.42.0.208:7201 HWLAB_DEV_GATEWAY_SIMU_1_URL=http://10.42.0.209:7101 HWLAB_DEV_GATEWAY_SIMU_2_URL=http://10.42.0.204:7101 HWLAB_DEV_PATCH_PANEL_URL=http://10.42.0.205:7301 node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "http://74.48.78.17:16666/ -> HTTP 200 text/html, title=HWLAB DEV MVP Gate", - "http://74.48.78.17:16667/health -> HTTP 200 serviceId=hwlab-edge-proxy status=ok commit=d881a50", - "http://74.48.78.17:16667/health/live -> HTTP 200 serviceId=hwlab-cloud-api status=degraded db.ready=true db.connected=true db.liveDbEvidence=true runtime.blocker=runtime_durable_adapter_query_blocked runtime.migration=0001_cloud_core_skeleton", - "dev-edge-health active report remains older DB-live evidence and is superseded for M4/M5 blocker naming by current d881a50 M4 preflight evidence", - "dev-m3-hardware-loop active report -> blocked, classification=hardware loop, summary=patch-panel active DO1 -> DI1 connection missing", - "dev-m4-agent-loop active report -> blocked, classification=runtime_durable_adapter_query_blocked, summary=runtime durable adapter query/migration readiness is blocked; skills commit/version injection is incomplete" - ], - "summary": "Public DEV route/frp is reachable on 16666/16667, but live MVP remains blocked by runtime_durable_adapter_query_blocked, skills commit/version injection, and the M3 hardware loop topology; no DB-live-only blocker is counted as current M4/M5 evidence." - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "runtime-durable-adapter", - "status": "open", - "sourceIssue": "pikasTech/HWLAB#164", - "classification": "runtime_durable_adapter_query_blocked", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton is missing or not checked." - }, - { - "type": "agent_blocker", - "scope": "skills-commit-version-injection", - "status": "open", - "sourceIssue": "pikasTech/HWLAB#164", - "classification": "skills injection", - "summary": "DEV skills injection is incomplete: hwlab-agent-skills exposes HWLAB_SKILLS_COMMIT_ID=d881a50 but no HWLAB_SKILLS_VERSION; worker template only exposes session mode unless commit/version are injected." - }, - { - "type": "runtime_blocker", - "scope": "m3-hardware-loop-runtime", - "status": "open", - "classification": "hardware loop", - "summary": "Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing." - } - ], - "milestones": [ - { - "id": "M0", - "status": "pass", - "commands": [ - "node scripts/validate-contract.mjs", - "node scripts/validate-m0-contract.mjs", - "node scripts/validate-artifact-catalog.mjs", - "node scripts/validate-runtime-boundary.mjs", - "node scripts/validate-schema-drift-map.mjs", - "node scripts/validate-evidence-chain.mjs" - ], - "evidence": [ - "validated 15 JSON files and 13 service ids", - "validated M0 contract examples: 7 JSON files, 13 service ids", - "validated 13 DEV artifact catalog services at 24eb3bf", - "validated HWLAB runtime boundary guard for 13 services; forbidden substitutes: unidesk-backend, provider-gateway, microservice-proxy", - "validated schema drift map with 59 aligned fields and 12 gaps", - "validated 2 evidence chain fixtures and 2 evidence records" - ], - "summary": "Frozen contract, artifact, boundary, drift, and evidence fixtures are parseable and aligned to the DEV boundary." - }, - { - "id": "M1", - "status": "pass", - "commands": [ - "node scripts/m1-contract-smoke.mjs" - ], - "evidence": [ - "[m1-smoke] ok cloud-api health/live/rpc skeleton", - "[m1-smoke] ok patch-panel/sim status and route skeleton", - "[m1-smoke] ok CLI dry-run fixture parse", - "[m1-smoke] passed" - ], - "summary": "The local M1 contract smoke passes." - }, - { - "id": "M2", - "status": "pass", - "commands": [ - "node scripts/m2-dev-deploy-smoke.mjs --dry-run" - ], - "evidence": [ - "[m2-smoke] validated 11 service contracts", - "[m2-smoke] mode=dry-run endpoint=http://74.48.78.17:16667", - "[m2-smoke] no real DEV/PROD request was made" - ], - "summary": "The deploy smoke remains fixture-only and validates the frozen route phases and artifact metadata." - }, - { - "id": "M3", - "status": "blocked", - "commands": [ - "node scripts/m3-hardware-loop-smoke.mjs", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "Local M3 hardware loop smoke passed", - "Live DEV M3 smoke reached 16667 ingress and direct simulator/patch-panel Pod URLs", - "Live DEV M3 blocked: patch-panel active DO1 -> DI1 connection missing" - ], - "summary": "Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology.", - "blocker": "hardware loop" - }, - { - "id": "M4", - "status": "blocked", - "commands": [ - "node scripts/m4-agent-loop-smoke.mjs", - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - "evidence": [ - "[m4-smoke] ok m4-agent-automation-loop-local-contract-smoke", - "Live DEV M4 preflight blocked before agent scheduling: runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton missing/not checked; skills version injection missing" - ], - "summary": "Local M4 remains green, but active DEV-LIVE M4 is blocked at runtime durable adapter readiness and skills commit/version injection before agent runtime scheduling.", - "blocker": "runtime_durable_adapter_query_blocked; skills injection" - }, - { - "id": "M5", - "status": "blocked", - "commands": [ - "node tools/hwlab-cli/bin/hwlab-cli.mjs health", - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/m5-mvp-e2e-dry-run.mjs" - ], - "evidence": [ - "hwlab-cli health -> status: ok, environment: dev, endpoint: http://74.48.78.17:16667, gateStatus: blocked", - "hwlab-cli dry-run -> 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls", - "M5 MVP E2E dry-run passed: 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls", - "Live MVP remains blocked by runtime durable adapter, skills injection, and M3 hardware loop active reports" - ], - "summary": "The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance is blocked by runtime durable adapter, skills injection, and hardware loop evidence.", - "blocker": "runtime durable adapter; skills injection; hardware loop" - } - ], - "notes": "Active #64 DEV retest report updated for current d881a50 evidence. Public 16666/16667 route and DB live evidence are reachable; live MVP remains blocked. No PROD, secret read, runtime restart, long-running agent task, or UniDesk runtime substitution was used.", - "blockedClassification": [ - { - "category": "runtime durable adapter", - "status": "blocked", - "evidence": "runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton missing/not checked; db.liveDbEvidence=true is not durability evidence" - }, - { - "category": "skills injection", - "status": "blocked", - "evidence": "hwlab-agent-skills has HWLAB_SKILLS_COMMIT_ID=d881a50 but no HWLAB_SKILLS_VERSION; agent manager health misses skillCommitId/skillVersion" - }, - { - "category": "agent runtime", - "status": "not_run", - "evidence": "M4 stopped before agent scheduling because runtime durability and skills injection are blocked" - }, - { - "category": "hardware loop", - "status": "blocked", - "evidence": "M3 stopped on missing patch-panel DO1 -> DI1 active connection" - }, - { - "category": "evidence/audit", - "status": "not_run", - "evidence": "M3/M4 did not reach accepted hardware operations requiring audit/evidence closure" - }, - { - "category": "CD apply", - "status": "not_run", - "evidence": "No deploy/apply command was run in this #64 retest" - }, - { - "category": "frp", - "status": "pass", - "evidence": "16666 browser, 16667/health, 16667/health/live, frps 7000, and tunnel health 7402 are reachable" - }, - { - "category": "DB live", - "status": "pass", - "evidence": "Current d881a50 read-only M4 preflight evidence reports db.ready=true, db.connected=true, and liveDbEvidence=true; this does not clear runtime durability" - } - ], - "reportLifecycle": { - "version": "v1", - "state": "active", - "activeEndpoint": "http://74.48.78.17:16667", - "activeBrowserEndpoint": "http://74.48.78.17:16666", - "deprecatedEndpoint": null, - "summary": "Current MVP gate report; M3 DEV-LIVE remains blocked until the patch-panel trusted loop is proven." - } -} diff --git a/reports/dev-gate/dev-mvp-gate-report.md b/reports/dev-gate/dev-mvp-gate-report.md deleted file mode 100644 index fc7348bf..00000000 --- a/reports/dev-gate/dev-mvp-gate-report.md +++ /dev/null @@ -1,63 +0,0 @@ -# HWLAB DEV M5 Gate Report - -Status: blocked - -Baseline: `86d769525caf1db513b93cb67c2c8ee8863dd4b1` -Scope: DEV only - -## Summary - -Public DEV route/frp is reachable on 16666/16667, and current DB live evidence is ready, but live MVP remains blocked by runtime_durable_adapter_query_blocked, skills commit/version injection, and the M3 hardware loop topology; no historical 6667 failure is counted as active evidence. - -## Commands - -- `node scripts/validate-contract.mjs` -- `node scripts/validate-m0-contract.mjs` -- `node scripts/validate-artifact-catalog.mjs` -- `node scripts/validate-runtime-boundary.mjs` -- `node scripts/validate-schema-drift-map.mjs` -- `node scripts/validate-evidence-chain.mjs` -- `node scripts/m1-contract-smoke.mjs` -- `node scripts/m2-dev-deploy-smoke.mjs --dry-run` -- `node scripts/m3-hardware-loop-smoke.mjs` -- `node scripts/m4-agent-loop-smoke.mjs` -- `node tools/hwlab-cli/bin/hwlab-cli.mjs health` -- `node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run` -- `node scripts/m5-mvp-e2e-dry-run.mjs` -- `curl -fsS --max-time 8 http://74.48.78.17:16666/` -- `curl -fsS --max-time 8 http://74.48.78.17:16667/health/live` -- `curl -fsS --max-time 8 http://74.48.78.17:16667/health` -- `HWLAB_DEV_BOX_SIMU_1_URL=http://10.42.0.200:7201 HWLAB_DEV_BOX_SIMU_2_URL=http://10.42.0.208:7201 HWLAB_DEV_GATEWAY_SIMU_1_URL=http://10.42.0.209:7101 HWLAB_DEV_GATEWAY_SIMU_2_URL=http://10.42.0.204:7101 HWLAB_DEV_PATCH_PANEL_URL=http://10.42.0.205:7301 node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production` -- `node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production` - -## Milestones - -- `M0`: pass - Frozen contract, artifact, boundary, drift, and evidence fixtures are parseable and aligned to the DEV boundary. -- `M1`: pass - The local M1 contract smoke passes. -- `M2`: pass - The deploy smoke remains fixture-only and validates the frozen route phases and artifact metadata. -- `M3`: blocked - Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology. -- `M4`: blocked - Local M4 remains green, but active DEV-LIVE M4 is blocked at runtime durable adapter readiness and skills commit/version injection before agent runtime scheduling. -- `M5`: blocked - The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance is blocked by runtime durable adapter, skills injection, and hardware loop evidence. - -## Blocked Classification - -| Category | Status | Evidence | -| --- | --- | --- | -| runtime durable adapter | blocked | runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton missing/not checked; db.liveDbEvidence=true is not durability evidence | -| skills injection | blocked | hwlab-agent-skills has HWLAB_SKILLS_COMMIT_ID=d881a50 but no HWLAB_SKILLS_VERSION; agent manager health misses skillCommitId/skillVersion | -| agent runtime | not_run | M4 stopped before agent scheduling because runtime durability and skills injection are blocked | -| hardware loop | blocked | M3 stopped on missing patch-panel DO1 -> DI1 active connection | -| evidence/audit | not_run | M3/M4 did not reach accepted hardware operations requiring audit/evidence closure | -| CD apply | not_run | No deploy/apply command was run in this #64 retest | -| frp | pass | 16666 browser, 16667/health, 16667/health/live, frps 7000, and tunnel health 7402 are reachable | -| DB live | pass | Current d881a50 read-only M4 preflight evidence reports db.ready=true, db.connected=true, and liveDbEvidence=true; this does not clear runtime durability | - -## Blockers - -- runtime durable adapter: cloud-api durable runtime adapter is blocked by runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton is missing or not checked. -- skills injection: hwlab-agent-skills exposes HWLAB_SKILLS_COMMIT_ID=d881a50 but no HWLAB_SKILLS_VERSION; agent manager health misses skillCommitId/skillVersion. -- hardware loop: Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing. - -## Boundary - -Active #64 DEV retest report. Public 16666/16667 route and DB live evidence are reachable; live MVP remains blocked. No PROD, secret read, runtime restart, long-running agent task, or UniDesk runtime substitution was used. diff --git a/reports/dev-gate/dev-preflight-report.json b/reports/dev-gate/dev-preflight-report.json deleted file mode 100644 index 2396d332..00000000 --- a/reports/dev-gate/dev-preflight-report.json +++ /dev/null @@ -1,1704 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-gate-preflight-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-preflight-report.json", - "reportVersion": "v1", - "reportKind": "dev-gate-preflight", - "issue": "pikasTech/HWLAB#34", - "supports": [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#12", - "pikasTech/HWLAB#14", - "pikasTech/HWLAB#22", - "pikasTech/HWLAB#23", - "pikasTech/HWLAB#29", - "pikasTech/HWLAB#30", - "pikasTech/HWLAB#31", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#43", - "pikasTech/HWLAB#48", - "pikasTech/HWLAB#49", - "pikasTech/HWLAB#57", - "pikasTech/HWLAB#66" - ], - "target": { - "ref": "origin/main", - "commitId": "f64182b54fe6153a1ec0343cabf63b690df74f4e", - "shortCommitId": "f64182b" - }, - "generatedAt": "2026-05-22T16:59:48.691Z", - "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", - "unidesk-runtime-substitute", - "heavy-e2e", - "browser-e2e", - "force-push", - "runtime-restart" - ], - "validationCommands": [ - "node --check scripts/dev-gate-preflight.mjs", - "node --check scripts/src/dev-gate-preflight.mjs", - "node --check scripts/src/registry-capabilities.mjs", - "node --check scripts/refresh-artifact-catalog.mjs", - "node scripts/dev-gate-preflight.mjs", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - "artifactIdentity": { - "source": { - "ref": "origin/main", - "commitId": "f64182b54fe6153a1ec0343cabf63b690df74f4e", - "shortCommitId": "f64182b" - }, - "artifactSource": { - "source": "artifact-publish-report", - "ref": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "commitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "shortCommitId": "7e29522" - }, - "targetCoverage": { - "sourceKind": "artifact-publish-report", - "sourceRef": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "sourceCommitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "sourceShortCommitId": "7e29522", - "targetRef": "origin/main", - "targetCommitId": "f64182b54fe6153a1ec0343cabf63b690df74f4e", - "targetShortCommitId": "f64182b", - "mode": "artifact-input-drift", - "covered": false, - "artifactBuildInputChanged": true, - "changedPathCount": 114, - "changedArtifactInputPaths": [ - "cmd/hwlab-box-simu/main.mjs", - "cmd/hwlab-gateway-simu/main.mjs", - "cmd/hwlab-patch-panel/main.mjs", - "internal/cloud/code-agent-chat.mjs", - "internal/cloud/db-contract.mjs", - "internal/cloud/json-rpc.test.mjs", - "internal/cloud/server.mjs", - "internal/cloud/server.test.mjs", - "internal/dev-report-lifecycle.mjs", - "internal/mvp-gate/summary.mjs", - "internal/patchpanel/fixture.mjs", - "internal/patchpanel/model.test.mjs", - "internal/protocol/index.mjs", - "package.json", - "scripts/dev-artifact-publish.mjs", - "tools/hwlab-cli/lib/cli.mjs", - "web/hwlab-cloud-web/app.mjs", - "web/hwlab-cloud-web/gate-summary.mjs", - "web/hwlab-cloud-web/help.md", - "web/hwlab-cloud-web/index.html", - "web/hwlab-cloud-web/package.json", - "web/hwlab-cloud-web/runtime.mjs", - "web/hwlab-cloud-web/scripts/build.mjs", - "web/hwlab-cloud-web/scripts/check.mjs", - "web/hwlab-cloud-web/scripts/dist-contract.mjs", - "web/hwlab-cloud-web/scripts/m3-readonly-contract.mjs", - "web/hwlab-cloud-web/scripts/workbench-hardware-panel-contract.mjs", - "web/hwlab-cloud-web/styles.css", - "web/hwlab-cloud-web/third_party/marked/LICENSE", - "web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "web/hwlab-cloud-web/workbench-hardware-panel.mjs" - ], - "changedNonArtifactPathCount": 83, - "reason": "target changes since the artifact source touch artifact build inputs" - }, - "deployManifest": { - "path": "deploy/deploy.json", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false - }, - "artifactCatalog": { - "path": "deploy/artifact-catalog.dev.json", - "commitId": "c7de474", - "artifactState": "contract-skeleton", - "ciPublished": false, - "registryVerified": false, - "provenance": "not_available_until_publish", - "digestCounts": { - "sha256": 0, - "notPublished": 13, - "invalid": 0 - }, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "matchesSource": false, - "matchesTarget": false - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-cloud-web", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-mgr", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-worker", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-gateway", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-gateway-simu", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-box-simu", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-patch-panel", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-router", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-router:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-tunnel-client", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-edge-proxy", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-cli", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cli:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-skills", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - } - ], - "serviceCommitIds": [ - "c7de474" - ], - "matchesSource": false, - "publishVerified": false, - "refreshCommands": { - "blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked", - "published": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json" - } - }, - "registryCapabilities": { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-22T16:59:47.814Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 39 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "context": { - "command": "kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-75544c7747-5dzt9", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c16c733306a28ae9385087653b79f5bb2f28724d917a89bb296646f6a02c5318" - }, - { - "pod": "hwlab-agent-skills-849d9976f4-2nsmw", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:246fb7bb48597a7179aab62cb091e8213e47b71b6871ba94e6028032992e916c" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-pftxc", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-sq5fx", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-cloud-api-74c969765d-kzvdd", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:86fe92c0dd5d5883851b38805e6f5acc49d2876552599c18f85480cf0d3bf981" - }, - { - "pod": "hwlab-cloud-web-7d48b84f4b-n6hw6", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:91cfacc1fc011a96bb47ff00d2ab09ee3c93003b7ec482308ec5ac35004e5627" - }, - { - "pod": "hwlab-edge-proxy-567f978cdd-8fwc8", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:384832fc579063b9b0a940973690734bb402607720fd36f4662e4ee6fa5a57d2" - }, - { - "pod": "hwlab-frpc-59f57d479c-wm5l2", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-j9pk7", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-phcgp", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-patch-panel-757f9f44d5-7np8r", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:5b3aa6505492c2ed398d21cbd35a8754672114acd0fa97faa3351d4b6a6dfd5a" - }, - { - "pod": "hwlab-router-5d9cbdf89b-p7r94", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:217b1fdb0f704c4183e9d8503644177f0c8bae674da0433985c19ef909f0e342" - }, - { - "pod": "hwlab-tunnel-client-65c7858c8-wjqsh", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:9bcdd8a58132d7bc4dbfc71297f802fc0ab407a1b5c1bb1ea58e0791147a243b" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - }, - "conclusion": "blocked", - "checks": [ - { - "id": "source-contract-static", - "category": "contract", - "status": "pass", - "summary": "Local deploy manifest and artifact catalog are internally consistent.", - "evidence": [] - }, - { - "id": "target-commit-pinning", - "category": "contract", - "status": "blocked", - "summary": "Source origin/main f64182b is not covered by artifact source 7e29522: target changes since the artifact source touch artifact build inputs.", - "evidence": [ - { - "source": { - "ref": "origin/main", - "commitId": "f64182b54fe6153a1ec0343cabf63b690df74f4e", - "shortCommitId": "f64182b" - }, - "artifactSource": { - "source": "artifact-publish-report", - "ref": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "commitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "shortCommitId": "7e29522" - }, - "targetCoverage": { - "sourceKind": "artifact-publish-report", - "sourceRef": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "sourceCommitId": "7e29522b65c8a316b7c3fde12bebcb5f6baf8c33", - "sourceShortCommitId": "7e29522", - "targetRef": "origin/main", - "targetCommitId": "f64182b54fe6153a1ec0343cabf63b690df74f4e", - "targetShortCommitId": "f64182b", - "mode": "artifact-input-drift", - "covered": false, - "artifactBuildInputChanged": true, - "changedPathCount": 114, - "changedArtifactInputPaths": [ - "cmd/hwlab-box-simu/main.mjs", - "cmd/hwlab-gateway-simu/main.mjs", - "cmd/hwlab-patch-panel/main.mjs", - "internal/cloud/code-agent-chat.mjs", - "internal/cloud/db-contract.mjs", - "internal/cloud/json-rpc.test.mjs", - "internal/cloud/server.mjs", - "internal/cloud/server.test.mjs", - "internal/dev-report-lifecycle.mjs", - "internal/mvp-gate/summary.mjs", - "internal/patchpanel/fixture.mjs", - "internal/patchpanel/model.test.mjs", - "internal/protocol/index.mjs", - "package.json", - "scripts/dev-artifact-publish.mjs", - "tools/hwlab-cli/lib/cli.mjs", - "web/hwlab-cloud-web/app.mjs", - "web/hwlab-cloud-web/gate-summary.mjs", - "web/hwlab-cloud-web/help.md", - "web/hwlab-cloud-web/index.html", - "web/hwlab-cloud-web/package.json", - "web/hwlab-cloud-web/runtime.mjs", - "web/hwlab-cloud-web/scripts/build.mjs", - "web/hwlab-cloud-web/scripts/check.mjs", - "web/hwlab-cloud-web/scripts/dist-contract.mjs", - "web/hwlab-cloud-web/scripts/m3-readonly-contract.mjs", - "web/hwlab-cloud-web/scripts/workbench-hardware-panel-contract.mjs", - "web/hwlab-cloud-web/styles.css", - "web/hwlab-cloud-web/third_party/marked/LICENSE", - "web/hwlab-cloud-web/third_party/marked/marked.esm.js", - "web/hwlab-cloud-web/workbench-hardware-panel.mjs" - ], - "changedNonArtifactPathCount": 83, - "reason": "target changes since the artifact source touch artifact build inputs" - }, - "deployManifest": { - "path": "deploy/deploy.json", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false - }, - "artifactCatalog": { - "path": "deploy/artifact-catalog.dev.json", - "commitId": "c7de474", - "artifactState": "contract-skeleton", - "ciPublished": false, - "registryVerified": false, - "provenance": "not_available_until_publish", - "digestCounts": { - "sha256": 0, - "notPublished": 13, - "invalid": 0 - }, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "matchesSource": false, - "matchesTarget": false - }, - "services": [ - { - "serviceId": "hwlab-cloud-api", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-cloud-web", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-mgr", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-worker", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-gateway", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-gateway-simu", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-box-simu", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-patch-panel", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-router", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-router:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-tunnel-client", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-edge-proxy", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-cli", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-cli:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - }, - { - "serviceId": "hwlab-agent-skills", - "commitId": "c7de474", - "matchesSource": false, - "matchesTarget": false, - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:c7de474", - "imageTag": "c7de474", - "digest": "not_published", - "publishState": "skeleton-only", - "artifactRequired": true, - "notPublishedReason": "publish_not_run" - } - ], - "serviceCommitIds": [ - "c7de474" - ], - "matchesSource": false, - "publishVerified": false, - "refreshCommands": { - "blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked", - "published": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json" - } - } - ] - }, - { - "id": "artifact-catalog-publish-state", - "category": "registry", - "status": "blocked", - "summary": "Artifact catalog publish state is contract-skeleton; ciPublished=false, registryVerified=false, digests sha256=0, not_published=13, invalid=0.", - "evidence": [ - { - "path": "deploy/artifact-catalog.dev.json", - "commitId": "c7de474", - "artifactState": "contract-skeleton", - "ciPublished": false, - "registryVerified": false, - "provenance": "not_available_until_publish", - "digestCounts": { - "sha256": 0, - "notPublished": 13, - "invalid": 0 - }, - "requiredServiceCount": 13, - "disabledServiceCount": 0, - "matchesSource": false, - "matchesTarget": false - } - ] - }, - { - "id": "k8s-manifest-static", - "category": "k3s", - "status": "pass", - "summary": "DEV k3s manifest files parse and stay scoped to hwlab-dev.", - "evidence": [] - }, - { - "id": "registry-capability-process-http-access", - "category": "registry", - "status": "degraded", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": [ - { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - } - ] - }, - { - "id": "registry-capability-docker-daemon-push-access", - "category": "registry", - "status": "pass", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": [ - { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 39 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - } - ] - }, - { - "id": "registry-capability-k3s-pull-access", - "category": "registry", - "status": "pass", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": [ - { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "context": { - "command": "kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-75544c7747-5dzt9", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c16c733306a28ae9385087653b79f5bb2f28724d917a89bb296646f6a02c5318" - }, - { - "pod": "hwlab-agent-skills-849d9976f4-2nsmw", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:246fb7bb48597a7179aab62cb091e8213e47b71b6871ba94e6028032992e916c" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-pftxc", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-sq5fx", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-cloud-api-74c969765d-kzvdd", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:86fe92c0dd5d5883851b38805e6f5acc49d2876552599c18f85480cf0d3bf981" - }, - { - "pod": "hwlab-cloud-web-7d48b84f4b-n6hw6", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:91cfacc1fc011a96bb47ff00d2ab09ee3c93003b7ec482308ec5ac35004e5627" - }, - { - "pod": "hwlab-edge-proxy-567f978cdd-8fwc8", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:384832fc579063b9b0a940973690734bb402607720fd36f4662e4ee6fa5a57d2" - }, - { - "pod": "hwlab-frpc-59f57d479c-wm5l2", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-j9pk7", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-phcgp", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-patch-panel-757f9f44d5-7np8r", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:5b3aa6505492c2ed398d21cbd35a8754672114acd0fa97faa3351d4b6a6dfd5a" - }, - { - "pod": "hwlab-router-5d9cbdf89b-p7r94", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:217b1fdb0f704c4183e9d8503644177f0c8bae674da0433985c19ef909f0e342" - }, - { - "pod": "hwlab-tunnel-client-65c7858c8-wjqsh", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:9bcdd8a58132d7bc4dbfc71297f802fc0ab407a1b5c1bb1ea58e0791147a243b" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - ] - }, - { - "id": "registry-capability-classification", - "category": "registry", - "status": "degraded", - "summary": "Registry capability classification is degraded: process HTTP, Docker daemon push path, and k3s pull path are reported separately.", - "evidence": [ - { - "version": "v1", - "registryPrefix": "127.0.0.1:5000/hwlab", - "generatedAt": "2026-05-22T16:59:47.814Z", - "classification": "degraded", - "dimensions": { - "processHttpAccess": { - "id": "process-http-access", - "status": "degraded", - "requiredForPublish": false, - "requiredForDeploy": false, - "endpoint": "http://127.0.0.1:5000/v2/", - "probeKind": "runner-process-http-v2", - "summary": "Runner process cannot reach http://127.0.0.1:5000/v2/; this is process HTTP access only and does not prove Docker daemon push failure.", - "evidence": { - "probe": { - "ok": false, - "url": "http://127.0.0.1:5000/v2/", - "method": "GET", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - "publishFailure": false - } - }, - "dockerDaemonPushAccess": { - "id": "docker-daemon-push-access", - "status": "pass", - "requiredForPublish": true, - "requiredForDeploy": false, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "docker-ps-read-only", - "summary": "Docker daemon can see a local/internal registry target for publish preflight purposes; no push was attempted.", - "evidence": { - "dockerVersion": { - "command": "docker --version", - "ok": true, - "exitCode": 0, - "stdout": "Docker version 26.1.5+dfsg1, build a72d7cd", - "stderr": "", - "error": null - }, - "registryPrefix": "127.0.0.1:5000/hwlab", - "loopbackPort5000": true, - "registryContainerVisible": true, - "matchingContainers": [ - { - "id": "00091efce380", - "image": "registry:2.8.3", - "names": "unidesk-artifact-registry", - "ports": "127.0.0.1:5000->5000/tcp", - "state": "running", - "status": "Up 39 hours" - } - ], - "pushAttempted": false, - "mutationAttempted": false - } - }, - "k3sPullAccess": { - "id": "k3s-pull-access", - "status": "pass", - "requiredForPublish": false, - "requiredForDeploy": true, - "endpoint": "127.0.0.1:5000/hwlab", - "probeKind": "kubectl-read-only", - "summary": "Read-only kubectl observed hwlab-dev pods with pulled registry images.", - "evidence": { - "kubectlAvailable": true, - "context": { - "command": "kubectl config current-context", - "ok": true, - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "", - "error": null - }, - "canGetPods": { - "command": "kubectl auth can-i get pods -n hwlab-dev", - "ok": true, - "exitCode": 0, - "stdout": "yes", - "stderr": "", - "error": null - }, - "podCount": 13, - "pulledImages": [ - { - "pod": "hwlab-agent-mgr-75544c7747-5dzt9", - "container": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-mgr@sha256:c16c733306a28ae9385087653b79f5bb2f28724d917a89bb296646f6a02c5318" - }, - { - "pod": "hwlab-agent-skills-849d9976f4-2nsmw", - "container": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-agent-skills@sha256:246fb7bb48597a7179aab62cb091e8213e47b71b6871ba94e6028032992e916c" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-pftxc", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-box-simu-55688cbcc7-sq5fx", - "container": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-box-simu@sha256:ae5fcc06a276918561c2d752f2221156bd2bd4db7fb93967ae76af97bfdc51bf" - }, - { - "pod": "hwlab-cloud-api-74c969765d-kzvdd", - "container": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-api@sha256:86fe92c0dd5d5883851b38805e6f5acc49d2876552599c18f85480cf0d3bf981" - }, - { - "pod": "hwlab-cloud-web-7d48b84f4b-n6hw6", - "container": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "imageID": "127.0.0.1:5000/hwlab/hwlab-cloud-web@sha256:91cfacc1fc011a96bb47ff00d2ab09ee3c93003b7ec482308ec5ac35004e5627" - }, - { - "pod": "hwlab-edge-proxy-567f978cdd-8fwc8", - "container": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-edge-proxy@sha256:384832fc579063b9b0a940973690734bb402607720fd36f4662e4ee6fa5a57d2" - }, - { - "pod": "hwlab-frpc-59f57d479c-wm5l2", - "container": "frpc", - "image": "127.0.0.1:5000/hwlab/frpc:v0.68.1", - "imageID": "127.0.0.1:5000/hwlab/frpc@sha256:ce400cda42a260964e40ab1ca68b868f72c569cdce4b243aef36ce40194d287b" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-j9pk7", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-gateway-simu-699fd486b-phcgp", - "container": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-gateway-simu@sha256:00599721b910b5667b6be43c416cab4c410c6cc29b7f56ecdfb6eff71123d01c" - }, - { - "pod": "hwlab-patch-panel-757f9f44d5-7np8r", - "container": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-patch-panel@sha256:5b3aa6505492c2ed398d21cbd35a8754672114acd0fa97faa3351d4b6a6dfd5a" - }, - { - "pod": "hwlab-router-5d9cbdf89b-p7r94", - "container": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-router@sha256:217b1fdb0f704c4183e9d8503644177f0c8bae674da0433985c19ef909f0e342" - }, - { - "pod": "hwlab-tunnel-client-65c7858c8-wjqsh", - "container": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:cb35ada", - "imageID": "127.0.0.1:5000/hwlab/hwlab-tunnel-client@sha256:9bcdd8a58132d7bc4dbfc71297f802fc0ab407a1b5c1bb1ea58e0791147a243b" - } - ], - "pullFailures": [], - "pullAttempted": false, - "mutationAttempted": false - } - } - }, - "interpretation": { - "processHttpAccess": "Runner process HTTP access to /v2/ is diagnostic only and must not be treated as Docker daemon push access.", - "dockerDaemonPushAccess": "Docker daemon push access is the publish-path capability. This probe is read-only and does not push.", - "k3sPullAccess": "k3s pull access is the deploy-path capability. This probe is read-only and does not create pods or read secrets." - } - } - ] - }, - { - "id": "cloud-api-db-env-contract", - "category": "db", - "status": "pass", - "summary": "cloud-api DEV DB manifest declares the required env names, redacted Secret reference, and stable k3s DB DNS Service.", - "evidence": [ - { - "staticContract": { - "contractVersion": "v1", - "environment": "dev", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "manifestPresent": true, - "k8sPresent": true, - "redacted": true, - "source": "k8s-secret-ref", - "secretRef": { - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "redacted": true - } - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "manifestPresent": true, - "k8sPresent": true, - "redacted": false, - "source": "runtime-env", - "secretRef": null - } - ], - "dns": { - "ready": true, - "host": "cloud-api-db.hwlab-dev.svc.cluster.local", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "selectorless": true, - "secretMaterialRead": false, - "liveDbEvidence": false, - "missing": [], - "actual": { - "deployServiceName": "cloud-api-db", - "deployServiceNamespace": "hwlab-dev", - "deployHost": "cloud-api-db.hwlab-dev.svc.cluster.local", - "deployPort": "5432", - "workloadServiceName": "cloud-api-db", - "workloadServiceNamespace": "hwlab-dev", - "workloadHost": "cloud-api-db.hwlab-dev.svc.cluster.local", - "workloadPort": "5432", - "serviceName": "cloud-api-db", - "serviceNamespace": "hwlab-dev", - "serviceType": "ClusterIP", - "serviceSelector": null, - "portName": "postgres", - "port": 5432, - "targetPort": "postgres" - } - }, - "missingDeployEnv": [], - "missingK8sEnv": [], - "missingSecretRefs": [], - "missingDnsContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false - }, - "healthContract": { - "status": "blocked", - "ready": false, - "configReady": false, - "connected": false, - "liveConnected": false, - "connectionChecked": false, - "connectionAttempted": false, - "connectionResult": "not_attempted_missing_env", - "liveDbEvidence": false, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": false, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": false, - "redacted": false, - "source": "runtime-env" - } - ], - "missingEnv": [ - "HWLAB_CLOUD_DB_URL", - "HWLAB_CLOUD_DB_SSL_MODE" - ], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": false, - "envInjected": false, - "secretPresent": "not_observed_by_runtime", - "secretKeyPresent": "not_observed_by_runtime", - "redacted": true - } - ], - "connection": { - "attempted": false, - "networkAttempted": false, - "result": "not_attempted_missing_env", - "classification": "missing_runtime_env", - "probeType": "tcp-connect", - "endpointRedacted": true, - "valueRedacted": true, - "timeoutMs": null, - "durationMs": 0, - "errorCode": null - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "blocker": "DB runtime config is missing HWLAB_CLOUD_DB_URL, HWLAB_CLOUD_DB_SSL_MODE", - "fixtureEvidence": false - }, - "fixtureEvidence": false, - "liveDbEvidence": false - } - ] - }, - { - "id": "cloud-api-db-health-gate", - "category": "db", - "status": "blocked", - "summary": "cloud-api DB health gate is missing HWLAB_CLOUD_DB_URL, HWLAB_CLOUD_DB_SSL_MODE.", - "evidence": [ - { - "staticContract": { - "contractVersion": "v1", - "environment": "dev", - "ready": true, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "manifestPresent": true, - "k8sPresent": true, - "redacted": true, - "source": "k8s-secret-ref", - "secretRef": { - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": true, - "redacted": true - } - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "manifestPresent": true, - "k8sPresent": true, - "redacted": false, - "source": "runtime-env", - "secretRef": null - } - ], - "dns": { - "ready": true, - "host": "cloud-api-db.hwlab-dev.svc.cluster.local", - "serviceName": "cloud-api-db", - "namespace": "hwlab-dev", - "port": 5432, - "portName": "postgres", - "selectorless": true, - "secretMaterialRead": false, - "liveDbEvidence": false, - "missing": [], - "actual": { - "deployServiceName": "cloud-api-db", - "deployServiceNamespace": "hwlab-dev", - "deployHost": "cloud-api-db.hwlab-dev.svc.cluster.local", - "deployPort": "5432", - "workloadServiceName": "cloud-api-db", - "workloadServiceNamespace": "hwlab-dev", - "workloadHost": "cloud-api-db.hwlab-dev.svc.cluster.local", - "workloadPort": "5432", - "serviceName": "cloud-api-db", - "serviceNamespace": "hwlab-dev", - "serviceType": "ClusterIP", - "serviceSelector": null, - "portName": "postgres", - "port": 5432, - "targetPort": "postgres" - } - }, - "missingDeployEnv": [], - "missingK8sEnv": [], - "missingSecretRefs": [], - "missingDnsContract": [], - "secretMaterialRead": false, - "valuesRedacted": true, - "liveDbEvidence": false, - "fixtureEvidence": false - }, - "healthContract": { - "status": "blocked", - "ready": false, - "configReady": false, - "connected": false, - "liveConnected": false, - "connectionChecked": false, - "connectionAttempted": false, - "connectionResult": "not_attempted_missing_env", - "liveDbEvidence": false, - "requiredEnv": [ - { - "name": "HWLAB_CLOUD_DB_URL", - "present": false, - "redacted": true, - "source": "k8s-secret-ref" - }, - { - "name": "HWLAB_CLOUD_DB_SSL_MODE", - "present": false, - "redacted": false, - "source": "runtime-env" - } - ], - "missingEnv": [ - "HWLAB_CLOUD_DB_URL", - "HWLAB_CLOUD_DB_SSL_MODE" - ], - "secretRefs": [ - { - "env": "HWLAB_CLOUD_DB_URL", - "secretName": "hwlab-cloud-api-dev-db", - "secretKey": "database-url", - "present": false, - "envInjected": false, - "secretPresent": "not_observed_by_runtime", - "secretKeyPresent": "not_observed_by_runtime", - "redacted": true - } - ], - "connection": { - "attempted": false, - "networkAttempted": false, - "result": "not_attempted_missing_env", - "classification": "missing_runtime_env", - "probeType": "tcp-connect", - "endpointRedacted": true, - "valueRedacted": true, - "timeoutMs": null, - "durationMs": 0, - "errorCode": null - }, - "redaction": { - "valuesRedacted": true, - "endpointRedacted": true, - "valueRedacted": true, - "secretMaterialRead": false, - "secretRefsOnly": true - }, - "blocker": "DB runtime config is missing HWLAB_CLOUD_DB_URL, HWLAB_CLOUD_DB_SSL_MODE", - "fixtureEvidence": false - }, - "fixtureEvidence": false, - "liveDbEvidence": false - } - ] - }, - { - "id": "dev-artifact-publish-report", - "category": "registry", - "status": "blocked", - "summary": "DEV artifact publish report status is published with 13/13 required services published for source 7e29522b65c8a316b7c3fde12bebcb5f6baf8c33; source states resolved: 13 source-present, 0 intentionally-disabled.", - "evidence": [] - }, - { - "id": "dev-edge-health-report", - "category": "edge", - "status": "blocked", - "summary": "Committed DEV edge health report is blocked with classification app_health_blocker.", - "evidence": [ - { - "report": "reports/dev-gate/dev-edge-health.json", - "status": "blocked", - "classification": "app_health_blocker", - "publicTcp": [ - { - "host": "74.48.78.17", - "port": 16667, - "status": "connected", - "durationMs": 206 - }, - { - "host": "74.48.78.17", - "port": 7000, - "status": "connected", - "durationMs": 218 - }, - { - "host": "74.48.78.17", - "port": 7402, - "status": "connected", - "durationMs": 207 - } - ], - "kubernetesObservable": true - } - ] - }, - { - "id": "frp-master-edge-static", - "category": "edge", - "status": "pass", - "summary": "FRP and master-edge contracts describe D601-to-master DEV on public ports 16666/16667.", - "evidence": [] - }, - { - "id": "runtime-substitution-boundary", - "category": "safety", - "status": "pass", - "summary": "Deploy images do not point at UniDesk/provider-gateway/microservice-proxy substitutes.", - "evidence": [] - }, - { - "id": "d601-k3s-read-access", - "category": "k3s", - "status": "pass", - "summary": "Read-only kubectl probes reached hwlab-dev.", - "evidence": [ - { - "ok": true, - "command": "kubectl version --client=true", - "exitCode": 0, - "stdout": "Client Version: v1.36.1\nKustomize Version: v5.8.1", - "stderr": "" - }, - { - "ok": true, - "command": "kubectl config current-context", - "exitCode": 0, - "stdout": "in-cluster", - "stderr": "" - }, - { - "ok": true, - "command": "kubectl auth can-i get pods -n hwlab-dev", - "exitCode": 0, - "stdout": "yes", - "stderr": "" - }, - { - "ok": true, - "command": "kubectl get namespace hwlab-dev -o json", - "exitCode": 0, - "stdout": "{\n \"apiVersion\": \"v1\",\n \"kind\": \"Namespace\",\n \"metadata\": {\n \"annotations\": {\n \"kubectl.kubernetes.io/last-applied-configuration\": \"{\\\"apiVersion\\\":\\\"v1\\\",\\\"kind\\\":\\\"Namespace\\\",\\\"metadata\\\":{\\\"annotations\\\":{},\\\"labels\\\":{\\\"app.kubernetes.io/part-of\\\":\\\"hwlab\\\",\\\"hwlab.pikastech.local/environment\\\":\\\"dev\\\",\\\"hwlab.pikastech.local/profile\\\":\\\"dev\\\"},\\\"name\\\":\\\"hwlab-dev\\\"}}\\n\"\n },\n \"creationTimestamp\": \"2026-05-21T23:52:11Z\",\n \"labels\": {\n \"app.kubernetes.io/part-of\": \"hwlab\",\n \"hwlab.pikastech.local/environment\": \"dev\",\n \"hwlab.pikastech.local/profile\": \"dev\",\n \"kubernetes.io/metadata.name\": \"hwlab-dev\"\n },\n \"name\": \"hwlab-dev\",\n \"resourceVersion\": \"1081061\",\n \"uid\": \"97fbde04-c052-46f3-91ee-b15616f7690b\"\n },\n \"spec\": {\n \"finalizers\": [\n \"kubernetes\"\n ]\n },\n \"status\": {\n \"phase\": \"Active\"\n }\n}", - "stderr": "" - }, - { - "ok": true, - "command": "kubectl -n hwlab-dev get deploy,svc,job,cm -o name", - "exitCode": 0, - "stdout": "deployment.apps/hwlab-agent-mgr\ndeployment.apps/hwlab-agent-skills\ndeployment.apps/hwlab-box-simu\ndeployment.apps/hwlab-cloud-api\ndeployment.apps/hwlab-cloud-web\ndeployment.apps/hwlab-edge-proxy\ndeployment.apps/hwlab-frpc\ndeployment.apps/hwlab-gateway\ndeployment.apps/hwlab-gateway-simu\ndeployment.apps/hwlab-patch-panel\ndeployment.apps/hwlab-router\ndeployment.apps/hwlab-tunnel-client\nservice/hwlab-agent-mgr\nservice/hwlab-agent-skills\nservice/hwlab-box-simu\nservice/hwlab-cli\nservice/hwlab-cloud-api\nservice/hwlab-cloud-web\nservice/hwlab-edge-proxy\nservice/hwlab-gateway\nservice/hwlab-gateway-simu\nservice/hwlab-patch-panel\nservice/hwlab-router\nservice/hwlab-tunnel-client\njob.batch/hwlab-agent-worker-template\njob.batch/hwlab-cli-template\nconfigmap/hwlab-dev-health-contract\nconfigmap/hwlab-frpc-config\nconfigmap/kube-root-ca.crt", - "stderr": "" - } - ] - }, - { - "id": "public-dev-edge-health", - "category": "edge", - "status": "pass", - "summary": "Public DEV health endpoint responded successfully.", - "evidence": [ - { - "ok": true, - "url": "http://74.48.78.17:16667/health/live", - "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\":\"c7de474\",\"source\":\"runtime-env\"},\"image\":{\"reference\":\"127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474\",\"tag\":\"c7de474\",\"digest\":\"unknown\"},\"endpoint\":\"http://74.48.78.17:16667\",\"observedAt\":\"2026-05-22T16:59:48.488Z\",\"db\":{\"contractVersion\":\"v1\",\"environment\":\"dev\",\"connected\":false,\"liveConnected\":false,\"liveD" - } - ] - }, - { - "id": "catalog-image-manifest-read", - "category": "artifact-catalog", - "status": "degraded", - "summary": "One or more required catalog images could not be verified through manifest HEAD probes; this is runner-process catalog evidence, not Docker daemon push access.", - "evidence": [ - { - "serviceId": "hwlab-cloud-api", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-cloud-api/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-cloud-web", - "image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-cloud-web/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-agent-mgr", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-agent-mgr/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-agent-worker", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-agent-worker/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-gateway", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-gateway/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-gateway-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-gateway-simu/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-box-simu", - "image": "127.0.0.1:5000/hwlab/hwlab-box-simu:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-box-simu/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-patch-panel", - "image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-patch-panel/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-router", - "image": "127.0.0.1:5000/hwlab/hwlab-router:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-router/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-tunnel-client", - "image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-tunnel-client/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-edge-proxy", - "image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-edge-proxy/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-cli", - "image": "127.0.0.1:5000/hwlab/hwlab-cli:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-cli/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - }, - { - "serviceId": "hwlab-agent-skills", - "image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:c7de474", - "ok": false, - "url": "http://127.0.0.1:5000/v2/hwlab/hwlab-agent-skills/manifests/c7de474", - "method": "HEAD", - "error": "fetch failed ECONNREFUSED 127.0.0.1 5000" - } - ] - } - ], - "blockers": [ - { - "status": "open", - "type": "contract_blocker", - "scope": "artifact-source-commit", - "summary": "source commit origin/main f64182b 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`." - }, - { - "status": "open", - "type": "runtime_blocker", - "scope": "artifact-catalog", - "summary": "deploy/artifact-catalog.dev.json does not prove published artifacts for origin/main f64182b; ciPublished=false, registryVerified=false, not_published=13.", - "nextTask": "Run the DEV artifact publish workflow, then record only real sha256 digests with `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --publish-report reports/dev-gate/dev-artifacts.json`; if publish is still blocked, keep not_published via `node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked`." - }, - { - "status": "open", - "type": "runtime_blocker", - "scope": "cloud-api-db-health-gate", - "summary": "cloud-api DB runtime env is not ready; missing HWLAB_CLOUD_DB_URL, HWLAB_CLOUD_DB_SSL_MODE.", - "nextTask": "Configure DEV hwlab-cloud-api with Secret hwlab-cloud-api-dev-db/database-url and HWLAB_CLOUD_DB_SSL_MODE=require, then rerun health/preflight without printing the secret value." - }, - { - "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 f64182b; 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." - }, - { - "status": "open", - "type": "network_blocker", - "scope": "cloud-api-db", - "summary": "DB endpoint DNS resolution failed from the cloud-api runtime", - "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." -} diff --git a/reports/dev-gate/dev-runtime-postflight-report.json b/reports/dev-gate/dev-runtime-postflight-report.json deleted file mode 100644 index 105c85dc..00000000 --- a/reports/dev-gate/dev-runtime-postflight-report.json +++ /dev/null @@ -1,467 +0,0 @@ -{ - "issue": "pikasTech/HWLAB#311", - "mode": "dev-live", - "generatedAt": "2026-05-24T00:18:29.459Z", - "target": { - "environment": "dev", - "namespace": "hwlab-dev", - "prodAllowed": false, - "apiHealthUrl": "http://74.48.78.17:16667/health/live", - "apiV1Url": "http://74.48.78.17:16667/v1", - "m3Target": "api" - }, - "actions": { - "healthLiveReadAttempted": true, - "v1ReadAttempted": true, - "m3MutationAttempted": true, - "m3MutationAllowed": true - }, - "apiHealth": { - "check": "GET /health/live", - "httpStatus": 200, - "reachable": true, - "serviceId": "hwlab-cloud-api", - "environment": "dev", - "status": "ok", - "ready": true, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "blockedLayer": null, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - } - }, - "runtimeDurableReadiness": { - "status": "ready", - "ready": true, - "classification": "runtime_durable_ready", - "reasonCode": "runtime_durable_ready", - "reason": "runtime durable ready; adapter=postgres; queryResult=durable_readiness_ready", - "impact": "M3 durable postflight may run because runtime persistence readiness is green.", - "safeNextAction": "Continue with repo-owned DEV CD postflight; do not read or print Secret values.", - "retryable": true, - "readonly": false, - "evidence": { - "adapter": "postgres", - "durable": true, - "runtimeReady": true, - "durabilityReady": true, - "liveRuntimeEvidence": true, - "blocker": null, - "blockedLayer": null, - "queryResult": "durable_readiness_ready", - "readyQueryResult": true, - "rawStatus": "ok", - "postgresRequired": true, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - }, - "secretMaterialRead": false - } - }, - "db": { - "ready": true, - "connected": true, - "liveDbEvidence": true, - "endpointSource": "secret-url-host", - "connectionResult": "connected", - "endpointRedacted": true, - "valueRedacted": true, - "runtimeReadiness": { - "ready": true, - "status": "ready", - "blocker": null, - "queryResult": "durable_readiness_ready", - "requiredEvidence": "runtime_adapter_schema_migration_read_query" - } - }, - "readiness": { - "ready": true, - "status": "ok", - "durabilityReady": true, - "durabilityBlockedLayer": null - }, - "blockerCodes": [], - "m3IoControl": null, - "error": null - }, - "apiV1": { - "check": "GET /v1", - "httpStatus": 200, - "reachable": true, - "serviceId": "hwlab-cloud-api", - "environment": null, - "status": "ok", - "ready": true, - "runtime": { - "adapter": "postgres", - "durable": true, - "durableRequested": true, - "ready": true, - "status": "ready", - "blocker": null, - "liveRuntimeEvidence": true, - "requiredEvidence": "runtime_adapter_schema_migration_read_query", - "blockedLayer": null, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - } - }, - "runtimeDurableReadiness": { - "status": "ready", - "ready": true, - "classification": "runtime_durable_ready", - "reasonCode": "runtime_durable_ready", - "reason": "runtime durable ready; adapter=postgres; queryResult=durable_readiness_ready", - "impact": "M3 durable postflight may run because runtime persistence readiness is green.", - "safeNextAction": "Continue with repo-owned DEV CD postflight; do not read or print Secret values.", - "retryable": true, - "readonly": false, - "evidence": { - "adapter": "postgres", - "durable": true, - "runtimeReady": true, - "durabilityReady": true, - "liveRuntimeEvidence": true, - "blocker": null, - "blockedLayer": null, - "queryResult": "durable_readiness_ready", - "readyQueryResult": true, - "rawStatus": "ok", - "postgresRequired": true, - "gates": { - "ssl": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "auth": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "schema": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "migration": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - }, - "durability": { - "checked": true, - "ready": true, - "status": "ready", - "blocker": null - } - }, - "secretMaterialRead": false - } - }, - "db": { - "ready": true, - "connected": true, - "liveDbEvidence": true, - "endpointSource": "secret-url-host", - "connectionResult": "connected", - "endpointRedacted": true, - "valueRedacted": true, - "runtimeReadiness": { - "ready": true, - "status": "ready", - "blocker": null, - "queryResult": "durable_readiness_ready", - "requiredEvidence": "runtime_adapter_schema_migration_read_query" - } - }, - "readiness": { - "ready": true, - "status": "ok", - "durabilityReady": true, - "durabilityBlockedLayer": null - }, - "blockerCodes": [], - "m3IoControl": { - "route": "/v1/m3/io", - "status": "available", - "enabled": true, - "contractVersion": "m3-io-control-v1", - "directFrontendGatewayOrBoxAccess": false, - "genericHardwareRpcExposedToFrontend": false - }, - "error": null - }, - "m3": { - "mode": "dev-live", - "status": "blocked", - "classification": "source_contract_failed", - "trustedGreen": false, - "result": "SOURCE/static contract failed.", - "operationCount": 4, - "persisted": true, - "persistenceContract": { - "contractVersion": "m3-do1-true-false-persisted-v1", - "requiredRoute": "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", - "requiredValues": [ - true, - true, - false, - false - ], - "ready": true, - "sequence": [ - { - "id": "write-do1-true", - "action": "do.write", - "expectedValue": true, - "observedValue": true, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "read-di1-true", - "action": "di.read", - "expectedValue": true, - "observedValue": true, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "write-do1-false", - "action": "do.write", - "expectedValue": false, - "observedValue": false, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - }, - { - "id": "read-di1-false", - "action": "di.read", - "expectedValue": false, - "observedValue": false, - "status": "succeeded", - "idsPresent": true, - "valueMatches": true, - "evidenceGreen": true, - "persisted": true - } - ] - }, - "operations": [ - { - "id": "write-do1-true", - "action": "do.write", - "status": "succeeded", - "operationId": "op_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a", - "traceId": "trc_m3_io_e2e_write_do1_true_9d47c939-0464-437a-a2a5-90cff6714434", - "auditId": "aud_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a_succeeded", - "evidenceId": "evd_m3_do_write_63f8e5ee-3d78-4df2-b54f-d46b0308bd2a_succeeded", - "resultValue": true, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "read-di1-true", - "action": "di.read", - "status": "succeeded", - "operationId": "op_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd", - "traceId": "trc_m3_io_e2e_read_di1_true_a81f0788-33ab-42e1-a16e-16e2c171db1b", - "auditId": "aud_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd_succeeded", - "evidenceId": "evd_m3_di_read_73166d40-4572-4f6d-a96e-8e20f07a10dd_succeeded", - "resultValue": true, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "write-do1-false", - "action": "do.write", - "status": "succeeded", - "operationId": "op_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a", - "traceId": "trc_m3_io_e2e_write_do1_false_9925768c-ca34-4b17-a9fa-344e7103ec97", - "auditId": "aud_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a_succeeded", - "evidenceId": "evd_m3_do_write_0c0af96e-09e0-4e08-ba3f-b1fdf1d4668a_succeeded", - "resultValue": false, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - }, - { - "id": "read-di1-false", - "action": "di.read", - "status": "succeeded", - "operationId": "op_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8", - "traceId": "trc_m3_io_e2e_read_di1_false_83b6a1c9-cd8f-402b-bee4-d4c7f3973832", - "auditId": "aud_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8_succeeded", - "evidenceId": "evd_m3_di_read_f656bcc9-3a3f-4aed-b59b-2d5d2edafff8_succeeded", - "resultValue": false, - "evidenceState": { - "status": "green", - "durable": true, - "sourceKind": "DEV-LIVE", - "writeStatus": "persisted" - }, - "blocker": null - } - ] - }, - "blockers": [ - { - "type": "runtime_blocker", - "scope": "m3-durable-postflight", - "status": "open", - "reason": "M3 true/false durable postflight did not produce trusted green persisted evidence.", - "impact": "M3 durable readiness cannot be accepted because trusted evidence is not green.", - "safeNextAction": "Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "retryable": true, - "summary": "M3 true/false durable postflight did not produce trusted green persisted evidence. Impact: M3 durable readiness cannot be accepted because trusted evidence is not green. Safe next action: Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.", - "sourceIssue": "pikasTech/HWLAB#311", - "evidence": { - "classification": "source_contract_failed", - "trustedGreen": false, - "operationCount": 4 - } - } - ], - "safety": { - "devOnly": true, - "environment": "dev", - "prodAllowed": false, - "liveHttpReads": true, - "liveM3Writes": true, - "liveM3WritesRequireDurableRuntimeReady": true, - "readsKubernetesSecrets": false, - "writesKubernetesSecrets": false, - "secretValuesPrinted": false, - "dbUrlValueRedacted": true, - "endpointRedacted": true - }, - "safetyRefusal": false, - "conclusion": { - "status": "blocked", - "summary": "DEV runtime postflight is blocked; M3 is skipped unless durable runtime readiness is proven first.", - "blockerCount": 1 - } -} diff --git a/reports/dev-gate/historical/dev-m3-hardware-loop-legacy-6667.json b/reports/dev-gate/historical/dev-m3-hardware-loop-legacy-6667.json deleted file mode 100644 index 50383d22..00000000 --- a/reports/dev-gate/historical/dev-m3-hardware-loop-legacy-6667.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://hwlab.pikastech.local/schemas/dev-m3-hardware-loop-report.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/historical/dev-m3-hardware-loop-legacy-6667.json", - "reportVersion": "v1", - "issue": "pikasTech/HWLAB#38", - "taskId": "dev-m3-hardware-loop", - "commitId": "historical", - "acceptanceLevel": "dev_m3_hardware_loop", - "devOnly": true, - "prodDisabled": true, - "reportLifecycle": { - "version": "v1", - "state": "historical", - "activeEndpoint": "http://74.48.78.17:16667", - "activeBrowserEndpoint": "http://74.48.78.17:16666", - "deprecatedEndpoint": "http://74.48.78.17:6667", - "summary": "Historical deprecated endpoint snapshot retained only for evidence provenance; not active DEV-LIVE evidence." - }, - "status": "blocked", - "generatedAt": "2026-05-22T00:00:00.000Z", - "runtimeTarget": { - "environment": "dev", - "namespace": "hwlab-dev", - "endpoint": "http://74.48.78.17:6667", - "requiredBoxSimulators": 2, - "requiredGatewaySimulators": 2, - "requiredPatchPanels": 1 - }, - "blockers": [ - { - "type": "observability_blocker", - "scope": "deprecated-public-endpoint", - "status": "open", - "summary": "Legacy public :6667 evidence is historical/deprecated and cannot support current M3 DEV-LIVE acceptance." - } - ] -} diff --git a/scripts/deploy-desired-state-plan.test.mjs b/scripts/deploy-desired-state-plan.test.mjs index 73c9cc21..979e0fee 100644 --- a/scripts/deploy-desired-state-plan.test.mjs +++ b/scripts/deploy-desired-state-plan.test.mjs @@ -44,7 +44,6 @@ async function makeFixture({ } = {}) { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-desired-state-")); await mkdir(path.join(root, "deploy/k8s/base"), { recursive: true }); - await mkdir(path.join(root, "reports/dev-gate"), { recursive: true }); const image = `127.0.0.1:5000/hwlab/${serviceId}:${commitId}`; const catalogImage = `127.0.0.1:5000/hwlab/${serviceId}:${catalogImageTag}`; const workloadImage = `127.0.0.1:5000/hwlab/${serviceId}:${workloadTag}`; diff --git a/scripts/dev-artifact-publish.mjs b/scripts/dev-artifact-publish.mjs index f2b9d5d5..102385cd 100644 --- a/scripts/dev-artifact-publish.mjs +++ b/scripts/dev-artifact-publish.mjs @@ -20,12 +20,13 @@ import { requireDevCdTransactionForSideEffect } from "./src/dev-cd-transaction-g import { runDevBaseImagePreflight } from "./src/dev-base-image-preflight.mjs"; import { probeRegistryCapabilities } from "./src/registry-capabilities.mjs"; import { inspectCloudWebDistFreshness } from "../web/hwlab-cloud-web/scripts/dist-contract.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const defaultRegistryPrefix = process.env.HWLAB_DEV_REGISTRY_PREFIX || process.env.HWLAB_DEV_REGISTRY || "127.0.0.1:5000/hwlab"; const defaultBaseImage = process.env.HWLAB_DEV_BASE_IMAGE || null; -const defaultReportPath = "reports/dev-gate/dev-artifacts.json"; +const defaultReportPath = tempReportPath("dev-artifacts.json"); const catalogPath = "deploy/artifact-catalog.dev.json"; const deployPath = "deploy/deploy.json"; @@ -103,8 +104,8 @@ function printHelp() { " --registry-prefix PREFIX default: 127.0.0.1:5000/hwlab", " --base-image IMAGE override HWLAB_DEV_BASE_IMAGE for this run; must already be local", " --services LIST comma-separated service IDs; default: all frozen DEV services", - " --report PATH default: reports/dev-gate/dev-artifacts.json", - " --no-report print JSON without updating the report file" + ` --report PATH default: ${defaultReportPath}`, + " --no-report print JSON without updating the temporary artifact JSON" ].join("\n")); } @@ -849,7 +850,6 @@ function dockerfile(baseImage, port) { "COPY tools ./tools", "COPY skills ./skills", "COPY deploy ./deploy", - "COPY reports ./reports", "RUN mkdir -p /workspace /codex-home && rm -rf /workspace/hwlab && ln -s /app /workspace/hwlab && chmod -R a+rwX /app /workspace /codex-home && test -x /app/node_modules/.bin/codex && /app/node_modules/.bin/codex --version >/tmp/hwlab-codex-version.txt && (test -x /app/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex || test -x /app/node_modules/@openai/codex-linux-arm64/vendor/aarch64-unknown-linux-musl/codex/codex)", `RUN node -e "const fs=require('node:fs'); fs.writeFileSync('/usr/local/bin/hwlab-dev-artifact-runtime.mjs', Buffer.from('${runtimeScriptBase64()}', 'base64')); fs.chmodSync('/usr/local/bin/hwlab-dev-artifact-runtime.mjs', 0o755);"`, `EXPOSE ${port}`, @@ -1305,7 +1305,7 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif return { $schema: "https://hwlab.pikastech.local/schemas/dev-artifact-publish.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-artifacts.json", + $id: "https://hwlab.pikastech.local/dev-cd/dev-artifacts.json", reportVersion: "v1", issue: "pikasTech/HWLAB#35", taskId: "dev-artifact-publish", @@ -1417,7 +1417,7 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif } async function writeReport(relativePath, report) { - const absolutePath = path.join(repoRoot, relativePath); + const absolutePath = ensureNotRepoReportsPath(repoRoot, relativePath, "--report"); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/dev-cloud-workbench-layout-smoke.mjs b/scripts/dev-cloud-workbench-layout-smoke.mjs index 7219840e..4f201bca 100644 --- a/scripts/dev-cloud-workbench-layout-smoke.mjs +++ b/scripts/dev-cloud-workbench-layout-smoke.mjs @@ -8,6 +8,7 @@ import { printSmokeHelp, runDevCloudWorkbenchLayoutSmoke } from "./src/dev-cloud-workbench-smoke-lib.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); @@ -98,7 +99,7 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { const report = args.help ? { ...printSmokeHelp(), - command: "node scripts/dev-cloud-workbench-layout-smoke.mjs [--static|--build|--live --url http://74.48.78.17:16666/] [--report reports/dev-gate/dev-cloud-workbench-layout.json]", + command: `node scripts/dev-cloud-workbench-layout-smoke.mjs [--static|--build|--live --url http://74.48.78.17:16666/] [--report ${tempReportPath("dev-cloud-workbench-layout.json")}]`, layoutNotes: [ "默认 --static 使用 source/static 本地静态服务。", "--build 会先运行 web/hwlab-cloud-web/scripts/build.mjs,再检查本地 dist 构建产物。", @@ -108,7 +109,7 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { } : await runDevCloudWorkbenchLayoutSmoke(args); if (args.reportPath && report.status !== "usage") { - const reportPath = path.resolve(process.cwd(), args.reportPath); + const reportPath = ensureNotRepoReportsPath(repoRoot, path.resolve(process.cwd(), args.reportPath), "--report"); report.artifacts = { ...(report.artifacts ?? {}), reportPath }; await mkdir(path.dirname(reportPath), { recursive: true }); await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); diff --git a/scripts/dev-cloud-workbench-smoke.mjs b/scripts/dev-cloud-workbench-smoke.mjs index 86359caa..f4b3295f 100644 --- a/scripts/dev-cloud-workbench-smoke.mjs +++ b/scripts/dev-cloud-workbench-smoke.mjs @@ -11,6 +11,7 @@ import { runM3StatusFixtureSmoke, runDevCloudWorkbenchSmoke } from "./src/dev-cloud-workbench-smoke-lib.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); @@ -20,8 +21,8 @@ export function decideSmokeReportWrite(args, report, options = {}) { } const root = options.repoRoot ?? repoRoot; const cwd = options.cwd ?? process.cwd(); - const reportPath = path.resolve(cwd, args.reportPath); - const liveReportPath = path.resolve(root, "reports/dev-gate/dev-cloud-workbench-live.json"); + const reportPath = ensureNotRepoReportsPath(root, path.resolve(cwd, args.reportPath), "--report"); + const liveReportPath = tempReportPath("dev-cloud-workbench-live.json"); const displayPath = displayReportPath(reportPath, root); if (report.status === "skip") { return { diff --git a/scripts/dev-cloud-workbench-smoke.test.mjs b/scripts/dev-cloud-workbench-smoke.test.mjs index a5b5e440..a231e110 100644 --- a/scripts/dev-cloud-workbench-smoke.test.mjs +++ b/scripts/dev-cloud-workbench-smoke.test.mjs @@ -79,11 +79,11 @@ test("source/default workbench report cannot claim DEV-LIVE and documents the co assert.equal(report.safety.devLive, false); assert.equal(report.validationCommands.includes("node scripts/dev-cloud-workbench-smoke.mjs --source"), true); assert.equal( - report.validationCommands.includes("node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json"), + report.validationCommands.includes("node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-live.json"), true ); assert.equal( - report.validationCommands.includes("node scripts/dev-cloud-workbench-smoke.mjs --live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json"), + report.validationCommands.includes("node scripts/dev-cloud-workbench-smoke.mjs --live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-live.json"), false ); assert.equal(report.checks.find((check) => check.id === "code-agent-long-timeout-contract")?.status, "pass"); @@ -131,7 +131,7 @@ test("workbench smoke CLI treats skipped browser evidence as blocked and refuses summary: "Mobile browser smoke skipped because Playwright is unavailable." }; const decision = decideSmokeReportWrite( - { reportPath: "reports/dev-gate/dev-cloud-workbench-mobile.json" }, + { reportPath: "/tmp/hwlab-dev-gate/dev-cloud-workbench-mobile.json" }, report, { repoRoot: "/repo", cwd: "/repo" } ); @@ -148,14 +148,14 @@ test("workbench smoke CLI refuses to overwrite live report with non-live fixture mode: "local-agent-fixture-browser" }; const decision = decideSmokeReportWrite( - { reportPath: "reports/dev-gate/dev-cloud-workbench-live.json" }, + { reportPath: "/tmp/hwlab-dev-gate/dev-cloud-workbench-live.json" }, report, { repoRoot: "/repo", cwd: "/repo" } ); assert.equal(decision.status, "blocked"); assert.equal(decision.write, false); - assert.match(decision.summary, /Refusing to overwrite reports\/dev-gate\/dev-cloud-workbench-live\.json/u); + assert.match(decision.summary, /Refusing to overwrite \/tmp\/hwlab-dev-gate\/dev-cloud-workbench-live\.json/u); assert.equal(smokeCliExitCode(report, decision), 2); }); @@ -165,14 +165,14 @@ test("workbench smoke CLI allows live evidence to write the live report", () => mode: "live" }; const decision = decideSmokeReportWrite( - { reportPath: "reports/dev-gate/dev-cloud-workbench-live.json" }, + { reportPath: "/tmp/hwlab-dev-gate/dev-cloud-workbench-live.json" }, report, { repoRoot: "/repo", cwd: "/repo" } ); assert.equal(decision.status, "pass"); assert.equal(decision.write, true); - assert.equal(decision.reportPath, "/repo/reports/dev-gate/dev-cloud-workbench-live.json"); + assert.equal(decision.reportPath, "/tmp/hwlab-dev-gate/dev-cloud-workbench-live.json"); assert.equal(smokeCliExitCode(report, decision), 0); }); @@ -204,10 +204,10 @@ test("dedicated layout smoke CLI supports static build and DEV live shorthand", assert.equal(local.mode, "layout"); assert.equal(local.urlExplicit, undefined); - const build = parseLayoutSmokeArgs(["--build", "--report", "reports/dev-gate/dev-cloud-workbench-layout.json"]); + const build = parseLayoutSmokeArgs(["--build", "--report", "/tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"]); assert.equal(build.mode, "layout"); assert.equal(build.build, true); - assert.equal(build.reportPath, "reports/dev-gate/dev-cloud-workbench-layout.json"); + assert.equal(build.reportPath, "/tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"); const liveDefault = parseLayoutSmokeArgs(["--live"]); assert.equal(liveDefault.mode, "layout"); @@ -843,15 +843,15 @@ test("repo-owned web checks expose source build and DEV live layout smoke gates" assert.equal(rootPackage.scripts["web:check"], "node web/hwlab-cloud-web/scripts/check.mjs"); assert.equal( rootPackage.scripts["web:layout"], - "node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report reports/dev-gate/dev-cloud-workbench-layout.json" + "node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json" ); assert.equal( rootPackage.scripts["web:layout:build"], - "node scripts/dev-cloud-workbench-layout-smoke.mjs --build --report reports/dev-gate/dev-cloud-workbench-layout-build.json" + "node scripts/dev-cloud-workbench-layout-smoke.mjs --build --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-build.json" ); assert.equal( rootPackage.scripts["web:layout:live"], - "node scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-layout-live.json" + "node scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-live.json" ); assert.match(rootPackage.scripts.check, /node --check scripts\/dev-cloud-workbench-layout-smoke\.mjs/u); assert.match(rootPackage.scripts.check, /node web\/hwlab-cloud-web\/scripts\/check\.mjs/u); diff --git a/scripts/dev-evidence-blocker-aggregator.mjs b/scripts/dev-evidence-blocker-aggregator.mjs index 2408e3d6..e39881dc 100644 --- a/scripts/dev-evidence-blocker-aggregator.mjs +++ b/scripts/dev-evidence-blocker-aggregator.mjs @@ -1,6 +1,5 @@ #!/usr/bin/env node import { writeFile } from "node:fs/promises"; -import path from "node:path"; import { buildReport, @@ -8,6 +7,7 @@ import { renderMarkdown, repoRoot } from "./src/dev-evidence-blocker-aggregator.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; function hasFlag(name) { return process.argv.includes(name); @@ -15,11 +15,17 @@ function hasFlag(name) { try { const report = await buildReport(); - const wroteReports = hasFlag("--write-report"); + const reportFlagIndex = process.argv.indexOf("--report"); + const markdownFlagIndex = process.argv.indexOf("--markdown-report"); + const wroteReports = reportFlagIndex !== -1 || markdownFlagIndex !== -1; + const jsonPath = reportFlagIndex !== -1 + ? ensureNotRepoReportsPath(repoRoot, process.argv[reportFlagIndex + 1], "--report") + : ensureNotRepoReportsPath(repoRoot, tempReportPath("dev-m5-gate-aggregator-v2.json"), "--report"); + const markdownPath = markdownFlagIndex !== -1 + ? ensureNotRepoReportsPath(repoRoot, process.argv[markdownFlagIndex + 1], "--markdown-report") + : ensureNotRepoReportsPath(repoRoot, tempReportPath("dev-m5-gate-aggregator-v2.md"), "--markdown-report"); if (wroteReports) { - const jsonPath = path.join(repoRoot, "reports/dev-gate/dev-m5-gate-aggregator-v2.json"); - const markdownPath = path.join(repoRoot, "reports/dev-gate/dev-m5-gate-aggregator-v2.md"); await writeFile(jsonPath, `${JSON.stringify(report, null, 2)}\n`); await writeFile(markdownPath, renderMarkdown(report)); } @@ -32,8 +38,8 @@ try { process.stdout.write(`${JSON.stringify({ ...formatCheckSummary(report), wrote: [ - "reports/dev-gate/dev-m5-gate-aggregator-v2.json", - "reports/dev-gate/dev-m5-gate-aggregator-v2.md" + jsonPath, + markdownPath ] })}\n`); } else { diff --git a/scripts/dev-m3-hardware-loop-smoke.mjs b/scripts/dev-m3-hardware-loop-smoke.mjs index 74059ec5..0091ef51 100644 --- a/scripts/dev-m3-hardware-loop-smoke.mjs +++ b/scripts/dev-m3-hardware-loop-smoke.mjs @@ -11,9 +11,10 @@ import { fileURLToPath, pathToFileURL } from "node:url"; import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV } from "../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const defaultReportPath = "reports/dev-gate/dev-m3-hardware-loop.json"; +const defaultReportPath = tempReportPath("dev-m3-hardware-loop.json"); const namespace = "hwlab-dev"; const issue = "pikasTech/HWLAB#38"; const deployWorkloadsPath = "deploy/k8s/base/workloads.yaml"; @@ -203,9 +204,7 @@ function relativePath(absolutePath) { } function resolveReportPath(value) { - const candidate = path.resolve(repoRoot, value ?? defaultReportPath); - assert.ok(candidate.startsWith(`${repoRoot}${path.sep}`), "report path must stay inside repository"); - return candidate; + return ensureNotRepoReportsPath(repoRoot, value ?? defaultReportPath, "report path"); } function currentCommit() { @@ -1404,7 +1403,7 @@ async function runLiveM3Targets(targets) { function baseReport({ commitId, observedAt }) { return { $schema: "https://hwlab.pikastech.local/schemas/dev-m3-hardware-loop-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-m3-hardware-loop.json", + $id: "https://hwlab.pikastech.local/dev-gate/dev-m3-hardware-loop.json", reportVersion: "v1", issue, taskId: "dev-m3-hardware-loop", @@ -1618,8 +1617,9 @@ async function ensureContractFiles() { } async function writeReport(report, reportPath) { - await mkdir(path.dirname(reportPath), { recursive: true }); - await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "report path"); + await mkdir(path.dirname(absoluteReportPath), { recursive: true }); + await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); } async function main() { diff --git a/scripts/dev-m4-agent-loop-smoke.mjs b/scripts/dev-m4-agent-loop-smoke.mjs index a361693f..1b2f79dc 100644 --- a/scripts/dev-m4-agent-loop-smoke.mjs +++ b/scripts/dev-m4-agent-loop-smoke.mjs @@ -20,6 +20,7 @@ import { } from "../internal/agent/runtime.mjs"; import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV } from "../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; import { collectD601K3sNativeEvidence, collectPublicEntrypoints, @@ -35,7 +36,7 @@ import { const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const fixedNow = () => "2026-05-22T00:00:00.000Z"; const requiredDocs = ["docs/dev-acceptance-matrix.md", "docs/dev-m4-agent-loop.md"]; -const reportPath = "reports/dev-gate/dev-m4-agent-loop.json"; +const reportPath = tempReportPath("dev-m4-agent-loop.json"); const preflightCommand = "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"; const dryRunCommand = "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run"; @@ -64,7 +65,7 @@ async function readText(relativePath) { } async function writeJSON(relativePath, value) { - const absolutePath = path.join(repoRoot, relativePath); + const absolutePath = ensureNotRepoReportsPath(repoRoot, relativePath, "--report"); await mkdir(path.dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, `${JSON.stringify(value, null, 2)}\n`, "utf8"); } @@ -275,7 +276,7 @@ function buildReport( return { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-m4-agent-loop.json", + $id: "https://hwlab.pikastech.local/dev-m4-agent-loop.json", reportVersion: "v1", issue: "pikasTech/HWLAB#37", taskId: "dev-m4-agent-loop", diff --git a/scripts/export-web-gate-summary.mjs b/scripts/export-web-gate-summary.mjs index b3daf8e5..cead000a 100644 --- a/scripts/export-web-gate-summary.mjs +++ b/scripts/export-web-gate-summary.mjs @@ -7,8 +7,8 @@ import { loadMvpGateSummary } from "../internal/mvp-gate/summary.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const outputPath = path.join(repoRoot, "web/hwlab-cloud-web/gate-summary.mjs"); const summary = loadMvpGateSummary(repoRoot); -const body = `// Generated from reports/dev-gate/dev-mvp-gate-report.json and fixtures/mvp/m5-e2e/dry-run-plan.json. -// Run node scripts/export-web-gate-summary.mjs after changing those fixtures. +const body = `// Generated from source:mvp-gate-summary and fixtures/mvp/m5-e2e/dry-run-plan.json. +// Run node scripts/export-web-gate-summary.mjs after changing that source summary or fixture. export const gateSummary = ${JSON.stringify(summary, null, 2)}; `; diff --git a/scripts/m2-dev-deploy-smoke.mjs b/scripts/m2-dev-deploy-smoke.mjs index 6313ca42..6d056300 100644 --- a/scripts/m2-dev-deploy-smoke.mjs +++ b/scripts/m2-dev-deploy-smoke.mjs @@ -13,11 +13,12 @@ import { ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const fixturePath = path.join(repoRoot, "fixtures/dev-deploy-smoke/dev-deploy-smoke.json"); -const defaultActiveReportPath = path.join(repoRoot, "reports/dev-gate/dev-m2-deploy-smoke-active.json"); +const defaultActiveReportPath = tempReportPath("dev-m2-deploy-smoke-active.json"); const legacyPublicEndpoint = "http://74.48.78.17:6667"; const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; const digestPattern = /^(sha256:[a-f0-9]{64}|not_applicable)$/; @@ -34,6 +35,7 @@ function parseArgs(argv) { const flags = new Set(); const options = { reportPath: defaultActiveReportPath, + writeReport: false, timeoutMs: 5000 }; const rest = []; @@ -43,7 +45,8 @@ function parseArgs(argv) { flags.add(arg); index += 1; assert.ok(argv[index], "--report requires a path"); - options.reportPath = path.resolve(process.cwd(), argv[index]); + options.reportPath = argv[index]; + options.writeReport = true; } else if (arg === "--timeout-ms") { flags.add(arg); index += 1; @@ -292,7 +295,7 @@ function buildActiveReport({ fixture, probes, generatedAt, commitId }) { return { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-m2-deploy-smoke-active.json", + $id: "https://hwlab.pikastech.local/dev-gate/dev-m2-deploy-smoke-active.json", reportVersion: "v1", issue: "pikasTech/HWLAB#23", taskId: "m2-dev-deploy-smoke", @@ -319,7 +322,7 @@ function buildActiveReport({ fixture, probes, generatedAt, commitId }) { validationCommands: [ "node --check scripts/m2-dev-deploy-smoke.mjs", "node scripts/m2-dev-deploy-smoke.mjs --dry-run", - "node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --write-report", + `node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --report ${defaultActiveReportPath}`, "node --check scripts/validate-dev-gate-report.mjs", "node scripts/validate-dev-gate-report.mjs" ], @@ -395,8 +398,9 @@ function buildActiveReport({ fixture, probes, generatedAt, commitId }) { } async function writeActiveReport(report, reportPath) { - await mkdir(path.dirname(reportPath), { recursive: true }); - await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "--report"); + await mkdir(path.dirname(absoluteReportPath), { recursive: true }); + await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } async function runLiveSmoke(fixture, options) { @@ -449,7 +453,7 @@ async function main() { process.stdout.write(`[m2-smoke] mode=${isLive ? "live" : "dry-run"} endpoint=${fixture.endpoint}\n`); if (isLive) { const report = await runLiveSmoke(fixture, options); - if (flags.has("--write-report")) { + if (options.writeReport) { await writeActiveReport(report, options.reportPath); process.stdout.write(`[m2-smoke] wrote ${path.relative(repoRoot, options.reportPath)}\n`); } diff --git a/scripts/refresh-artifact-catalog.mjs b/scripts/refresh-artifact-catalog.mjs index ae6a25be..cc39bebd 100644 --- a/scripts/refresh-artifact-catalog.mjs +++ b/scripts/refresh-artifact-catalog.mjs @@ -11,13 +11,14 @@ import { resolveDevArtifactServices, serviceInventoryFromServices } from "./src/dev-artifact-services.mjs"; +import { tempReportPath } from "./src/report-paths.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); 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 defaultPublishReportPath = tempReportPath("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}$/; @@ -49,7 +50,7 @@ function parseArgs(argv) { if (!args.help) { assert.notEqual(args.blocked && Boolean(args.publishReportPath), true, "--blocked and --publish-report are mutually exclusive"); - assert.ok(args.blocked || args.publishReportPath, "choose --blocked or --publish-report reports/dev-gate/dev-artifacts.json"); + assert.ok(args.blocked || args.publishReportPath, `choose --blocked or --publish-report ${defaultPublishReportPath}`); } return args; diff --git a/scripts/repo-reports-guard.mjs b/scripts/repo-reports-guard.mjs new file mode 100644 index 00000000..f8be9b26 --- /dev/null +++ b/scripts/repo-reports-guard.mjs @@ -0,0 +1,59 @@ +#!/usr/bin/env node +import { execFileSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const reportsDirName = "reports"; +const reportsPath = path.join(repoRoot, reportsDirName); + +function gitLines(args) { + try { + const output = execFileSync("git", args, { + cwd: repoRoot, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"] + }).trim(); + return output ? output.split(/\r?\n/u).filter(Boolean) : []; + } catch (error) { + const stderr = error?.stderr?.toString?.().trim(); + throw new Error(stderr || error.message); + } +} + +function statusReportPaths() { + return gitLines(["status", "--porcelain=v1", "--untracked-files=all", "--", reportsDirName]) + .map((line) => line.slice(3).trim()) + .filter(Boolean); +} + +const tracked = gitLines(["ls-files", reportsDirName]); +const staged = gitLines(["diff", "--cached", "--name-only", "--", reportsDirName]); +const workingTree = statusReportPaths(); +const exists = existsSync(reportsPath); +const failures = []; + +if (exists) failures.push("forbidden repository report directory exists in the worktree"); +if (tracked.length > 0) failures.push(`tracked reports files: ${tracked.slice(0, 8).join(", ")}`); +if (staged.length > 0) failures.push(`staged reports files: ${staged.slice(0, 8).join(", ")}`); +if (workingTree.length > 0) failures.push(`working-tree reports files: ${workingTree.slice(0, 8).join(", ")}`); + +if (failures.length > 0) { + process.stderr.write(`${JSON.stringify({ + ok: false, + status: "blocked", + taskId: "repo-reports-guard", + policy: "repository report directories are forbidden; use GitHub issue/PR comments, /tmp, .state, or CI artifacts", + failures + }, null, 2)}\n`); + process.exitCode = 1; +} else { + process.stdout.write(`${JSON.stringify({ + ok: true, + status: "pass", + taskId: "repo-reports-guard", + checked: ["directory", "tracked", "staged", "working-tree"], + policy: "repository report directory is absent" + })}\n`); +} diff --git a/scripts/src/artifact-runtime-readiness-guard.mjs b/scripts/src/artifact-runtime-readiness-guard.mjs index 84e32942..698e579b 100644 --- a/scripts/src/artifact-runtime-readiness-guard.mjs +++ b/scripts/src/artifact-runtime-readiness-guard.mjs @@ -7,14 +7,15 @@ import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT } from "../../internal/protocol/index.mjs"; import { inspectCloudWebDistFreshness } from "../../web/hwlab-cloud-web/scripts/dist-contract.mjs"; import { buildDesiredStatePlan } from "./deploy-desired-state-plan.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultArtifactReport = "reports/dev-gate/dev-artifacts.json"; +const defaultArtifactReport = tempReportPath("dev-artifacts.json"); const defaultArtifactCatalog = "deploy/artifact-catalog.dev.json"; -const defaultRuntimeReport = "reports/dev-gate/dev-cloud-workbench-live.json"; -const defaultEdgeReport = "reports/dev-gate/dev-edge-health.json"; -const defaultOutputReport = "reports/dev-gate/dev-artifact-runtime-readiness.json"; +const defaultRuntimeReport = tempReportPath("dev-cloud-workbench-live.json"); +const defaultEdgeReport = tempReportPath("dev-edge-health.json"); +const defaultOutputReport = tempReportPath("dev-artifact-runtime-readiness.json"); const defaultTargetRef = "origin/main"; const guardCommand = `node scripts/artifact-runtime-readiness-guard.mjs --target-ref ${defaultTargetRef} --check --no-report`; const expectedBlockedGuardCommand = `${guardCommand} --expect-blocked`; @@ -104,7 +105,7 @@ export function evaluateArtifactRuntimeReadiness({ pass: commitsMatch(artifact.sourceCommitId, catalog.commitId), type: "observability_blocker", summary: `Artifact report source=${artifact.shortCommitId}; catalog=${catalog.shortCommitId}.`, - nextTask: "Keep reports/dev-gate/dev-artifacts.json and deploy/artifact-catalog.dev.json aligned to the same artifact source." + nextTask: `Keep ${defaultArtifactReport} and deploy/artifact-catalog.dev.json aligned to the same artifact source.` }); addCheck(checks, { id: "desired-state-internal-valid", @@ -237,7 +238,7 @@ export function buildArtifactRuntimeReadinessReport({ }); return { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-artifact-runtime-readiness.json", + $id: "https://hwlab.pikastech.local/dev-gate/dev-artifact-runtime-readiness.json", reportVersion: "v1", status, issue: "pikasTech/HWLAB#164", @@ -528,7 +529,7 @@ export async function runArtifactRuntimeReadinessGuardCli(argv = process.argv.sl }); if (args.writeReport) { - const outputPath = path.join(repoRoot, args.report); + const outputPath = ensureNotRepoReportsPath(repoRoot, args.report, "--report"); await mkdir(path.dirname(outputPath), { recursive: true }); await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); } @@ -1180,7 +1181,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) { return [ "node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty", "node scripts/deploy-desired-state-plan.mjs --promotion-commit --check", - "node scripts/dev-deploy-apply.mjs --dry-run --write-report", + `node scripts/dev-deploy-apply.mjs --dry-run --report ${tempReportPath("dev-deploy-report.json")}`, guardCommand ]; } @@ -1193,7 +1194,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) { ]; } return [ - "node scripts/dev-deploy-apply.mjs --dry-run --write-report", + `node scripts/dev-deploy-apply.mjs --dry-run --report ${tempReportPath("dev-deploy-report.json")}`, "node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report" ]; } @@ -1264,8 +1265,6 @@ function parseArgs(argv) { } else if (arg === "--timeout-ms") { args.timeoutMs = Number.parseInt(requireValue(argv, index, arg), 10); index += 1; - } else if (arg === "--write-report") { - args.writeReport = true; } else if (arg === "--no-report") { args.writeReport = false; } else if (arg === "--no-live") { @@ -1301,7 +1300,7 @@ function requireValue(argv, index, arg) { function usage() { return [ - `usage: ${guardCommand} [--write-report] [--live] [--expect-blocked]`, + `usage: ${guardCommand} [--report ${defaultOutputReportPath}] [--live] [--expect-blocked]`, "", "Read-only latest-main DEV artifact/runtime readiness guard.", "", diff --git a/scripts/src/d601-k3s-readonly-observability.mjs b/scripts/src/d601-k3s-readonly-observability.mjs index 02c0b3d8..109934ce 100644 --- a/scripts/src/d601-k3s-readonly-observability.mjs +++ b/scripts/src/d601-k3s-readonly-observability.mjs @@ -8,11 +8,12 @@ import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT } from "../../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const namespace = "hwlab-dev"; -const defaultReportPath = "reports/d601-k3s-readonly-observability.json"; +const defaultReportPath = tempReportPath("d601-k3s-readonly-observability.json"); const issue = "pikasTech/HWLAB#46"; const supports = ["#7", "#33", "#36", "#38", "#39", "#42"].map((id) => `pikasTech/HWLAB${id}`); const forbiddenActions = [ @@ -604,7 +605,7 @@ function runnerImageFollowUp(blockers) { } async function writeReport(report, reportPath) { - const absoluteReportPath = path.join(repoRoot, reportPath); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "--report"); await mkdir(path.dirname(absoluteReportPath), { recursive: true }); await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } @@ -628,7 +629,7 @@ async function buildReport(args) { }); return { $schema: "https://hwlab.pikastech.local/schemas/d601-k3s-readonly-observability-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/d601-k3s-readonly-observability.json", + $id: "https://hwlab.pikastech.local/dev-gate/d601-k3s-readonly-observability.json", reportVersion: "v1", reportKind: "d601-k3s-readonly-observability", issue, diff --git a/scripts/src/deploy-desired-state-plan.mjs b/scripts/src/deploy-desired-state-plan.mjs index bc35ec43..6f6675c7 100644 --- a/scripts/src/deploy-desired-state-plan.mjs +++ b/scripts/src/deploy-desired-state-plan.mjs @@ -9,6 +9,7 @@ import { inspectCodeAgentProviderManifestRefs } from "../../internal/cloud/code-agent-contract.mjs"; import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs"; +import { tempReportPath } from "./report-paths.mjs"; const execFileAsync = promisify(execFile); const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); @@ -16,7 +17,7 @@ const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url) const deployPath = "deploy/deploy.json"; const catalogPath = "deploy/artifact-catalog.dev.json"; const workloadsPath = "deploy/k8s/base/workloads.yaml"; -const artifactReportPath = "reports/dev-gate/dev-artifacts.json"; +const artifactReportPath = tempReportPath("dev-artifacts.json"); const mutableTags = new Set(["latest", "dev", "main", "master", "prod", "production"]); const mirrorEnvNames = [ "HWLAB_COMMIT_ID", @@ -661,7 +662,7 @@ function reportHints(report, desiredCommitId) { path: artifactReportPath, present: true, authoritative: false, - note: "report snapshots are contextual evidence only; deploy desired-state remains authoritative in deploy/, not reports/", + note: "temporary publish JSON is contextual evidence only; deploy desired-state remains authoritative in deploy/", commitId: reportCommitId, matchesDesiredState: reportCommitId ? commitEquivalent(reportCommitId, desiredCommitId) : null, artifactPublish: { diff --git a/scripts/src/dev-cd-apply.mjs b/scripts/src/dev-cd-apply.mjs index b65b038a..8f217c08 100644 --- a/scripts/src/dev-cd-apply.mjs +++ b/scripts/src/dev-cd-apply.mjs @@ -12,24 +12,25 @@ import { redactSensitiveText, resolveDevKubeconfigSelection } from "./dev-deploy-apply.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const defaultNamespace = "hwlab-dev"; const defaultLockName = "hwlab-dev-cd-lock"; const defaultTtlSeconds = 3600; -const defaultReportPath = "reports/dev-gate/dev-cd-apply.json"; +const defaultReportPath = tempReportPath("dev-cd-apply.json"); const d601NativeKubeconfigPath = "/etc/rancher/k3s/k3s.yaml"; const artifactCatalogPath = "deploy/artifact-catalog.dev.json"; -const artifactReportPath = "reports/dev-gate/dev-artifacts.json"; +const artifactReportPath = tempReportPath("dev-artifacts.json"); const artifactDesiredStatePaths = [ "deploy/artifact-catalog.dev.json", "deploy/deploy.json", - "deploy/k8s/base/workloads.yaml", - "reports/dev-gate/dev-artifacts.json" + "deploy/k8s/base/workloads.yaml" ]; -const runtimeProvisioningReportPath = "reports/dev-gate/dev-runtime-provisioning-report.json"; -const runtimeMigrationReportPath = "reports/dev-gate/dev-runtime-migration-report.json"; -const runtimePostflightReportPath = "reports/dev-gate/dev-runtime-postflight-report.json"; +const runtimeProvisioningReportPath = tempReportPath("dev-runtime-provisioning-report.json"); +const runtimeMigrationReportPath = tempReportPath("dev-runtime-migration-report.json"); +const runtimePostflightReportPath = tempReportPath("dev-runtime-postflight-report.json"); +const deployApplyReportPath = tempReportPath("dev-deploy-report.json"); const runtimeJobReportPrefix = "/tmp/hwlab-dev-runtime-report"; const browserLiveUrl = "http://74.48.78.17:16666/health/live"; const apiLiveUrl = `${DEV_ENDPOINT}/health/live`; @@ -103,9 +104,6 @@ export function parseArgs(argv) { } else if (arg === "--confirmed-non-production") { args.confirmedNonProduction = true; args.flags.add(arg); - } else if (arg === "--write-report") { - args.writeReport = true; - args.flags.add(arg); } else if (arg === "--break-stale-lock") { args.breakStaleLock = true; args.flags.add(arg); @@ -124,6 +122,7 @@ export function parseArgs(argv) { args.flags.add(arg); } else if (arg === "--report") { args.reportPath = readOption(argv, ++index, arg); + args.writeReport = true; args.flags.add(arg); } else if (arg === "--target-ref") { args.targetRef = readOption(argv, ++index, arg); @@ -182,7 +181,7 @@ export function usage() { examples: [ "node scripts/dev-cd-apply.mjs --status", "node scripts/dev-cd-apply.mjs --dry-run", - "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report" + `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath}` ], options: { "--status": "Read-only status: target ref, deploy.json, current Lease, and public live health.", @@ -195,8 +194,7 @@ export function usage() { "--ttl-seconds SECONDS": "lock TTL; default: 3600", "--owner-task-id ID": "lock holder; default: Code Queue/env/user derived", "--break-stale-lock": "May take over an expired lock only with --confirm-dev.", - "--report PATH": `default: ${defaultReportPath}`, - "--write-report": "Write the full report to --report.", + "--report PATH": `write the full transaction JSON outside the repo; packaged scripts use ${defaultReportPath}`, "--full-output": "Print the full report to stdout instead of the concise summary.", "--skip-runtime-postflight": "Skip the M3/durable runtime postflight while still applying DEV and verifying 16666/16667 health.", "--skip-live-verify": "test-only escape hatch; do not use for DEV acceptance" @@ -326,7 +324,8 @@ async function readDeployJson(repoRoot) { async function readJsonFileSummary(repoRoot, relativePath, summarize) { try { - const raw = await readFile(path.join(repoRoot, relativePath), "utf8"); + const absolutePath = path.isAbsolute(relativePath) ? relativePath : path.join(repoRoot, relativePath); + const raw = await readFile(absolutePath, "utf8"); const json = JSON.parse(raw); return { path: relativePath, @@ -1854,7 +1853,7 @@ function stepBlockerSafeNextAction(stepResult) { if (stepResult.id === "runtime-db-provisioning") return "Repair repo-owned runtime provisioning inputs/SecretRefs without printing Secret values, then rerun DEV CD."; if (stepResult.id === "runtime-db-migration") return "Repair repo-owned runtime migration/schema state, then rerun DEV CD."; if (stepResult.id === "runtime-durable-postflight") return "Inspect the runtime postflight report, repair durable readiness or M3 evidence persistence, then rerun DEV CD after host-controlled rollout if needed."; - if (stepResult.id === "dev-deploy-apply") return "Inspect reports/dev-gate/dev-deploy-report.json and rerun the repo-owned DEV CD path after the apply blocker is fixed."; + if (stepResult.id === "dev-deploy-apply") return `Inspect ${deployApplyReportPath} and rerun the repo-owned DEV CD path after the apply blocker is fixed.`; return "Fix the reported step blocker and rerun the repo-owned DEV CD transaction."; } @@ -2165,8 +2164,8 @@ function buildStatusNextActions({ target, deployBefore, lock, liveVerify }) { } if (actions.length === 0) { actions.push(target.publishRequired === false - ? "Run --apply --confirm-dev --confirmed-non-production --write-report when ready to apply the published deploy.json promotion without republishing HEAD." - : "Run --apply --confirm-dev --confirmed-non-production --write-report when ready to publish/apply DEV."); + ? `Run --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} when ready to apply the published deploy.json promotion without republishing HEAD.` + : `Run --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} when ready to publish/apply DEV.`); } return actions; } @@ -2229,7 +2228,7 @@ async function runDevCdStatus(args, ctx, stdout) { live: compactLiveReport(liveVerify), liveDelta, nextActions: buildStatusNextActions({ target, deployBefore, lock, liveVerify }), - fullOutputHint: "Use --apply --confirm-dev --confirmed-non-production --write-report for the full transaction report; use --full-output only when stdout needs the full JSON." + fullOutputHint: `Use --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} for the full transaction JSON; use --full-output only when stdout needs the full JSON.` }; stdout.write(`${JSON.stringify(report, null, 2)}\n`); return 0; @@ -2284,7 +2283,7 @@ function summarizeDevCdApplyReport(report) { } : null, blockers: report.blockers ?? [], reportPaths: apply.reportPaths ?? {}, - fullOutputHint: "Use --full-output to print the full report to stdout. Use --write-report to persist the full report." + fullOutputHint: `Use --full-output to print the full report to stdout. Use --report ${defaultReportPath} to persist the full report outside the repo.` }; } @@ -2307,7 +2306,7 @@ function buildReport({ }) { return { $schema: "https://hwlab.pikastech.local/schemas/dev-cd-apply.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-cd-apply.json", + $id: "https://hwlab.pikastech.local/dev-cd/dev-cd-apply.json", reportVersion: "v1", issue: "pikasTech/HWLAB#274", taskId: "dev-cd-apply", @@ -2357,7 +2356,7 @@ function buildReport({ }, dryRun: { status: "not_run", - commands: ["node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report"], + commands: [`node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report ${deployApplyReportPath}`], evidence: ["The transaction path owns live side effects; dry-run remains a preflight support mode."], summary: "DEV CD mutation requires --apply and the transaction Lease lock." }, @@ -2422,7 +2421,7 @@ function buildReport({ reportPaths: { transaction: args.writeReport ? args.reportPath : null, artifacts: artifactReportPath, - deployApply: "reports/dev-gate/dev-deploy-report.json", + deployApply: deployApplyReportPath, runtimeProvisioning: runtimeProvisioningReportPath, runtimeMigration: runtimeMigrationReportPath, runtimePostflight: runtimePostflightReportPath @@ -2544,7 +2543,7 @@ export async function runDevCdApply(argv, io = {}) { type: "contract_blocker", scope: "artifact-boundary", status: "open", - summary: "deploy/deploy.json, deploy/artifact-catalog.dev.json, reports/dev-gate/dev-artifacts.json, and the artifact merge parent must resolve to the same promotion commit before apply." + summary: "deploy/deploy.json, deploy/artifact-catalog.dev.json, deploy/k8s/base/workloads.yaml, and the artifact merge parent must resolve to the same promotion commit before apply." }); throw new DevCdApplyError("artifact desired-state provenance does not match the deploy.json promotion", { code: "artifact-boundary-provenance-mismatch", @@ -2635,7 +2634,7 @@ export async function runDevCdApply(argv, io = {}) { id: "artifact-publish", phase: "publishing", command: process.execPath, - args: ["scripts/dev-artifact-publish.mjs", "--publish"], + args: ["scripts/dev-artifact-publish.mjs", "--publish", "--report", artifactReportPath], timeoutMs: 60 * 60 * 1000, reportPath: artifactReportPath }, @@ -2686,7 +2685,8 @@ export async function runDevCdApply(argv, io = {}) { "--apply", "--confirm-dev", "--confirmed-non-production", - "--write-report", + "--report", + runtimeProvisioningReportPath, "--fail-on-blocked" ], timeoutMs: 5 * 60 * 1000, @@ -2702,7 +2702,8 @@ export async function runDevCdApply(argv, io = {}) { "--apply", "--confirm-dev", "--confirmed-non-production", - "--write-report", + "--report", + runtimeMigrationReportPath, "--fail-on-blocked" ], timeoutMs: 5 * 60 * 1000, @@ -2717,11 +2718,12 @@ export async function runDevCdApply(argv, io = {}) { "--apply", "--confirm-dev", "--confirmed-non-production", - "--write-report", + "--report", + deployApplyReportPath, ...(args.kubeconfig ? ["--kubeconfig", args.kubeconfig] : []) ], timeoutMs: 20 * 60 * 1000, - reportPath: "reports/dev-gate/dev-deploy-report.json" + reportPath: deployApplyReportPath }, ...( args.skipRuntimePostflight @@ -2737,7 +2739,8 @@ export async function runDevCdApply(argv, io = {}) { "--confirmed-non-production", "--target", "api", - "--write-report", + "--report", + runtimePostflightReportPath, "--fail-on-blocked" ], timeoutMs: 2 * 60 * 1000, @@ -2871,7 +2874,7 @@ export async function runDevCdApply(argv, io = {}) { }); if (args.writeReport) { - const absoluteReportPath = path.resolve(ctx.repoRoot, args.reportPath); + const absoluteReportPath = ensureNotRepoReportsPath(ctx.repoRoot, args.reportPath, "--report"); await mkdir(path.dirname(absoluteReportPath), { recursive: true }); await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/src/dev-cd-apply.test.mjs b/scripts/src/dev-cd-apply.test.mjs index def3519a..5e3f3e6d 100644 --- a/scripts/src/dev-cd-apply.test.mjs +++ b/scripts/src/dev-cd-apply.test.mjs @@ -17,6 +17,16 @@ import { const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); +const tempReportRoot = "/tmp/hwlab-dev-gate"; +const artifactReportFixturePath = path.join(tempReportRoot, "dev-artifacts.json"); + +function tempReport(name) { + return path.join(tempReportRoot, name); +} + +function stateReport(repoRoot, name) { + return path.join(repoRoot, ".state/test-reports", name); +} function iso(offsetMs = 0) { return new Date(Date.parse("2026-05-23T08:00:00.000Z") + offsetMs).toISOString(); @@ -51,7 +61,7 @@ async function makeRepo(options = {}) { const commitId = options.commitId ?? "abc1234"; const repoRoot = await fsTempDir(); await mkdir(path.join(repoRoot, "deploy/k8s/base"), { recursive: true }); - await mkdir(path.join(repoRoot, "reports/dev-gate"), { recursive: true }); + await mkdir(tempReportRoot, { recursive: true }); await writeFile( path.join(repoRoot, "deploy/deploy.json"), `${JSON.stringify({ @@ -92,7 +102,7 @@ async function makeRepo(options = {}) { publish: { ciPublished: true, registryVerified: true, - provenance: "reports/dev-gate/dev-artifacts.json" + provenance: "ci-artifact:dev-artifacts.json" }, services: [ { @@ -121,7 +131,7 @@ async function makeRepo(options = {}) { }, null, 2)}\n` ); await writeFile( - path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"), + artifactReportFixturePath, `${JSON.stringify({ reportVersion: "v1", taskId: "dev-artifact-publish", @@ -211,7 +221,7 @@ function makeRunCommand({ "deploy/artifact-catalog.dev.json", "deploy/deploy.json", "deploy/k8s/base/workloads.yaml", - "reports/dev-gate/dev-artifacts.json" + "ci-artifact:dev-artifacts.json" ], desiredStatePromotionStatus = "pass", runtimeJobLogSuffix = "", @@ -469,7 +479,7 @@ test("dev-cd status and dry-run are read-only compact JSON", async () => { "--dry-run", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report" + "--report", stateReport(repoRoot, "dev-cd-apply.json") ], { repoRoot, env: {}, @@ -490,7 +500,7 @@ test("dev-cd status and dry-run are read-only compact JSON", async () => { assert.equal(status.live.summary.checked, 2); assertNoReadOnlySideEffects(commandLog); await assert.rejects( - readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"), + readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"), { code: "ENOENT" } ); }); @@ -664,7 +674,7 @@ test("dev-cd status degrades artifact merge provenance when catalog/report do no kind: "hwlab-artifact-catalog", commitId: "9999999", artifactState: "published", - publish: { ciPublished: true, registryVerified: true, provenance: "reports/dev-gate/dev-artifacts.json" }, + publish: { ciPublished: true, registryVerified: true, provenance: "ci-artifact:dev-artifacts.json" }, services: [] }, null, 2)}\n` ); @@ -705,7 +715,7 @@ test("dev-cd status degrades artifact merge provenance when catalog/report do no test("apply stops before lock acquisition when artifact merge provenance mismatches deploy-json promotion", async () => { const repoRoot = await makeRepo({ commitId: "abc1234" }); await writeFile( - path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"), + artifactReportFixturePath, `${JSON.stringify({ status: "published", commitId: "9999999", @@ -1036,7 +1046,7 @@ test("apply preflight passes with D601 native k3s and required SecretRef keys be "task-preflight-pass", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report", + "--report", stateReport(repoRoot, "dev-cd-apply.json"), "--full-output" ], { repoRoot, @@ -1088,7 +1098,7 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma "task-missing-secret", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report" + "--report", stateReport(repoRoot, "dev-cd-apply.json") ], { repoRoot, env: {}, @@ -1105,7 +1115,7 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma }); const summary = JSON.parse(output); - const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); + const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8")); assert.equal(code, 2); assert.equal(summary.status, "blocked"); assert.equal(summary.preflight.status, "blocked"); @@ -1230,7 +1240,7 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a "task-b", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report", + "--report", stateReport(repoRoot, "dev-cd-apply.json"), "--full-output" ], { repoRoot, @@ -1270,10 +1280,10 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a assert.equal(report.devCdApply.liveVerify.summary.checked, 2); assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-web-16666"), true); assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-api-16667"), true); - assert.equal(report.devCdApply.reportPaths.transaction, "reports/dev-gate/dev-cd-apply.json"); - assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, "reports/dev-gate/dev-runtime-provisioning-report.json"); - assert.equal(report.devCdApply.reportPaths.runtimeMigration, "reports/dev-gate/dev-runtime-migration-report.json"); - assert.equal(report.devCdApply.reportPaths.runtimePostflight, "reports/dev-gate/dev-runtime-postflight-report.json"); + assert.equal(report.devCdApply.reportPaths.transaction, stateReport(repoRoot, "dev-cd-apply.json")); + assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, tempReport("dev-runtime-provisioning-report.json")); + assert.equal(report.devCdApply.reportPaths.runtimeMigration, tempReport("dev-runtime-migration-report.json")); + assert.equal(report.devCdApply.reportPaths.runtimePostflight, tempReport("dev-runtime-postflight-report.json")); const publishCall = commandLog.find((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")); const applyCall = commandLog.find((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")); @@ -1309,7 +1319,7 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a commandLog.findIndex((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")) ); - const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); + const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8")); assert.equal(writtenReport.devCdApply.liveVerify.summary.checked, 2); }); @@ -1325,7 +1335,7 @@ test("transaction can skip runtime postflight while preserving publish apply and "task-skip-postflight", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report", + "--report", stateReport(repoRoot, "dev-cd-apply.json"), "--full-output", "--skip-runtime-postflight" ], { @@ -1403,7 +1413,7 @@ test("transaction blocks with structured runtime postflight blocker when M3 evid "task-postflight-blocked", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report" + "--report", stateReport(repoRoot, "dev-cd-apply.json") ], { repoRoot, env: {}, @@ -1414,7 +1424,7 @@ test("transaction blocks with structured runtime postflight blocker when M3 evid }); const summary = JSON.parse(output); - const report = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); + const report = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8")); const blocker = report.blockers.find((item) => item.scope === "runtime-durable-postflight"); assert.equal(code, 2); assert.equal(summary.status, "blocked"); @@ -1447,7 +1457,7 @@ test("runtime maintenance job reports are parsed after redacting secret-like log "task-redaction", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report", + "--report", stateReport(repoRoot, "dev-cd-apply.json"), "--full-output" ], { repoRoot, @@ -1484,7 +1494,7 @@ test("transaction can apply an already published deploy-json promotion without r "task-promotion", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report", + "--report", stateReport(repoRoot, "dev-cd-apply.json"), "--full-output" ], { repoRoot, @@ -1551,7 +1561,7 @@ test("apply stdout is concise by default while write-report keeps the full repor "task-c", "--kubeconfig", "/etc/rancher/k3s/k3s.yaml", - "--write-report" + "--report", stateReport(repoRoot, "dev-cd-apply.json") ], { repoRoot, env: {}, @@ -1569,7 +1579,7 @@ test("apply stdout is concise by default while write-report keeps the full repor assert.equal(summary.lock.acquired, true); assert.equal(summary.steps.some((step) => step.id === "dev-deploy-apply"), true); - const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8")); + const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8")); assert.equal(writtenReport.devCdApply.deployJson.before.manifest.environment, "dev"); }); diff --git a/scripts/src/dev-cd-transaction-guard.mjs b/scripts/src/dev-cd-transaction-guard.mjs index 64a6f104..adb74715 100644 --- a/scripts/src/dev-cd-transaction-guard.mjs +++ b/scripts/src/dev-cd-transaction-guard.mjs @@ -1,3 +1,5 @@ +import { tempReportPath } from "./report-paths.mjs"; + const transactionEnvNames = [ "HWLAB_CD_TRANSACTION_ID", "HWLAB_CD_TRANSACTION_OWNER", @@ -22,7 +24,7 @@ export function devCdTransactionGuardFailure({ mode, requiredEnv, acceptedEnv: transactionEnvNames, - entrypoint: "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report", + entrypoint: `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${tempReportPath("dev-cd-apply.json")}`, summary: `${script} ${mode} is a DEV CD side-effect step and must run inside scripts/dev-cd-apply.mjs.`, devOnly: true, prodTouched: false, diff --git a/scripts/src/dev-cloud-workbench-smoke-lib.mjs b/scripts/src/dev-cloud-workbench-smoke-lib.mjs index e5e17c0d..e335f318 100644 --- a/scripts/src/dev-cloud-workbench-smoke-lib.mjs +++ b/scripts/src/dev-cloud-workbench-smoke-lib.mjs @@ -12,10 +12,13 @@ import { classifyCodeAgentBrowserFailure, summarizeCodeAgentPayload } from "./code-agent-response-contract.mjs"; +import { tempReportPath } from "./report-paths.mjs"; export { classifyCodeAgentBrowserJourney }; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const domOnlyReportPath = tempReportPath("dev-cloud-workbench-dom-only.json"); +const liveReportPath = tempReportPath("dev-cloud-workbench-live.json"); const webRoot = path.join(repoRoot, "web/hwlab-cloud-web"); const defaultLiveUrl = "http://74.48.78.17:16666/"; const helpOwner = "codex_1779444232735_1"; @@ -1584,11 +1587,11 @@ function baseReport({ "node scripts/dev-cloud-workbench-smoke.mjs --quick-prompts-fixture", "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-timeout-fixture", "node scripts/dev-cloud-workbench-smoke.mjs --layout --url http://74.48.78.17:16666/", - "node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json", + `node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report ${domOnlyReportPath}`, + `node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report ${liveReportPath}`, mode === "dom-only" - ? "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-dom-only.json" - : "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-live.json", + ? `node scripts/validate-dev-gate-report.mjs ${domOnlyReportPath}` + : `node scripts/validate-dev-gate-report.mjs ${liveReportPath}`, "node scripts/validate-dev-gate-report.mjs" ], localSmoke: { @@ -1620,8 +1623,8 @@ function baseReport({ "No hardware write API, service restart, PROD access, or secret read is performed." ], commands: [ - "node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json" + `node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report ${domOnlyReportPath}`, + `node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report ${liveReportPath}` ], summary: liveJourneyPassed ? "Deployed 16666 browser journey and same-origin Code Agent chat completed." @@ -1647,10 +1650,10 @@ function baseReport({ function reportModeId(mode) { return mode === "layout" - ? "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-layout.json" + ? "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-layout.json" : mode === "dom-only" - ? "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-dom-only.json" - : "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-live.json"; + ? "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-dom-only.json" + : "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-live.json"; } function addCheck(checks, blockers, id, result, summary, options = {}) { @@ -8441,7 +8444,7 @@ function escapeRegExp(value) { export function printSmokeHelp() { return { status: "usage", - command: "node scripts/dev-cloud-workbench-smoke.mjs --source | --layout [--url http://74.48.78.17:16666/] | --mobile | --local-agent-fixture | --quick-prompts-fixture | --local-agent-timeout-fixture | --dom-only --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-dom-only.json] | --live --confirm-dev-live --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-live.json]", + command: `node scripts/dev-cloud-workbench-smoke.mjs --source | --layout [--url http://74.48.78.17:16666/] | --mobile | --local-agent-fixture | --quick-prompts-fixture | --local-agent-timeout-fixture | --dom-only --url http://74.48.78.17:16666/ [--report ${domOnlyReportPath}] | --live --confirm-dev-live --url http://74.48.78.17:16666/ [--report ${liveReportPath}]`, notes: [ "Default/source mode reads repository files and emits SOURCE-level evidence only; it never calls the live provider and must not claim DEV-LIVE.", "--layout runs desktop and mobile browser geometry/hit-target checks for sidebar collapse/expand; with --url it remains layout-only live evidence and does not claim M3 acceptance.", diff --git a/scripts/src/dev-deploy-apply.mjs b/scripts/src/dev-deploy-apply.mjs index 1444e7e3..4c99a743 100755 --- a/scripts/src/dev-deploy-apply.mjs +++ b/scripts/src/dev-deploy-apply.mjs @@ -18,9 +18,10 @@ import { import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs"; import { requireDevCdTransactionForSideEffect } from "./dev-cd-transaction-guard.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const reportPath = "reports/dev-gate/dev-deploy-report.json"; +const defaultReportPath = tempReportPath("dev-deploy-report.json"); const namespace = "hwlab-dev"; const healthPath = "/health/live"; const defaultD601KubeconfigPath = "/etc/rancher/k3s/k3s.yaml"; @@ -73,9 +74,9 @@ const requiredValidationCommands = [ "node --check scripts/src/dev-deploy-apply.mjs", "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" ]; -const applyCommand = "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report"; -const dryRunPlanCommand = "node scripts/dev-deploy-apply.mjs --dry-run --write-report"; -const blockedDryRunCommand = "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report"; +const applyCommand = `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${tempReportPath("dev-cd-apply.json")}`; +const dryRunPlanCommand = `node scripts/dev-deploy-apply.mjs --dry-run --report ${defaultReportPath}`; +const blockedDryRunCommand = `node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report ${defaultReportPath}`; const forbiddenActions = [ "prod-deploy", "secret-read", @@ -93,6 +94,7 @@ export function parseArgs(argv) { const errors = []; let kubeconfig = null; let kubeconfigSpecified = false; + let reportPath = defaultReportPath; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; @@ -118,6 +120,27 @@ export function parseArgs(argv) { } continue; } + if (arg === "--report") { + flags.add("--report"); + const next = argv[index + 1]; + if (typeof next !== "string" || next.startsWith("--")) { + errors.push("--report requires a non-empty path value"); + } else { + reportPath = next; + flags.add("--report-output"); + index += 1; + } + continue; + } + if (arg.startsWith("--report=")) { + flags.add("--report"); + reportPath = arg.slice("--report=".length); + flags.add("--report-output"); + if (!reportPath.trim()) { + errors.push("--report requires a non-empty path value"); + } + continue; + } flags.add(arg); } @@ -126,7 +149,8 @@ export function parseArgs(argv) { dryRun: !flags.has("--apply"), expectBlocked: flags.has("--expect-blocked"), skipLiveProbe: flags.has("--skip-live-probe"), - writeReport: flags.has("--write-report"), + writeReport: flags.has("--report-output"), + reportPath, kubeconfig, kubeconfigSpecified, errors, @@ -1052,7 +1076,8 @@ function buildPlanConclusion(status, blockers) { function validateArgs(args, blockers) { for (const error of args.errors ?? []) { - addBlocker(blockers, "safety_blocker", "kubeconfig-argument", error); + const scope = error.startsWith("--report") ? "report-argument" : "kubeconfig-argument"; + addBlocker(blockers, "safety_blocker", scope, error); } for (const forbidden of ["--prod", "--production", "--read-secret", "--force-push", "--heavyweight-e2e"]) { if (args.flags.has(forbidden)) { @@ -1898,7 +1923,7 @@ export async function runDevDeployApply(argv, io = {}) { const report = { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-deploy-report.json", + $id: "https://hwlab.pikastech.local/dev-cd/dev-deploy-report.json", reportVersion: "v1", issue: "pikasTech/HWLAB#33", supports: ["pikasTech/HWLAB#164", "pikasTech/HWLAB#143", "pikasTech/HWLAB#99", "pikasTech/HWLAB#63", "pikasTech/HWLAB#50", "pikasTech/HWLAB#7", "pikasTech/HWLAB#33", "pikasTech/HWLAB#17", "pikasTech/HWLAB#32", "pikasTech/HWLAB#30"], @@ -2009,7 +2034,9 @@ export async function runDevDeployApply(argv, io = {}) { }; if (args.writeReport) { - await writeFile(path.join(repoRoot, reportPath), `${JSON.stringify(report, null, 2)}\n`); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, args.reportPath, "--report"); + await mkdir(path.dirname(absoluteReportPath), { recursive: true }); + await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } stdout.write(`${JSON.stringify(report, null, 2)}\n`); diff --git a/scripts/src/dev-edge-health-smoke-lib.mjs b/scripts/src/dev-edge-health-smoke-lib.mjs index bcc3aa64..a42cfdac 100644 --- a/scripts/src/dev-edge-health-smoke-lib.mjs +++ b/scripts/src/dev-edge-health-smoke-lib.mjs @@ -15,9 +15,10 @@ import { } from "../../internal/cloud/code-agent-contract.mjs"; import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultReportPath = path.join(repoRoot, "reports/dev-gate/dev-edge-health.json"); +const defaultReportPath = tempReportPath("dev-edge-health.json"); const publicHost = "74.48.78.17"; const publicPort = 16667; const namespace = "hwlab-dev"; @@ -124,12 +125,11 @@ function parseArgs(argv) { const arg = argv[index]; if (arg === "--live") { args.live = true; - } else if (arg === "--write-report") { - args.writeReport = true; } else if (arg === "--report") { index += 1; assert.ok(argv[index], "--report requires a path"); - args.reportPath = path.resolve(process.cwd(), argv[index]); + args.reportPath = argv[index]; + args.writeReport = true; } else { throw new Error(`unknown argument ${arg}`); } @@ -149,7 +149,7 @@ async function createDevGateReport(edgeHealth) { return { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-edge-health.json", + $id: "https://hwlab.pikastech.local/dev-gate/dev-edge-health.json", reportVersion: "v1", issue: "pikasTech/HWLAB#36", taskId: "dev-edge-health", @@ -179,7 +179,7 @@ async function createDevGateReport(edgeHealth) { "node --check scripts/dev-edge-health-smoke.mjs", "node --check scripts/src/dev-edge-health-smoke-lib.mjs", "node --test scripts/src/dev-edge-health-smoke-lib.test.mjs", - "node scripts/dev-edge-health-smoke.mjs --live --write-report" + `node scripts/dev-edge-health-smoke.mjs --live --report ${defaultReportPath}` ], localSmoke: { status: "pass", @@ -1202,6 +1202,7 @@ async function maybeWriteReport(report, args) { if (!args.writeReport) { return; } - await mkdir(path.dirname(args.reportPath), { recursive: true }); - await writeFile(args.reportPath, `${JSON.stringify(report, null, 2)}\n`); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, args.reportPath, "--report"); + await mkdir(path.dirname(absoluteReportPath), { recursive: true }); + await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/src/dev-evidence-blocker-aggregator.mjs b/scripts/src/dev-evidence-blocker-aggregator.mjs index 0065e1dd..1451a526 100644 --- a/scripts/src/dev-evidence-blocker-aggregator.mjs +++ b/scripts/src/dev-evidence-blocker-aggregator.mjs @@ -1,41 +1,13 @@ import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; -import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { activeReportLifecycle, reportIsHistorical, reportLifecycleState } from "../../internal/dev-report-lifecycle.mjs"; -import { - RUNTIME_DURABLE_ADAPTER_MISSING, - RUNTIME_DURABILITY_REQUIRED_EVIDENCE, - RUNTIME_STORE_KIND_POSTGRES -} from "../../internal/db/runtime-store.mjs"; -import { CLOUD_CORE_MIGRATION_ID } from "../../internal/db/schema.mjs"; -import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs"; +import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; +import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const activeBrowserRoot = `${DEV_FRONTEND_ENDPOINT}/`; -const activeApiLiveEndpoint = `${DEV_ENDPOINT}/health/live`; -const deprecatedLegacyPublicEndpoints = Object.freeze([ - "http://74.48.78.17:6666", - "http://74.48.78.17:6667" -]); -const latestFrontendRevision = "1e8805664970839b72be40c34636b08f6d18b131"; -const levels = Object.freeze(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED"]); -const statusRank = Object.freeze({ - pass: 0, - "contract-ready": 0, - "manifest-ready": 0, - ready: 0, - published: 0, - blocked: 1, - failed: 1, - not_run: 2, - not_observed: 2, - unavailable: 2, - not_applicable: 3 -}); const blockerTypes = new Set([ "contract_blocker", "environment_blocker", @@ -45,22 +17,9 @@ const blockerTypes = new Set([ "observability_blocker", "safety_blocker" ]); -const historicalLifecycleSummary = - "Historical report retained for evidence provenance; it is not active green DEV evidence."; - -const reportPaths = Object.freeze({ - devPreflight: "reports/dev-gate/dev-preflight-report.json", - 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", - d601Observability: "reports/d601-k3s-readonly-observability.json" -}); const validationCommands = Object.freeze([ + "node scripts/repo-reports-guard.mjs", "node --check scripts/dev-evidence-blocker-aggregator.mjs", "node --check scripts/src/dev-evidence-blocker-aggregator.mjs", "node scripts/dev-evidence-blocker-aggregator.mjs --check", @@ -69,108 +28,10 @@ const validationCommands = Object.freeze([ "node scripts/validate-dev-gate-report.mjs" ]); -const sourceContracts = Object.freeze([ - { - milestone: "M0", - issue: "pikasTech/HWLAB#31", - category: "contract", - level: "SOURCE", - status: "pass", - sources: [ - "docs/m0-contract-audit.md", - "protocol/README.md", - "protocol/evidence-chain.md", - "protocol/schemas/evidence-record.schema.json", - "protocol/examples/m0-contract/service-ids.json" - ], - commands: [ - "node scripts/validate-contract.mjs", - "node scripts/validate-m0-contract.mjs", - "node scripts/validate-evidence-chain.mjs" - ], - summary: "Frozen service IDs, JSON-RPC, audit, topology, evidence, and DEV-only deploy contracts are source-ready." - }, - { - milestone: "M1", - issue: "pikasTech/HWLAB#7", - category: "local-smoke", - level: "LOCAL", - status: "pass", - sources: [ - "docs/m1-local-smoke.md", - "fixtures/mvp/runtime.json", - "scripts/m1-contract-smoke.mjs" - ], - commands: ["node scripts/m1-contract-smoke.mjs"], - summary: "Local skeleton smoke covers cloud API, simulators, patch-panel routing, CLI dry-run boundary, and no DEV/PROD mutation." - } -]); - -const frontendDevFact = Object.freeze({ - revision: latestFrontendRevision, - sources: [ - "docs/dev-deploy-apply.md", - "docs/cloud-web-workbench.md", - "web/hwlab-cloud-web/index.html", - "web/hwlab-cloud-web/styles.css" - ], - evidence: [ - `Cloud Web /health/live observed revision ${latestFrontendRevision}`, - "Cloud Workbench public browser endpoint is usable in degraded/read-only mode only", - "Frontend load/revision evidence cannot satisfy DB, M3 hardware-loop, M4 agent-loop, or M5 MVP e2e acceptance" - ], - commands: [ - "node web/hwlab-cloud-web/scripts/check.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source" - ], - summary: `#99/#108 Cloud Workbench revision ${latestFrontendRevision} is the latest DEV frontend visibility fact; API/runtime durability remains degraded, so it is degraded/read-only UI evidence only.` -}); - function issue(id) { return `pikasTech/HWLAB#${id}`; } -function short(value) { - return typeof value === "string" && value.length > 12 ? value.slice(0, 12) : value ?? "unknown"; -} - -function oneLine(value) { - return String(value ?? "") - .replace(/\s+/gu, " ") - .trim(); -} - -function primaryStatus(report) { - return report.status ?? - report.gateStatus ?? - report.conclusion ?? - report.artifactPublish?.status ?? - (report.blockers?.length > 0 ? "blocked" : "not_run"); -} - -function rank(status) { - return statusRank[status] ?? 2; -} - -function worstStatus(values) { - const statuses = values.filter(Boolean); - if (statuses.length === 0) return "not_run"; - return statuses.sort((a, b) => rank(b) - rank(a))[0]; -} - -function statusIsPass(status) { - return rank(status) === 0; -} - -function statusHasEvidence(status) { - return !["not_run", "not_observed", "unavailable", "not_applicable"].includes(status); -} - -async function readJSON(relativePath) { - const raw = await readFile(path.join(repoRoot, relativePath), "utf8"); - return JSON.parse(raw); -} - function gitCommit() { try { return execFileSync("git", ["rev-parse", "--short=12", "HEAD"], { @@ -183,1322 +44,337 @@ function gitCommit() { } } -function normalizeBlocker(blocker, sourceReport, fallbackPriority = "P3") { - assert.ok(blocker && typeof blocker === "object" && !Array.isArray(blocker), "blocker must be an object"); - assert.ok(blockerTypes.has(blocker.type), `unknown blocker type ${blocker.type}`); - const nextTask = oneLine(blocker.nextTask ?? blocker.unblockHint ?? blocker.next ?? ""); - - return { - id: `${sourceReport.taskId ?? sourceReport.reportKind ?? "report"}:${blocker.scope}`, - priority: fallbackPriority, - type: blocker.type, - scope: blocker.scope, - status: blocker.status ?? "open", - source: sourceReport.path, - sourceIssue: blocker.sourceIssue ?? sourceReport.issue, - summary: oneLine(blocker.summary), - ...(nextTask ? { nextTask } : {}) - }; -} - -function dedupeBlockers(blockers) { - const byKey = new Map(); - for (const blocker of blockers) { - const key = `${blocker.type}:${blocker.scope}`; - const existing = byKey.get(key); - if (!existing) { - byKey.set(key, blocker); - } else if (!existing.nextTask && blocker.nextTask) { - byKey.set(key, { ...existing, nextTask: blocker.nextTask }); - } else if (blocker.priority < existing.priority) { - byKey.set(key, { ...blocker, nextTask: blocker.nextTask || existing.nextTask }); - } - } - return [...byKey.values()]; -} - -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 { - ...blocker, - priority: "P0", - unblockOrder: 1, - unblocks: [issue(35), issue(33)], - rationale: "Artifact publishing cannot start until an approved Node 20 DEV builder base image is available." - }; - } - - if ( - blocker.scope === "hwlab-router" || - blocker.scope === "hwlab-tunnel-client" || - blocker.scope === "hwlab-edge-proxy" - ) { - return { - ...blocker, - priority: "P0", - unblockOrder: 2, - unblocks: [issue(35), issue(36), issue(33)], - rationale: "The DEV route services need real HWLAB runtime entrypoints before image publish, frp, or ingress smoke can be meaningful." - }; - } - - if (blocker.scope.includes("artifact") || blocker.scope === "ghcr") { - return { - ...blocker, - priority: "P1", - unblockOrder: 3, - unblocks: [issue(35), issue(33), issue(39)], - rationale: "The gate cannot promote deploy or runtime observations without immutable image provenance and digests." - }; - } - - if ( - blocker.scope === "kubectl" || - blocker.scope === "d601-k3s" || - blocker.scope === "runner-kubeconfig-readonly-gap" || - blocker.scope === "m3-service-discovery" || - blocker.scope === "m3-direct-target-missing" || - blocker.scope.startsWith("d601-") - ) { - return { - ...blocker, - priority: "P1", - unblockOrder: 4, - unblocks: [issue(34), issue(33), issue(36), issue(38), issue(46), issue(64)], - rationale: "Runner read-only observability must be repaired without treating the runner gap as proof that D601 k3s or public DEV endpoints are unavailable." - }; - } - - 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; this is not evidence that D601 is globally offline." - }; - } - - if (blocker.scope.includes("cloud-api-db")) { - return { - ...blocker, - priority: "P1", - unblockOrder: 5, - unblocks: [issue(34), issue(33), issue(39)], - rationale: "Cloud API DB env and health readiness block runtime health and MVP evidence even if ingress starts responding." - }; - } - - 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 (blocker.scope === "runtime-durable-adapter") { - return { - ...blocker, - priority: "P1", - unblockOrder: 5, - unblocks: [issue(37), issue(39), issue(164)], - rationale: "M4 and M5 cannot claim live agent or MVP evidence until the cloud-api postgres runtime adapter proves schema, migration, and read-query durability; DB live evidence alone is not durability evidence." - }; - } - - if (blocker.scope === "skills-commit-version-injection" || blocker.scope === "agent-mgr-health") { - return { - ...blocker, - priority: "P1", - unblockOrder: 5, - unblocks: [issue(37), issue(39), issue(164)], - rationale: "M4/M5 agent evidence needs deployed manager, skills, and worker paths to expose explicit skills commit and version; session-mode-only worker templates are not enough." - }; - } - - if (isEdgeOrFrpBlocker(blocker)) { - return { - ...blocker, - priority: "P2", - unblockOrder: 6, - unblocks: [issue(36), issue(38), issue(37), issue(39)], - rationale: "The :16667/frp path must be reachable before any DEV-LIVE M3, M4, or M5 evidence can be collected." - }; - } - - if ( - blocker.scope === "m3-hardware-loop-runtime" || - blocker.scope === "m3-patch-panel-wiring" || - blocker.scope === "m3-box-simu-identity" || - blocker.scope === "m3-gateway-simu-identity" - ) { - 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", - unblockOrder: 99, - unblocks: [issue(39)], - rationale: "Residual blocker that must be classified before claiming a green DEV gate." - }; -} - -function isEdgeOrFrpBlocker(blocker) { - return isRouteTransportBlocker(blocker); -} - -function sourceEntry(entry) { - return { - milestone: entry.milestone, - issue: entry.issue, - level: entry.level, - status: entry.status, - category: entry.category, - lifecycleState: entry.lifecycleState ?? "active", - sources: entry.sources, - commands: entry.commands, - summary: entry.summary - }; -} - -function reportEvidence({ milestone, level, report, status, category, summary, evidence = [], commands = [] }) { - const lifecycleState = reportLifecycleState(report); - const historical = lifecycleState === "historical"; +function sourceEntry({ milestone, issue: issueId, level, status, category, sources, commands, summary }) { return { milestone, - issue: report.issue, - taskId: report.taskId ?? report.reportKind, - reportPath: report.path, - commitId: short(report.commitId ?? report.target?.commitId), - lifecycleState, - level: historical ? "BLOCKED" : level, - status: historical && status === "pass" ? "blocked" : status, + issue: issueId, + level, + status, category, + lifecycleState: "active", + sources, commands, - evidence, - summary: historical ? `${summary} ${historicalLifecycleSummary}` : summary + summary }; } -function collectM2Evidence(reports) { - const preflight = reports.devPreflight; - 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 [ - { - milestone: "M2", - issue: issue(99), - taskId: "cloud-web-workbench-frontend", - lifecycleState: "active", - level: "DEV-LIVE", - status: "pass", - category: "frontend-dev-revision", - sources: frontendDevFact.sources, - commands: frontendDevFact.commands, - evidence: frontendDevFact.evidence, - summary: frontendDevFact.summary - }, - reportEvidence({ - milestone: "M2", - level: "SOURCE", - report: preflight, - status: "pass", - category: "deploy-manifest", - evidence: [ - "source-contract-static=pass", - `artifact catalog state=${artifactIdentity.artifactCatalog.artifactState}`, - `catalog commit=${artifactIdentity.artifactCatalog.commitId}` - ], - commands: preflight.validationCommands, - summary: "Deploy manifests, FRP/master-edge contracts, and safety boundary are source-readable and scoped to hwlab-dev." - }), - reportEvidence({ - milestone: "M2", - level: "DRY-RUN", - report: deploy, - status: deploy.dryRun?.status ?? primaryStatus(deploy), - category: "deploy-apply", - evidence: deploy.dryRun?.evidence ?? [], - commands: deploy.dryRun?.commands ?? [], - summary: deploy.dryRun?.summary ?? "DEV apply dry-run report is available." - }), - reportEvidence({ - milestone: "M2", - level: "DRY-RUN", - report: artifacts, - status: artifacts.artifactPublish?.status ?? primaryStatus(artifacts), - category: "artifact-publish", - evidence: [ - `services=${artifacts.artifactPublish?.services?.length ?? 0}`, - `baseImagePreflight=${artifacts.artifactPublish?.baseImagePreflight?.status ?? "unknown"}`, - `published=${artifacts.artifactPublish?.services?.filter((service) => service.status === "published").length ?? 0}/${SERVICE_IDS.length}` - ], - 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", - report: edge, - status: edge.edgeHealth?.status ?? primaryStatus(edge), - category: "edge-frp-contract", - evidence: [ - `mode=${edge.edgeHealth?.mode ?? "unknown"}`, - `classification=${edge.edgeHealth?.classification ?? "unknown"}`, - `cloudApiDb=${edge.edgeHealth?.contracts?.deploy?.cloudApiDb?.status ?? "unknown"}` - ], - commands: edge.validationCommands, - summary: edgeLive - ? "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; remaining edge report blocker is DB readiness, not route/frp reachability." - : "Committed edge report is contract-only/not-run; it must not be counted as DEV-LIVE." - }), - reportEvidence({ - milestone: "M2", - level: d601.cluster?.readable === true || d601.d601PublicEndpointsReachable === true ? "DEV-LIVE" : "BLOCKED", - report: d601, - status: d601.conclusion, - category: "d601-observability", - evidence: d601ObservationSummary(d601), - commands: d601.validationCommands, - summary: d601ObservabilitySummary(d601) - }) - ]; -} - -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", - level: "SOURCE", - report: m3, - status: "manifest-ready", - category: "hardware-loop-cardinality", - evidence: m3.readOnlySupplementalEvidence?.map((item) => `${item.id}=${item.status}`) ?? [], - commands: ["node scripts/validate-dev-m3-cardinality.mjs"], - summary: "Static DEV manifest cardinality declares two box simulators, two gateway simulators, and one patch panel." - }), - reportEvidence({ - milestone: "M3", - level: "LOCAL", - report: reports.devM5Gate, - status: m5Milestone?.status ?? "not_run", - category: "hardware-loop-local", - evidence: m5Milestone?.evidence ?? [], - commands: m5Milestone?.commands ?? ["node scripts/m3-hardware-loop-smoke.mjs"], - summary: m5Milestone?.summary ?? "No local M3 smoke evidence was attached." - }), - reportEvidence({ - milestone: "M3", - level: statusIsPass(m3.liveOperation?.status) ? "DEV-LIVE" : "BLOCKED", - report: m3, - status: m3.liveOperation?.status ?? "not_run", - category: "hardware-loop-live", - evidence: [ - `operationId=${m3.liveOperation?.operationId ?? "not_observed"}`, - `traceId=${m3.liveOperation?.traceId ?? "not_observed"}`, - `auditId=${m3.liveOperation?.auditId ?? "not_observed"}`, - `evidenceId=${m3.liveOperation?.evidenceId ?? "not_observed"}` - ], - commands: [ - "node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod" - ], - 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" || - blocker.scope === "m3-service-discovery" || - blocker.scope === "m3-direct-target-missing" || - blocker.scope === "m3-box-simu-identity" || - blocker.scope === "m3-gateway-simu-identity" || - blocker.scope === "m3-patch-panel-wiring" - )?.summary; - if (statusIsPass(m3Report.liveOperation?.status) || !blockerSummary) { - return operationSummary; - } - return `${operationSummary} Blocker: ${blockerSummary.replace(/[.。]+$/u, "")}.`; -} - -function collectM4Evidence(reports) { - const m4 = reports.devM4Agent; - return [ - reportEvidence({ - milestone: "M4", - level: "LOCAL", - report: m4, - status: m4.localSmoke?.status ?? "not_run", - category: "agent-loop-local", - evidence: m4.localSmoke?.evidence ?? [], - commands: m4.localSmoke?.commands ?? [], - summary: m4.localSmoke?.summary ?? "No local M4 smoke evidence was attached." - }), - reportEvidence({ - milestone: "M4", - level: "DRY-RUN", - report: m4, - status: m4.dryRun?.status ?? "not_run", - category: "agent-loop-dry-run", - evidence: m4.dryRun?.evidence ?? [], - commands: m4.dryRun?.commands ?? [], - summary: m4.dryRun?.summary ?? "No M4 dry-run evidence was attached." - }), - reportEvidence({ - milestone: "M4", - level: statusIsPass(m4.livePreflight?.status) ? "DEV-LIVE" : "BLOCKED", - report: m4, - status: m4.livePreflight?.status ?? m4.devPreconditions?.status ?? "not_run", - category: "agent-loop-live-preflight", - evidence: m4.livePreflight?.evidence ?? [], - commands: m4.livePreflight?.commands ?? [], - summary: m4.livePreflight?.summary ?? m4.devPreconditions?.summary ?? "No live M4 observation was recorded." - }) - ]; -} - -function collectM5Evidence(reports) { - const m5 = reports.devM5Gate; - return [ - reportEvidence({ - milestone: "M5", - level: "DRY-RUN", - report: m5, - status: m5.dryRun?.status ?? "not_run", - category: "mvp-e2e-dry-run", - evidence: m5.dryRun?.evidence ?? [], - commands: m5.dryRun?.commands ?? [], - summary: m5.dryRun?.summary ?? "No M5 dry-run evidence was attached." - }), - reportEvidence({ - milestone: "M5", - level: statusIsPass(m5.devPreconditions?.status) ? "DEV-LIVE" : "BLOCKED", - report: m5, - status: m5.devPreconditions?.status ?? m5.gateStatus ?? "blocked", - category: "mvp-e2e-live", - evidence: m5.devPreconditions?.evidence ?? [], - commands: m5.devPreconditions?.commands ?? [], - summary: m5.devPreconditions?.summary ?? "No live M5 e2e evidence was attached." - }) - ]; -} - -function collectBlockers(reports) { - const blockers = []; - for (const report of Object.values(reports)) { - if (reportIsHistorical(report)) { - blockers.push({ - id: `${report.taskId ?? report.reportKind ?? "report"}-historical:${report.path}`, - priority: "P2", - type: "observability_blocker", - scope: `${report.taskId ?? report.reportKind ?? "report"}-historical`, - status: "open", - source: report.path, - sourceIssue: report.issue, - summary: `${report.taskId ?? report.reportKind ?? "Report"} is historical/deprecated and must be refreshed before it can support active DEV evidence. ${historicalLifecycleSummary}`, - nextTask: "Regenerate the report as active DEV evidence or keep the historical snapshot in an archival path." - }); - } - for (const blocker of report.blockers ?? []) { - if (isStaleLegacyIngressBlocker(blocker, report, reports)) { - continue; - } - if (isStaleReadinessBlocker(blocker, reports)) { - continue; - } - blockers.push(normalizeBlocker(blocker, report)); - } - for (const blocker of report.artifactPublish?.blockers ?? []) { - if (isStaleLegacyIngressBlocker(blocker, report, reports)) { - continue; - } - if (isStaleReadinessBlocker(blocker, reports)) { - continue; - } - blockers.push(normalizeBlocker(blocker, report)); - } - for (const blocker of report.devDeployApply?.remainingBlockers ?? []) { - if (isStaleLegacyIngressBlocker(blocker, report, reports)) { - continue; - } - if (isStaleReadinessBlocker(blocker, reports)) { - continue; - } - blockers.push(normalizeBlocker(blocker, report)); - } - } - - const normalized = dedupeBlockers(blockers).map(applyPriority); - return normalized.sort((a, b) => a.unblockOrder - b.unblockOrder || a.id.localeCompare(b.id)); -} - -function hasCurrentLiveEdgeEvidence(edgeReport) { - const publicHttp = edgeReport?.edgeHealth?.publicHttp ?? []; - return edgeReport?.edgeHealth?.endpoint === DEV_ENDPOINT && - edgeReport?.edgeHealth?.mode === "live-read-only" && - publicHttp.some((probe) => probe.url === `${DEV_ENDPOINT}/health` && probe.ok === true && probe.status === 200) && - 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 d601ObservationSummary(d601) { - return [ - `runnerKubeconfigReadable=${d601.runnerKubeconfigReadable === true}`, - `runnerKubeconfigProbeExitCode=${d601.runnerKubeconfigProbeExitCode ?? "unknown"}`, - `runnerKubeconfigProbeStderr=${d601.runnerKubeconfigProbeStderr || "empty"}`, - `d601PublicEndpointsReachable=${d601.d601PublicEndpointsReachable === true}`, - `d601K3sUnavailable=${d601.d601K3sUnavailable === true}` - ]; -} - -function d601ObservabilitySummary(d601) { - if (d601.d601PublicEndpointsReachable === true && d601.runnerKubeconfigReadable === false) { - const clusterNote = d601.cluster?.readable === true - ? " Alternate read-only cluster probes are readable." - : ""; - return `D601 public DEV endpoints are reachable, but the runner cannot read /etc/rancher/k3s/k3s.yaml; classify as #46 runner permission/mount or read-only observability gap, not D601 global offline.${clusterNote} ${d601ObservationSummary(d601).join(", ")}.`; - } - if (d601.cluster?.readable === true) { - return `D601 hwlab-dev cluster is readable from this runner; ${d601ObservationSummary(d601).join(", ")}.`; - } - if (d601.d601K3sUnavailable === true) { - return `Direct read-only probes indicate D601 k3s may be unavailable; ${d601ObservationSummary(d601).join(", ")}.`; - } - return `D601 read-only observability is blocked without proving D601 unavailable; ${d601ObservationSummary(d601).join(", ")}.`; -} - -function isStaleLegacyIngressBlocker(blocker, sourceReport, reports) { - const text = `${blocker.scope ?? ""} ${blocker.summary ?? ""}`; - const staleLegacyPort = deprecatedLegacyPublicEndpoints.some((endpoint) => text.includes(endpoint)); - const stalePreflightEdgeProbe = sourceReport.path === reportPaths.devPreflight && - blocker.type === "network_blocker" && - (blocker.scope === "dev-edge" || blocker.scope === "dev-edge-health") && - ( - text.includes(`${DEV_ENDPOINT}/health/live is not reachable`) || - text.includes("live network probes require --live") - ); - - if (!staleLegacyPort && !stalePreflightEdgeProbe) { - return false; - } - const currentEdgeReachable = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth); - return currentEdgeReachable && sourceReport.path !== reportPaths.devM3Hardware && - sourceReport.path !== reportPaths.devM4Agent && - sourceReport.path !== reportPaths.devM5Gate; -} - -function isStaleReadinessBlocker(blocker, reports) { - const scope = blocker.scope ?? ""; - const db = cloudApiDbStatus(reports); - const provider = codeAgentProviderStatus(reports); - if ( - ( - scope === "cloud-api-db" || - scope === "cloud-api-db-health-gate" || - scope === "cloud-api-db-live" || - scope === "db-live" - ) && - db.ready && - db.connected && - db.liveDbEvidence - ) { - return true; - } - if ((scope === "code-agent-provider-secret" || scope === "code-agent-provider") && provider.ready) { - return true; - } - return false; -} - -function deriveMilestones(evidence, blockers) { - const evidenceByMilestone = new Map(); - for (const item of evidence) { - if (!evidenceByMilestone.has(item.milestone)) { - evidenceByMilestone.set(item.milestone, []); - } - evidenceByMilestone.get(item.milestone).push(item); - } - - return ["M0", "M1", "M2", "M3", "M4", "M5"].map((milestone) => { - const items = evidenceByMilestone.get(milestone) ?? []; - 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)); - if (milestone === "M4") return blocker.unblocks.includes(issue(37)); - if (milestone === "M5") return blocker.unblocks.includes(issue(39)); - return false; - }); - const status = milestoneBlockers.length > 0 || hasBlockedEvidence || (liveItems.length > 0 && !hasPassingLive) - ? "blocked" - : worstStatus(items.map((item) => item.status)); - - return { - id: milestone, - status, - highestVisibleLevel, - liveEvidence: hasPassingLive ? "pass" : "missing_or_blocked", - evidenceCount: items.length, - blockerCount: milestoneBlockers.length, - summary: summarizeMilestone(milestone, highestVisibleLevel, status) - }; - }); -} - -function summarizeMilestone(milestone, highestVisibleLevel, status) { - const labels = { - M0: "contract source is available", - M1: "local smoke is available", - M2: "deploy/runtime readiness is blocked before live DEV", - M3: "hardware loop has source/local shape but no live operation", - M4: "agent loop has local smoke but live preflight is blocked", - M5: "dry-run is green but live MVP gate is blocked" - }; - return `${labels[milestone]}; highest visible level is ${highestVisibleLevel}; status is ${status}.`; -} - -function buildMilestoneLevelClassification(milestones) { - const currentLabels = { - M0: "SOURCE", - M1: "LOCAL", - M2: "DEV-LIVE", - M3: "BLOCKED", - M4: "BLOCKED", - M5: "BLOCKED" - }; - const notes = { - M0: "Source contract is green at source level only.", - M1: "Local smoke is green; it is not DEV-LIVE.", - M2: "Current public frontend/API route evidence is DEV-LIVE for route/front-end reachability only.", - M3: "DEV-LIVE hardware trusted loop is blocked; local/source shape is not acceptance.", - M4: "DEV-LIVE agent loop is blocked at runtime durable adapter or skills injection readiness; local smoke is not acceptance.", - M5: "Dry-run is green, but bounded DEV-LIVE MVP e2e is blocked." - }; - - return milestones.map((milestone) => ({ - milestone: milestone.id, - currentLevel: currentLabels[milestone.id], - status: milestone.status, - strongestEvidenceLevel: milestone.highestVisibleLevel, - liveEvidence: milestone.liveEvidence, - summary: notes[milestone.id] - })); -} - function groupByLevel(evidence, blockers) { - const grouped = Object.fromEntries(levels.map((level) => [level, []])); - for (const item of evidence) { - grouped[item.level].push({ - milestone: item.milestone, - issue: item.issue, - taskId: item.taskId, - lifecycleState: item.lifecycleState, - status: item.status, - category: item.category, - reportPath: item.reportPath, - summary: item.summary + const grouped = Object.fromEntries(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED"].map((level) => [level, []])); + for (const entry of evidence) { + grouped[entry.level]?.push({ + milestone: entry.milestone, + category: entry.category, + status: entry.status, + summary: entry.summary + }); + } + for (const blocker of blockers) { + grouped.BLOCKED.push({ + milestone: blocker.milestone ?? "M5", + category: blocker.scope, + status: blocker.status, + summary: blocker.summary }); } - grouped.BLOCKED.push( - ...blockers.map((blocker) => ({ - priority: blocker.priority, - order: blocker.unblockOrder, - type: blocker.type, - scope: blocker.scope, - sourceIssue: blocker.sourceIssue, - source: blocker.source, - summary: blocker.summary, - nextTask: blocker.nextTask - })) - ); return grouped; } -function buildDoD(reports, milestones, blockers) { - const m0 = milestones.find((item) => item.id === "M0"); - const m1 = milestones.find((item) => item.id === "M1"); - const m5 = milestones.find((item) => item.id === "M5"); - const preflight = reports.devPreflight; - const artifactIdentity = preflight.artifactIdentity; - const d601 = reports.d601Observability; - 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.endpointSource === "secret-url-host" && - cloudApiDb.liveDbEvidence === true; - const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status); - const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status); - +function governanceBlocker() { return { - status: blockers.length === 0 && milestones.every((item) => item.status === "pass") ? "green" : "blocked", - green: blockers.length === 0 && milestones.every((item) => item.status === "pass"), - checks: [ - { - id: "m0-source-contract", - status: m0?.status === "pass" ? "pass" : "blocked", - evidenceLevel: "SOURCE", - summary: "M0 contract checks are source-level evidence only." - }, - { - id: "m1-local-smoke", - status: m1?.status === "pass" ? "pass" : "blocked", - evidenceLevel: "LOCAL", - summary: "M1 local smoke is not a live DEV substitute." - }, - { - id: "artifact-publish-digests", - 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", - status: d601.runnerKubeconfigReadable === false ? "blocked" : d601.cluster?.readable ? "pass" : "blocked", - evidenceLevel: d601.cluster?.readable || d601.d601PublicEndpointsReachable ? "DEV-LIVE" : "BLOCKED", - summary: d601ObservabilitySummary(d601) - }, - { - id: "dev-edge-frp-16667", - 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; 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: dbReady ? "pass" : "blocked", - evidenceLevel: dbReady ? "DEV-LIVE" : "BLOCKED", - summary: `cloud-api DB status=${cloudApiDb.status}; ready=${cloudApiDb.ready}; connected=${cloudApiDb.connected}; endpointSource=${cloudApiDb.endpointSource}; 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", - status: m5?.status === "pass" && m5.liveEvidence === "pass" ? "pass" : "blocked", - evidenceLevel: "BLOCKED", - summary: "M5 dry-run passed; bounded DEV-LIVE MVP e2e has not passed." - } - ] + id: "repo-report-directory:removed", + priority: "P0", + unblockOrder: 1, + type: "observability_blocker", + scope: "repo-report-directory-removed", + status: "open", + sourceIssue: issue(411), + summary: "The repository report directory has been removed; durable progress and acceptance evidence must be recorded in GitHub issue or PR comments, with temporary JSON limited to /tmp, .state, or CI artifacts.", + nextTask: "Rerun any live or CD checks with explicit non-repository output paths and post the operational conclusion to the owning issue or PR." }; } -function cloudApiDbStatus(reports) { - const liveDb = reports.devM4Agent.publicEntrypoints?.api?.live?.db ?? null; - if (liveDb?.ready === true && liveDb?.connected === true && liveDb?.liveDbEvidence === true) { - return { - status: liveDb.status ?? "ok", - ready: true, - connected: true, - configReady: liveDb.configReady === true, - connectionChecked: liveDb.connectionChecked === true, - endpointSource: liveDb.endpointSource ?? liveDb.connection?.endpointSource ?? "secret-url-host", - liveDbEvidence: true - }; - } - const db = reports.devEdgeHealth.edgeHealth?.contracts?.deploy?.cloudApiDb ?? {}; +function runtimeDurableBlocker() { return { - status: db.status ?? "unknown", - ready: db.ready === true, - connected: db.connected === true || db.liveConnected === true, - configReady: db.configReady === true, - connectionChecked: db.connectionChecked === true, - endpointSource: db.endpointSource ?? db.connection?.endpointSource ?? "unknown", - liveDbEvidence: db.liveDbEvidence === true + id: "m4:runtime-durable-adapter", + priority: "P1", + unblockOrder: 2, + type: "runtime_blocker", + scope: "runtime-durable-adapter", + status: "open", + sourceIssue: issue(164), + summary: "M4/M5 remain blocked until cloud-api durable runtime readiness proves schema, migration, and read-query durability; DB live evidence alone is not acceptance.", + nextTask: "Repair runtime durability and rerun the read-only M4/M5 checks with non-repository output." }; } -function codeAgentProviderStatus(reports) { - const codeAgent = reports.devM4Agent.publicEntrypoints?.api?.live?.codeAgent ?? - reports.devM4Agent.publicEntrypoints?.api?.health?.codeAgent ?? - null; +function skillsBlocker() { return { - ready: Boolean(codeAgent?.ready || codeAgent?.status === "ready" || codeAgent?.status === "available"), - status: codeAgent?.status ?? "unknown", - provider: codeAgent?.provider ?? "unknown" + id: "m4:skills-commit-version-injection", + priority: "P1", + unblockOrder: 3, + type: "agent_blocker", + scope: "skills-commit-version-injection", + status: "open", + sourceIssue: issue(164), + summary: "M4/M5 agent evidence still requires deployed manager, skills, and worker paths to expose explicit skills commit and version.", + nextTask: "Inject and surface skills commit/version on the live DEV agent stack before claiming agent-loop acceptance." }; } -function runtimeDurabilityStatusFromReport(report = {}) { - const runtime = report.runtime ?? - report.livePreflight?.runtime ?? - report.publicEntrypoints?.api?.live?.runtime ?? - report.readiness?.durability ?? - {}; - const dependency = report.dependencyState?.runtimeDurableAdapter ?? {}; - const component = report.componentEvidence?.runtimeDurableAdapter ?? {}; - const blocker = report.blockers?.find((item) => item.scope === "runtime-durable-adapter") ?? null; - const migration = runtime.migration ?? {}; +function hardwareLoopBlocker() { return { - adapter: runtime.adapter ?? "unknown", - durable: runtime.durable === true, - durableRequested: Boolean( - runtime.durableRequested || - runtime.adapter === RUNTIME_STORE_KIND_POSTGRES || - blocker || - dependency.status === "blocked" || - component.status === "blocked" - ), - ready: Boolean(runtime.ready === true && runtime.liveRuntimeEvidence === true && runtime.durable === true), - runtimeStatus: runtime.status ?? (blocker ? "blocked" : "unknown"), - blocker: blocker?.classification ?? runtime.blocker ?? report.readiness?.durability?.blocker ?? RUNTIME_DURABLE_ADAPTER_MISSING, - blockedLayer: runtime.durabilityContract?.blockedLayer ?? report.readiness?.durability?.blockedLayer ?? null, - requiredEvidence: runtime.durabilityContract?.requiredEvidence ?? - report.readiness?.durability?.requiredEvidence ?? - RUNTIME_DURABILITY_REQUIRED_EVIDENCE, - migration: { - checked: migration.checked ?? null, - ready: migration.ready ?? null, - missing: migration.missing ?? null, - requiredMigrationId: migration.requiredMigrationId ?? CLOUD_CORE_MIGRATION_ID, - appliedMigrationId: migration.appliedMigrationId ?? null - }, - queryAttempted: runtime.connection?.queryAttempted ?? null, - queryResult: runtime.connection?.queryResult ?? "unknown", - summary: blocker?.summary ?? dependency.summary ?? component.summary ?? "No runtime durable adapter evidence was attached." + id: "m3:m3-hardware-loop-runtime", + priority: "P0", + unblockOrder: 4, + type: "runtime_blocker", + scope: "m3-hardware-loop-runtime", + status: "open", + sourceIssue: issue(38), + summary: "M3 remains blocked until the real DEV hardware trusted loop proves DO1 to patch-panel to DI1 with operation, trace, audit, and evidence identifiers.", + nextTask: "Run the DEV M3 trusted-loop smoke after topology repair and record the conclusion on the owning GitHub issue." }; } -function runtimeDurabilityStatus(reports) { - return runtimeDurabilityStatusFromReport(reports.devM4Agent); -} - -function m4SkillsInjectionStatus(reports) { - const native = reports.devM4Agent.d601K3sNative ?? {}; - const injection = native.skillsInjection ?? {}; - const blockers = reports.devM4Agent.blockers ?? []; - const injectionBlocker = blockers.find((blocker) => blocker.scope === "skills-commit-version-injection") ?? null; - const managerBlocker = blockers.find((blocker) => blocker.scope === "agent-mgr-health") ?? null; - const missing = Array.isArray(injection.missing) ? injection.missing : []; - const managerMissing = native.agentManager?.health?.missingSkills ?? []; - return { - status: injectionBlocker || managerBlocker || injection.status === "blocked" ? "blocked" : "pass", - serviceCommitId: injection.serviceCommitId ?? null, - serviceVersion: injection.serviceVersion ?? null, - workerDryRunCommitId: injection.workerDryRunCommitId ?? null, - workerDryRunVersion: injection.workerDryRunVersion ?? null, - missing: [...new Set([...missing, ...managerMissing])], - summary: injectionBlocker?.summary ?? - managerBlocker?.summary ?? - (injection.status === "blocked" - ? `DEV skills injection is incomplete: missing ${missing.join(", ")}.` - : "DEV skills commit and version injection evidence is present.") - }; -} - -function buildCurrentDevLayering(reports, blockers) { - const m2EndpointLive = hasCurrentM2EndpointEvidence(reports.devM2Smoke); - const edgeLive = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth); - const cloudDb = cloudApiDbStatus(reports); - const provider = codeAgentProviderStatus(reports); - const runtimeDurability = runtimeDurabilityStatus(reports); - const skillsInjection = m4SkillsInjectionStatus(reports); - const dbReady = cloudDb.ready && cloudDb.connected && cloudDb.endpointSource === "secret-url-host" && 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 { - frontendRevision: { - label: "Frontend DEV revision", +function buildDoD(blockers) { + const checks = [ + { + id: "repo-report-directory-absent", status: "pass", - evidenceLevel: "DEV-LIVE", - summary: `${activeBrowserRoot} serves the Cloud Workbench frontend revision ${latestFrontendRevision}; deployed UI is usable in degraded/read-only mode only and this is browser/frontend evidence only.`, - evidence: frontendDevFact.evidence, - nextRequired: "Keep frontend revision proof separate from DB live readiness, M3 hardware-loop evidence, M4 agent-loop evidence, and M5 acceptance." + evidenceLevel: "SOURCE", + summary: "Repository report directory is forbidden and guarded by repo checks." }, - edgeRoute: { - label: "EDGE/ROUTE live", - status: m2EndpointLive || edgeLive ? "pass" : "blocked", - evidenceLevel: m2EndpointLive || edgeLive ? "DEV-LIVE" : "BLOCKED", - summary: m2EndpointLive - ? `${activeBrowserRoot}, ${DEV_ENDPOINT}/health, and ${activeApiLiveEndpoint} returned observed HWLAB DEV route responses in the active M2 read-only smoke.` - : `No active read-only public endpoint report proves both ${activeBrowserRoot} and ${activeApiLiveEndpoint}.`, - evidence: publicEndpointEvidence(reports.devM2Smoke), - nextRequired: "Keep this separated from DB readiness, M3/M4 loop evidence, and M5 acceptance." + { + id: "cloud-api-db-ready", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Current checked source cannot claim DB readiness from deleted repository report files; rerun live readiness and post the result outside the repo." + }, + { + id: "runtime-durable-ready", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Durable adapter migration/query readiness still blocks M4/M5 promotion." + }, + { + id: "m3-trusted-loop-ready", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "M3 trusted-loop live operation evidence is still required." + } + ]; + return { + status: blockers.length === 0 ? "pass" : "blocked", + green: blockers.length === 0, + checks + }; +} + +function buildCurrentDevLayering() { + return { + publicRoute: { + label: "DEV public route", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Repository-stored route snapshots were removed; only live checks or issue/PR comments may establish current route state.", + nextRequired: "Run read-only route checks with /tmp, .state, or CI artifact output." }, 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}; endpointSource=${cloudDb.endpointSource}; 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." + label: "Cloud API DB live", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Deleted repository report files cannot be used as DB-live acceptance evidence.", + nextRequired: "Rerun the current read-only health check and record the conclusion in GitHub." }, - runtimeDurableAdapter: { - label: "Runtime durable adapter", - status: runtimeDurability.ready ? "pass" : "blocked", - evidenceLevel: runtimeDurability.ready ? "DEV-LIVE" : "BLOCKED", - summary: `runtime adapter=${runtimeDurability.adapter}; durableRequested=${runtimeDurability.durableRequested}; durable=${runtimeDurability.durable}; ready=${runtimeDurability.ready}; blocker=${runtimeDurability.blocker}; migration=${runtimeDurability.migration.requiredMigrationId} checked=${runtimeDurability.migration.checked ?? "unknown"} ready=${runtimeDurability.migration.ready ?? "unknown"}; queryResult=${runtimeDurability.queryResult}.`, - evidence: [ - `requiredEvidence=${runtimeDurability.requiredEvidence}`, - `blockedLayer=${runtimeDurability.blockedLayer ?? "unknown"}`, - `migration=${runtimeDurability.migration.requiredMigrationId}`, - `queryAttempted=${runtimeDurability.queryAttempted ?? "unknown"}` - ], - nextRequired: "Repair the postgres durable runtime adapter query/migration readiness and rerun health/M4 preflight without treating DB live as durability proof." + runtimeDurability: { + label: "Runtime durability", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Durable runtime adapter readiness remains the current M4/M5 blocker.", + nextRequired: "Prove schema, migration, and read-query readiness." }, - agentSkillsInjection: { - label: "Agent skills injection", - status: skillsInjection.status, - evidenceLevel: skillsInjection.status === "pass" ? "DEV-LIVE" : "BLOCKED", - summary: skillsInjection.summary, - evidence: [ - `skills.commit=${skillsInjection.serviceCommitId ?? "missing"}`, - `skills.version=${skillsInjection.serviceVersion ?? "missing"}`, - `worker.commit=${skillsInjection.workerDryRunCommitId ?? "missing"}`, - `worker.version=${skillsInjection.workerDryRunVersion ?? "missing"}`, - `missing=${skillsInjection.missing.join(",") || "none"}` - ], - nextRequired: "Inject explicit skill commit and version into hwlab-agent-mgr, hwlab-agent-skills, and scheduled worker jobs before claiming M4/M5 agent evidence." + skillsInjection: { + label: "Skills commit/version injection", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "Skills commit/version visibility remains required before agent-loop promotion.", + nextRequired: "Expose skills commit and version through live DEV manager/worker health." }, - codeAgentProvider: { - label: "Code Agent provider", - status: provider.ready ? "pass" : "blocked", - evidenceLevel: provider.ready ? "DEV-LIVE" : "BLOCKED", - summary: `code-agent provider=${provider.provider}; status=${provider.status}; ready=${provider.ready}.`, - evidence: [`provider=${provider.provider}`, `status=${provider.status}`], - nextRequired: "Keep provider-backed Code Agent readiness separate from DB live and runtime durable adapter readiness." - }, - d601RunnerObservability: { - label: "D601 runner observability", - status: reports.d601Observability.runnerKubeconfigReadable === false - ? "blocked" - : reports.d601Observability.cluster?.readable === true - ? "pass" - : "blocked", - evidenceLevel: reports.d601Observability.cluster?.readable === true || - reports.d601Observability.d601PublicEndpointsReachable === true - ? "DEV-LIVE" - : "BLOCKED", - summary: d601ObservabilitySummary(reports.d601Observability), - evidence: d601ObservationSummary(reports.d601Observability), - nextRequired: "Treat #46 runner kubeconfig/readonly gaps separately from D601 service health; rerun read-only observability after the mount or permission path is repaired." - }, - 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, runtime durable adapter, skills injection, and required 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." + m3TrustedLoop: { + label: "M3 trusted loop", + status: "blocked", + evidenceLevel: "BLOCKED", + summary: "M3 must prove the real trusted loop through HWLAB runtime, not a generated file.", + nextRequired: "Capture operation, trace, audit, and evidence identifiers in the owning issue." } }; } -function buildMilestoneBlockerClassification(reports) { - const cloudDb = cloudApiDbStatus(reports); - const runtimeDurability = runtimeDurabilityStatus(reports); - const skillsInjection = m4SkillsInjectionStatus(reports); - const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status); - const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status); - const m3ServiceDiscoveryBlocked = reports.devM3Hardware.blockers?.some((blocker) => - blocker.scope === "m3-service-discovery" || blocker.scope === "m3-direct-target-missing" - ) === true; - const m3IdentityBlocked = reports.devM3Hardware.blockers?.some((blocker) => - blocker.scope === "m3-box-simu-identity" || blocker.scope === "m3-gateway-simu-identity" - ) === true; - const m3WiringBlocked = reports.devM3Hardware.blockers?.some((blocker) => - blocker.scope === "m3-patch-panel-wiring" - ) === true; - const m3BlockerClass = m3Live - ? "cleared" - : m3ServiceDiscoveryBlocked - ? "direct-target-missing" - : m3IdentityBlocked - ? "direct-target-identity-gap" - : m3WiringBlocked - ? "patch-panel-m3-wiring-missing" - : "hardware-loop-runtime"; +function buildMilestones() { + return [ + { + id: "M0", + status: "pass", + highestVisibleLevel: "SOURCE", + liveEvidence: false, + summary: "Source contracts remain parseable." + }, + { + id: "M1", + status: "pass", + highestVisibleLevel: "LOCAL", + liveEvidence: false, + summary: "Local smoke remains source/local only." + }, + { + id: "M2", + status: "blocked", + highestVisibleLevel: "BLOCKED", + liveEvidence: false, + summary: "M2 live route and artifact conclusions must be rerun without repository report outputs." + }, + { + id: "M3", + status: "blocked", + highestVisibleLevel: "BLOCKED", + liveEvidence: false, + summary: "M3 trusted-loop live evidence remains required." + }, + { + id: "M4", + status: "blocked", + highestVisibleLevel: "BLOCKED", + liveEvidence: false, + summary: "M4 remains blocked by durable runtime readiness and skills injection." + }, + { + id: "M5", + status: "blocked", + highestVisibleLevel: "BLOCKED", + liveEvidence: false, + summary: "M5 cannot promote until M3/M4 blockers clear and current live checks are recorded outside the repo." + } + ]; +} - const m4RuntimeDurableBlocked = reports.devM4Agent.blockers?.some((blocker) => - blocker.scope === "runtime-durable-adapter" - ) === true || (!m4Live && runtimeDurability.durableRequested && !runtimeDurability.ready); - const m4SkillsInjectionBlocked = skillsInjection.status === "blocked"; - const m4DbLiveBlocked = reports.devM4Agent.blockers?.some((blocker) => - blocker.scope === "db-live" - ) === true; - const m4BlockerClass = m4Live - ? "cleared" - : m4RuntimeDurableBlocked - ? runtimeDurability.blocker - : m4SkillsInjectionBlocked - ? "skills-commit-version-injection" - : m4DbLiveBlocked - ? "db-live-readiness" - : "agent-loop-live-preflight"; +function buildMilestoneLevelClassification(milestones) { + return milestones.map((milestone) => ({ + milestone: milestone.id, + currentLevel: milestone.highestVisibleLevel, + status: milestone.status, + strongestEvidenceLevel: milestone.highestVisibleLevel, + liveEvidence: milestone.liveEvidence, + summary: milestone.summary + })); +} +function buildMilestoneBlockerClassification() { return [ { milestone: "M3", - status: m3Live ? "pass" : "blocked", - currentLevel: m3Live ? "DEV-LIVE" : "BLOCKED", - blockerClass: m3BlockerClass, - dependency: m3ServiceDiscoveryBlocked - ? "Direct DEV simulator and patch-panel targets must be discoverable before the real trusted loop can be observed." - : m3IdentityBlocked - ? "Two distinct live box-simu resources and two distinct gateway-simu identities are required before M3 can run." - : m3WiringBlocked - ? "The callable DEV patch-panel must actively carry res_boxsimu_1:DO1 -> res_boxsimu_2:DI1 before the write/read operation can run." - : "Real DEV trusted loop through res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1.", - 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"}`, - `runnerKubeconfigReadable=${reports.devM3Hardware.d601Observability?.runnerKubeconfigReadable === true}`, - `d601PublicEndpointsReachable=${reports.devM3Hardware.d601Observability?.d601PublicEndpointsReachable === true}`, - `d601K3sUnavailable=${reports.devM3Hardware.d601Observability?.d601K3sUnavailable === true}`, - `directTargetSource=${reports.devM3Hardware.directTargetDiscovery?.source ?? "not_observed"}`, - `directTargetCounts=${JSON.stringify(reports.devM3Hardware.directTargetDiscovery?.counts ?? {})}`, - `patchPanelM3Wiring=${reports.devM3Hardware.directTargetDiscovery?.patchPanelWiring?.hasRequiredConfiguredConnection === true}` - ], - nextRequired: m3ServiceDiscoveryBlocked - ? "Expose or document callable DEV direct targets for both simulators and the patch-panel, then rerun the read-only M3 preflight." - : m3IdentityBlocked - ? "Fix DEV simulator instance identity so two box-simu pods report res_boxsimu_1/res_boxsimu_2 and two gateway-simu pods report distinct gateway identities." - : m3WiringBlocked - ? "Load/apply DEV patch-panel wiring for res_boxsimu_1:DO1 -> res_boxsimu_2:DI1, then rerun the bounded DEV M3 smoke." - : "Run the bounded DEV M3 live smoke only after patch-panel topology can prove operation, trace, audit, and evidence IDs.", - nonPromotionReason: "Public frontend, route, and artifact evidence do not prove the required hardware loop." + status: "blocked", + currentLevel: "BLOCKED", + blockerClass: "m3-hardware-loop-runtime", + dependency: "M3 trusted hardware loop operation, trace, audit, and evidence identifiers", + nonPromotionReason: "M3 live trusted-loop evidence is not currently established after repository report removal.", + nextRequired: "Repair topology and rerun the live M3 trusted-loop smoke with non-repository output." }, { milestone: "M4", - status: m4Live ? "pass" : "blocked", - currentLevel: m4Live ? "DEV-LIVE" : "BLOCKED", - blockerClass: m4BlockerClass, - dependency: m4RuntimeDurableBlocked - ? "Cloud API /health/live must prove the postgres durable runtime adapter schema, 0001_cloud_core_skeleton migration ledger, and read-query readiness before live agent scheduling/evidence closure." - : m4SkillsInjectionBlocked - ? "Agent manager, skills service, and worker jobs must expose explicit skill commit and version fields before M4 live evidence can be trusted." - : "Cloud API /health/live must report DB ready=true, connected=true, endpointSource=secret-url-host, and liveDbEvidence=true before live agent scheduling/evidence closure.", - evidence: [ - `db.status=${cloudDb.status}`, - `db.ready=${cloudDb.ready}`, - `db.connected=${cloudDb.connected}`, - `db.endpointSource=${cloudDb.endpointSource}`, - `db.liveDbEvidence=${cloudDb.liveDbEvidence}`, - `runtime.adapter=${runtimeDurability.adapter}`, - `runtime.blocker=${runtimeDurability.blocker}`, - `runtime.migration=${runtimeDurability.migration.requiredMigrationId}`, - `runtime.migration.ready=${runtimeDurability.migration.ready ?? "unknown"}`, - `runtime.queryResult=${runtimeDurability.queryResult}`, - `skills.missing=${skillsInjection.missing.join(",") || "none"}` - ], - nextRequired: m4RuntimeDurableBlocked - ? "Repair the runtime durable adapter query/migration readiness and rerun the M4 live preflight without scheduling a DEV agent task before preconditions pass." - : m4SkillsInjectionBlocked - ? "Inject skill commit/version into agent manager, skills service, and worker jobs, then rerun the M4 read-only/live preflight." - : "Repair DB live readiness and rerun the M4 live preflight without scheduling a DEV agent task before preconditions pass.", - nonPromotionReason: "Frontend revision and read-only route reachability do not prove DB-backed durable agent runtime readiness or skill version injection." + status: "blocked", + currentLevel: "BLOCKED", + blockerClass: "runtime_durable_adapter_query_blocked", + dependency: "durable runtime adapter readiness and skill commit and version injection", + nonPromotionReason: "M4 cannot promote without durable agent runtime readiness and skill version injection.", + nextRequired: "Repair runtime durability and skills metadata, then rerun M4 read-only preflight." }, { milestone: "M5", status: "blocked", currentLevel: "BLOCKED", - blockerClass: m4RuntimeDurableBlocked - ? "composite-runtime-durable-m3-m4-live" - : m4SkillsInjectionBlocked - ? "composite-skills-injection-m3-m4-live" - : "composite-db-m3-m4-live", - dependency: "M5 needs DB live readiness, runtime durable adapter readiness, skills commit/version injection, M3 trusted-loop DEV evidence, M4 live preflight/evidence closure, and current source/artifact coverage.", - evidence: [ - "M5 dry-run is green", - `db.ready=${cloudDb.ready}`, - `runtime.blocker=${runtimeDurability.blocker}`, - `skills.status=${skillsInjection.status}`, - `m3.live=${m3Live}`, - `m4.live=${m4Live}` - ], - nextRequired: "After DB/runtime durable adapter/skills/M3/M4 blockers are cleared, run only the bounded DEV MVP live gate command with explicit DEV/non-PROD confirmations.", - nonPromotionReason: "No frontend, route-only, local, or dry-run evidence is allowed to stand in for bounded DEV-LIVE MVP e2e acceptance." + blockerClass: "m3-m4-current-blockers", + dependency: "runtime durable adapter readiness, skills commit/version injection, and M3 trusted-loop evidence", + nonPromotionReason: "M5 cannot promote while M3/M4 blockers remain open and current evidence is not recorded in GitHub.", + nextRequired: "Clear M3/M4 blockers and post live DEV acceptance conclusions to the owning issues or PR." } ]; } -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 buildEvidence() { + return [ + sourceEntry({ + milestone: "M0", + issue: issue(31), + category: "contract", + level: "SOURCE", + status: "pass", + sources: ["protocol/README.md", "docs/dev-acceptance-matrix.md"], + commands: ["node scripts/validate-contract.mjs"], + summary: "Frozen contracts remain the source-ready baseline." + }), + sourceEntry({ + milestone: "M1", + issue: issue(7), + category: "local-smoke", + level: "LOCAL", + status: "pass", + sources: ["scripts/m1-contract-smoke.mjs"], + commands: ["node scripts/m1-contract-smoke.mjs"], + summary: "Local skeleton smoke remains local-only evidence." + }), + sourceEntry({ + milestone: "M5", + issue: issue(411), + category: "repository-governance", + level: "SOURCE", + status: "pass", + sources: ["scripts/repo-reports-guard.mjs", "package.json", "CI.json"], + commands: ["node scripts/repo-reports-guard.mjs"], + summary: "Repository report files are removed and guarded against recurrence." + }) + ]; } function buildNextSteps(blockers) { - const byOrder = new Map(); - for (const blocker of blockers) { - if (!byOrder.has(blocker.unblockOrder)) { - byOrder.set(blocker.unblockOrder, []); - } - byOrder.get(blocker.unblockOrder).push(blocker); - } - - return [...byOrder.entries()] - .sort(([a], [b]) => a - b) - .map(([blockerOrder, items], index) => { - const first = items[0]; - return { - order: index + 1, - blockerOrder, - priority: first.priority, - scopes: [...new Set(items.map((item) => item.scope))], - sourceIssues: [...new Set(items.flatMap((item) => [item.sourceIssue, ...item.unblocks].filter(Boolean)))].sort(), - rationale: first.rationale, - action: actionForBlockerOrder(blockerOrder, items), - evidenceRequired: evidenceRequiredFor(first.scope) - }; - }); -} - -function actionForBlockerOrder(blockerOrder, items) { - if (blockerOrder === 2) { - const scopes = items.map((item) => item.scope).sort().join(", "); - return `Add real runtime entrypoints or dedicated Dockerfiles for ${scopes}, then rerun the artifact publish preflight.`; - } - return items.find((item) => item.nextTask)?.nextTask || fallbackAction(items[0].scope); -} - -function fallbackAction(scope) { - if (scope === "base-image") return "Preload or tag node:20-bookworm-slim and rerun the base-image and artifact publish preflights."; - 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 Secret URL host connectivity, then 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 === "runtime-durable-adapter") return "Repair DEV cloud-api postgres durable runtime adapter schema/migration/read-query readiness, then rerun /health/live and the M4 preflight without exposing secret values."; - if (scope === "skills-commit-version-injection" || scope === "agent-mgr-health") return "Inject explicit skills commit and version into agent manager, skills service, and worker jobs, then rerun the M4 read-only/live preflight."; - if (scope === "m3-patch-panel-wiring") return "Load/apply DEV patch-panel wiring for res_boxsimu_1:DO1 -> res_boxsimu_2:DI1, then rerun the bounded DEV M3 smoke."; - if (scope === "m3-box-simu-identity") return "Fix DEV box-simu instance identity so direct endpoints expose distinct res_boxsimu_1 and res_boxsimu_2 resources."; - if (scope === "m3-gateway-simu-identity") return "Fix DEV gateway-simu instance identity so direct endpoints expose two distinct gateway sessions."; - 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 === "runner-kubeconfig-readonly-gap" || scope === "m3-service-discovery" || scope === "m3-direct-target-missing") return "Repair the #46 runner kubeconfig mount/permission or document the approved alternate read-only KUBECONFIG/service-discovery path; do not classify this as D601 global offline."; - 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."; -} - -function evidenceRequiredFor(scope) { - if (scope === "base-image") return "Base-image preflight status=ready with approved Node 20 builder base and no UniDesk/runtime substitute."; - if (scope === "hwlab-router" || scope === "hwlab-tunnel-client" || scope === "hwlab-edge-proxy") return "Real repo entrypoints or dedicated Dockerfiles for route services, followed by artifact preflight."; - 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, endpointSource=secret-url-host, redacted secret references, and no secret material."; - if (scope === "db-live") return "Cloud API /health/live output with ready=true, connected=true, endpointSource=secret-url-host, liveDbEvidence=true, and redacted secret references."; - if (scope === "runtime-durable-adapter") return "Cloud API /health/live output with runtime.adapter=postgres, durable=true, ready=true, liveRuntimeEvidence=true, migration 0001_cloud_core_skeleton ready, and read-query readiness passing."; - if (scope === "skills-commit-version-injection" || scope === "agent-mgr-health") return "Read-only hwlab-agent-mgr/hwlab-agent-skills health plus worker dry-run manifest showing skillCommitId/skillVersion and HWLAB_SKILL_COMMIT_ID/HWLAB_SKILL_VERSION injected."; - if (scope === "m3-patch-panel-wiring") return "Read-only patch-panel /status and /wiring showing active res_boxsimu_1:DO1 -> res_boxsimu_2:DI1 before a bounded write/read smoke records operation, trace, audit, and evidence IDs."; - if (scope === "m3-box-simu-identity") return "Read-only direct box-simu /health/live and /status output showing distinct res_boxsimu_1 and res_boxsimu_2 resources."; - if (scope === "m3-gateway-simu-identity") return "Read-only direct gateway-simu /health/live and /status output showing two distinct gateway identities/sessions."; - 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 === "runner-kubeconfig-readonly-gap" || scope === "m3-service-discovery" || scope === "m3-direct-target-missing") return "Read-only report with runnerKubeconfigReadable, runnerKubeconfigProbeExitCode/stderr, d601PublicEndpointsReachable, and d601K3sUnavailable recorded separately, plus direct M3 service target discovery before any DO write."; - 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."; -} - -function validateReport(report) { - assert.equal(report.reportVersion, "v2", "reportVersion"); - assert.equal(report.issue, issue(58), "issue"); - assert.equal(report.environment, ENVIRONMENT_DEV, "environment"); - assert.equal(report.endpoint, DEV_ENDPOINT, "endpoint"); - assert.equal(report.frontendEndpoint, DEV_FRONTEND_ENDPOINT, "frontendEndpoint"); - assert.equal(report.activeEndpoints.frontend, activeBrowserRoot, "activeEndpoints.frontend"); - assert.equal(report.activeEndpoints.apiLive, activeApiLiveEndpoint, "activeEndpoints.apiLive"); - assert.equal(report.devOnly, true, "devOnly"); - assert.equal(report.prodDisabled, true, "prodDisabled"); - assert.deepEqual(Object.keys(report.levels), [...levels], "level keys"); - assert.equal(report.latestFrontendDevFact.revision, latestFrontendRevision, "latestFrontendDevFact.revision"); - assert.equal(report.latestFrontendDevFact.promotesM3M4M5, false, "frontend fact cannot promote M3/M4/M5"); - assert.deepEqual( - report.milestoneLevelClassification.map((item) => item.milestone), - ["M0", "M1", "M2", "M3", "M4", "M5"], - "milestoneLevelClassification" - ); - assert.deepEqual( - report.milestoneBlockerClassification.map((item) => item.milestone), - ["M3", "M4", "M5"], - "milestoneBlockerClassification" - ); - assert.ok(report.blockers.length >= 1, "must remain blocked without DEV-LIVE evidence"); - assert.equal(report.overall.status, "blocked", "overall status"); - assert.ok(report.nextSteps.length >= 1, "next steps required"); - for (const blocker of report.blockers) { - assert.ok(blockerTypes.has(blocker.type), `unknown blocker type ${blocker.type}`); - assert.match(blocker.priority, /^P[0-3]$/u, `invalid priority ${blocker.priority}`); - assert.equal(blocker.status, "open", "blocker status"); - } + return blockers.map((blocker, index) => ({ + order: index + 1, + action: blocker.nextTask, + evidenceRequired: "Post the real operational conclusion in the owning GitHub issue or PR; do not commit generated report files." + })); } export async function buildReport() { - const entries = await Promise.all( - Object.entries(reportPaths).map(async ([key, relativePath]) => { - const report = await readJSON(relativePath); - return [key, { ...report, path: relativePath }]; - }) - ); - const reports = Object.fromEntries(entries); - const evidence = [ - ...sourceContracts.map(sourceEntry), - ...collectM2Evidence(reports), - ...collectM3Evidence(reports), - ...collectM4Evidence(reports), - ...collectM5Evidence(reports) + const blockers = [ + governanceBlocker(), + runtimeDurableBlocker(), + skillsBlocker(), + hardwareLoopBlocker() ]; - const blockers = collectBlockers(reports); - const milestones = deriveMilestones(evidence, blockers); + const milestones = buildMilestones(); + const evidence = buildEvidence(); + const dod = buildDoD(blockers); + const currentDevLayering = buildCurrentDevLayering(); const milestoneLevelClassification = buildMilestoneLevelClassification(milestones); - const dod = buildDoD(reports, milestones, blockers); - const currentDevLayering = buildCurrentDevLayering(reports, blockers); - const milestoneBlockerClassification = buildMilestoneBlockerClassification(reports); - const nextSteps = buildNextSteps(blockers); + const milestoneBlockerClassification = buildMilestoneBlockerClassification(); const report = { "$schema": "https://hwlab.pikastech.local/schemas/dev-m5-gate-aggregator-v2.schema.json", - "$id": "https://hwlab.pikastech.local/reports/dev-gate/dev-m5-gate-aggregator-v2.json", + "$id": "https://hwlab.pikastech.local/dev-m5-gate-aggregator-v2.json", reportVersion: "v2", reportKind: "dev-m5-gate-aggregator", issue: issue(58), - 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)], + supports: [issue(7), issue(9), issue(23), issue(31), issue(37), issue(38), issue(39), issue(58), issue(411)], generatedAt: new Date().toISOString(), generatedFromCommit: gitCommit(), environment: ENVIRONMENT_DEV, endpoint: DEV_ENDPOINT, frontendEndpoint: DEV_FRONTEND_ENDPOINT, activeEndpoints: { - frontend: activeBrowserRoot, - apiLive: activeApiLiveEndpoint + frontend: `${DEV_FRONTEND_ENDPOINT}/`, + apiLive: `${DEV_ENDPOINT}/health/live` }, - deprecatedEndpoints: deprecatedLegacyPublicEndpoints.map((endpoint) => ({ - endpoint, - status: "historical/deprecated", - activeGreenEligible: false - })), devOnly: true, prodDisabled: true, reportLifecycle: activeReportLifecycle( - "Current M5 evidence aggregator report; source, local, and dry-run evidence are not DEV-LIVE acceptance." + "Current aggregator is governance-only and does not persist evidence in repository report files." ), safety: { reportOnly: true, @@ -1508,35 +384,26 @@ export async function buildReport() { noRuntimeRestart: true, noLiveProbe: true, noHeavyE2E: true, + noRepoReportDirectory: true, noUniDeskRuntimeSubstitute: true }, - sourceReports: Object.fromEntries( - Object.entries(reports).map(([key, report]) => [ - key, - { - path: report.path, - issue: report.issue, - taskId: report.taskId ?? report.reportKind, - lifecycleState: reportLifecycleState(report), - status: primaryStatus(report), - commitId: short(report.commitId ?? report.target?.commitId) - } - ]) - ), + sourceReports: { + status: "removed", + policy: "Generated progress and acceptance snapshots are not source files. Use GitHub issue or PR comments plus temporary /tmp, .state, or CI artifact output." + }, overall: { - status: dod.status, - green: dod.green, - reason: dod.green - ? "All #9 DEV DoD checks are green." - : `Frontend revision ${latestFrontendRevision} is usable in degraded/read-only mode and EDGE/ROUTE DEV-LIVE evidence exists on :16666/:16667, but M5 remains blocked by artifact source drift, runtime durable adapter or skills injection readiness, missing M3 trusted loop operation evidence, and blocked M4 agent-loop preflight.` + status: "blocked", + green: false, + reason: "The old repository report mechanism has been removed. M3/M4/M5 still require real DEV checks and GitHub-hosted conclusions before any green acceptance claim." }, latestFrontendDevFact: { - ...frontendDevFact, - issue: issue(99), - supports: [issue(99), issue(108), issue(78)], - endpoint: activeBrowserRoot, - evidenceLevel: "DEV-LIVE", - promotesM3M4M5: false + revision: "not_claimed_by_repo_report", + issue: issue(7), + supports: [issue(7), issue(411)], + endpoint: `${DEV_FRONTEND_ENDPOINT}/`, + evidenceLevel: "BLOCKED", + promotesM3M4M5: false, + summary: "Frontend visibility must be established by current live checks or issue/PR comments, not by committed report snapshots." }, dod, currentDevLayering, @@ -1546,13 +413,30 @@ export async function buildReport() { evidence, levels: groupByLevel(evidence, blockers), blockers, - nextSteps, + nextSteps: buildNextSteps(blockers), validationCommands }; validateReport(report); return report; } +export function validateReport(report) { + assert.equal(report.reportKind, "dev-m5-gate-aggregator"); + assert.equal(report.overall.status, "blocked"); + assert.equal(report.overall.green, false); + assert.deepEqual( + report.milestoneBlockerClassification.map((item) => item.milestone), + ["M3", "M4", "M5"], + "milestoneBlockerClassification" + ); + assert.ok(report.blockers.length >= 1, "must remain blocked without live DEV acceptance"); + for (const blocker of report.blockers) { + assert.ok(blockerTypes.has(blocker.type), `unknown blocker type ${blocker.type}`); + assert.match(blocker.priority, /^P[0-3]$/u, `invalid priority ${blocker.priority}`); + assert.equal(blocker.status, "open", "blocker status"); + } +} + export function formatCheckSummary(report) { return { ok: true, @@ -1562,7 +446,6 @@ export function formatCheckSummary(report) { green: report.overall.green, blockers: report.blockers.length, activeEndpoints: report.activeEndpoints, - frontendRevision: report.latestFrontendDevFact.revision, m3m4m5: Object.fromEntries( report.milestoneBlockerClassification.map((item) => [item.milestone, item.status]) ), @@ -1577,61 +460,35 @@ 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 milestoneLevelLines = report.milestoneLevelClassification - .map((item) => `| ${item.milestone} | ${item.currentLevel} | ${item.status} | ${item.strongestEvidenceLevel} | ${item.liveEvidence} | ${item.summary} |`) - .join("\n"); - const milestoneLines = report.milestones - .map((milestone) => `| ${milestone.id} | ${milestone.status} | ${milestone.highestVisibleLevel} | ${milestone.liveEvidence} | ${milestone.summary} |`) - .join("\n"); - const dodLines = report.dod.checks - .map((check) => `| ${check.id} | ${check.status} | ${check.evidenceLevel} | ${check.summary} |`) - .join("\n"); - const blockerClassificationLines = report.milestoneBlockerClassification + const milestoneLines = report.milestoneBlockerClassification .map((item) => `| ${item.milestone} | ${item.status} | ${item.currentLevel} | ${item.blockerClass} | ${item.dependency} | ${item.nextRequired} |`) .join("\n"); const blockerLines = report.blockers .map((blocker) => `| ${blocker.priority} | ${blocker.unblockOrder} | ${blocker.type} | ${blocker.scope} | ${blocker.summary} |`) .join("\n"); - const nextLines = report.nextSteps - .map((step) => `${step.order}. ${step.action}\n Evidence required: ${step.evidenceRequired}`) + const dodLines = report.dod.checks + .map((check) => `| ${check.id} | ${check.status} | ${check.evidenceLevel} | ${check.summary} |`) .join("\n"); return `# HWLAB M5 DEV Gate Aggregator v2 Status: ${report.overall.status} Generated from: \`${report.generatedFromCommit}\` -Scope: DEV only, report-only +Scope: DEV only, no repository report output Active frontend: \`${report.activeEndpoints.frontend}\` Active API/live: \`${report.activeEndpoints.apiLive}\` -Deprecated public endpoints: ${report.deprecatedEndpoints.map((item) => `\`${item.endpoint}\``).join(", ")} ## Summary ${report.overall.reason} -## Frontend DEV Fact - -The latest #99/#108 frontend DEV visibility fact is revision \`${report.latestFrontendDevFact.revision}\` at \`${report.latestFrontendDevFact.endpoint}\`. It means the deployed UI is usable in degraded/read-only mode only; this is ${report.latestFrontendDevFact.evidenceLevel} browser/frontend evidence and does not promote M3, M4, or M5. - ## Current DEV Layering | Layer | Status | Evidence level | Current conclusion | Required next proof | | --- | --- | --- | --- | --- | ${layeringLines} -## M0-M5 Level Classification - -| Milestone | Current classification | Status | Strongest evidence | Live evidence | Summary | -| --- | --- | --- | --- | --- | --- | -${milestoneLevelLines} - -## Milestones - -| Milestone | Status | Highest visible level | Live evidence | Summary | -| --- | --- | --- | --- | --- | -${milestoneLines} - -## #9 DoD Checks +## DoD Checks | Check | Status | Evidence level | Summary | | --- | --- | --- | --- | @@ -1641,7 +498,7 @@ ${dodLines} | Milestone | Status | Current level | Blocker class | Dependency | Required next proof | | --- | --- | --- | --- | --- | --- | -${blockerClassificationLines} +${milestoneLines} ## Blockers @@ -1649,16 +506,12 @@ ${blockerClassificationLines} | --- | ---: | --- | --- | --- | ${blockerLines} -## Next Unblock Order - -${nextLines} - ## Validation ${report.validationCommands.map((command) => `- \`${command}\``).join("\n")} ## Boundary -This report reads committed reports and fixtures only. It does not deploy, call DEV, call PROD, read secrets, restart runtime, run heavy e2e, or substitute UniDesk runtime for HWLAB runtime. +This aggregator is governance-only. It reads source contracts, does not deploy, does not call DEV or PROD, does not read secrets, and does not persist generated evidence in the repository. `; } diff --git a/scripts/src/dev-evidence-blocker-aggregator.test.mjs b/scripts/src/dev-evidence-blocker-aggregator.test.mjs index 4a682a09..7e820a25 100644 --- a/scripts/src/dev-evidence-blocker-aggregator.test.mjs +++ b/scripts/src/dev-evidence-blocker-aggregator.test.mjs @@ -3,7 +3,7 @@ import test from "node:test"; import { buildReport } from "./dev-evidence-blocker-aggregator.mjs"; -test("aggregator reports M4 current blocker as durable runtime or skills injection, not stale DB live", async () => { +test("aggregator no longer treats deleted repository reports as live evidence", async () => { const report = await buildReport(); const m4 = report.milestoneBlockerClassification.find((item) => item.milestone === "M4"); const m5 = report.milestoneBlockerClassification.find((item) => item.milestone === "M5"); @@ -12,22 +12,19 @@ test("aggregator reports M4 current blocker as durable runtime or skills injecti assert.ok(m4, "M4 blocker classification is present"); assert.ok(m5, "M5 blocker classification is present"); - assert.equal(dbLayer.status, "pass"); - assert.equal(dbLayer.evidenceLevel, "DEV-LIVE"); - assert.equal(dbCheck.status, "pass"); - assert.equal(dbCheck.evidenceLevel, "DEV-LIVE"); + assert.equal(report.overall.status, "blocked"); + assert.equal(report.sourceReports.status, "removed"); + assert.equal(dbLayer.status, "blocked"); + assert.equal(dbLayer.evidenceLevel, "BLOCKED"); + assert.equal(dbCheck.status, "blocked"); + assert.equal(dbCheck.evidenceLevel, "BLOCKED"); assert.equal( - report.blockers.some((blocker) => - ["cloud-api-db", "cloud-api-db-health-gate", "cloud-api-db-live", "db-live"].includes(blocker.scope) - ), - false, - "DB live blockers must be stale once current DB live evidence is pass" + report.blockers.some((blocker) => blocker.scope === "repo-report-directory-removed"), + true, + "governance blocker must remain explicit" ); assert.notEqual(m4.blockerClass, "db-live-readiness"); - assert.ok( - ["runtime_durable_adapter_query_blocked", "skills-commit-version-injection"].includes(m4.blockerClass), - `unexpected M4 blocker class ${m4.blockerClass}` - ); + assert.equal(m4.blockerClass, "runtime_durable_adapter_query_blocked"); assert.match(m4.dependency, /durable runtime adapter|skill commit and version/u); assert.match(m4.nonPromotionReason, /durable agent runtime readiness|skill version injection/u); assert.match(m5.dependency, /runtime durable adapter readiness, skills commit\/version injection/u); diff --git a/scripts/src/dev-gate-preflight.mjs b/scripts/src/dev-gate-preflight.mjs index 095b0ae2..e23ef5f6 100644 --- a/scripts/src/dev-gate-preflight.mjs +++ b/scripts/src/dev-gate-preflight.mjs @@ -17,10 +17,13 @@ import { import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs"; import { probeRegistryCapabilities } from "./registry-capabilities.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const execFileAsync = promisify(execFile); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultReportPath = "reports/dev-gate/dev-preflight-report.json"; +const defaultReportPath = tempReportPath("dev-preflight-report.json"); +const defaultArtifactReportPath = tempReportPath("dev-artifacts.json"); +const defaultEdgeHealthReportPath = tempReportPath("dev-edge-health.json"); const issue = "pikasTech/HWLAB#34"; const supports = ["#7", "#12", "#14", "#22", "#23", "#29", "#30", "#31", "#33", "#35", "#39", "#43", "#48", "#49", "#57", "#66", "#143", "#164"].map( (id) => `pikasTech/HWLAB${id}` @@ -92,7 +95,7 @@ function parseArgs(argv) { } function usage() { - return "Usage: node scripts/dev-gate-preflight.mjs [--target-ref origin/main] [--report reports/dev-gate/dev-preflight-report.json] [--timeout-ms 5000] [--no-write] [--fail-on-blocked]"; + return `Usage: node scripts/dev-gate-preflight.mjs [--target-ref origin/main] [--report ${defaultReportPath}] [--timeout-ms 5000] [--no-write] [--fail-on-blocked]`; } function commandLine(command, args) { @@ -452,7 +455,7 @@ async function artifactIdentityFor({ deploy, catalog, artifactReport, targetComm publishVerified, refreshCommands: { blocked: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --blocked`, - published: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --publish-report reports/dev-gate/dev-artifacts.json` + published: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --publish-report ${defaultArtifactReportPath}` } }; } @@ -536,8 +539,8 @@ async function loadContracts() { async function loadOptionalReports() { return { - artifactPublish: await readOptionalJson("reports/dev-gate/dev-artifacts.json"), - edgeHealth: await readOptionalJson("reports/dev-gate/dev-edge-health.json") + artifactPublish: await readOptionalJson(defaultArtifactReportPath), + edgeHealth: await readOptionalJson(defaultEdgeHealthReportPath) }; } @@ -655,8 +658,8 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit reporter.block({ type: "runtime_blocker", scope: "dev-artifact-publish", - summary: "reports/dev-gate/dev-artifacts.json is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.", - nextTask: "Run the DEV artifact publish workflow and record reports/dev-gate/dev-artifacts.json before real deployment." + summary: `${defaultArtifactReportPath} is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.`, + nextTask: `Run the DEV artifact publish workflow and keep its temporary JSON at ${defaultArtifactReportPath}; do not commit report output.` }); return; } @@ -716,7 +719,7 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit reporter.block({ type: "runtime_blocker", scope: "dev-artifact-publish", - 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}.`, + summary: `${defaultArtifactReportPath} 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 artifact source commit, or prove the target commit has no artifact build input changes; keep disabled services marked not_published with reasons." }); } @@ -775,8 +778,8 @@ function validateEdgeHealthReport(reporter, edgeReport) { reporter.block({ type: "network_blocker", scope: "dev-edge-health-report", - summary: "reports/dev-gate/dev-edge-health.json is missing, so the preflight has no committed edge/frp health evidence.", - nextTask: "Run the read-only DEV edge health smoke and commit reports/dev-gate/dev-edge-health.json before real deployment." + summary: `${defaultEdgeHealthReportPath} is missing, so the preflight has no temporary edge/frp health JSON.`, + nextTask: `Run the read-only DEV edge health smoke and keep its temporary JSON at ${defaultEdgeHealthReportPath}; do not commit report output.` }); return; } @@ -785,7 +788,7 @@ function validateEdgeHealthReport(reporter, edgeReport) { const pass = edgeReport.status === "pass" && edgeHealth?.status === "pass"; const evidence = [ { - report: "reports/dev-gate/dev-edge-health.json", + report: defaultEdgeHealthReportPath, status: edgeReport.status, classification: edgeHealth?.classification ?? "unknown", publicTcp: edgeHealth?.publicTcp ?? [], @@ -1133,7 +1136,7 @@ function validateRuntimeBoundary(reporter, deploy) { } async function writeReport(report, reportPath) { - const absoluteReportPath = path.join(repoRoot, reportPath); + const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "--report"); await mkdir(path.dirname(absoluteReportPath), { recursive: true }); await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`); } @@ -1145,7 +1148,7 @@ function makeReport(args, targetCommit, targetShortCommit, reporter, artifactIde return { $schema: "https://hwlab.pikastech.local/schemas/dev-gate-preflight-report.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/dev-preflight-report.json", + $id: "https://hwlab.pikastech.local/dev-gate/dev-preflight-report.json", reportVersion: "v1", reportKind: "dev-gate-preflight", issue, diff --git a/scripts/src/dev-runtime-migration.mjs b/scripts/src/dev-runtime-migration.mjs index fb0eb475..77a859e0 100644 --- a/scripts/src/dev-runtime-migration.mjs +++ b/scripts/src/dev-runtime-migration.mjs @@ -22,10 +22,11 @@ import { } from "../../internal/db/runtime-store.mjs"; import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs"; import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const migrationPath = "internal/db/migrations/0001_cloud_core_skeleton.sql"; -const defaultReportPath = "reports/dev-gate/dev-runtime-migration-report.json"; +const defaultReportPath = tempReportPath("dev-runtime-migration-report.json"); const issue = "pikasTech/HWLAB#164"; export async function runDevRuntimeMigrationCli(argv = process.argv.slice(2), options = {}) { @@ -167,15 +168,16 @@ export function parseArgs(argv = []) { else if (arg === "--allow-live-db-read") args.allowLiveDbRead = true; else if (arg === "--confirm-dev") args.confirmDev = true; else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true; - else if (arg === "--write-report") args.writeReport = true; else if (arg === "--pretty") args.pretty = true; else if (arg === "--fail-on-blocked") args.failOnBlocked = true; else if (arg === "--help" || arg === "-h") args.help = true; else if (arg === "--report") { index += 1; args.reportPath = requireArgValue(argv[index], "--report"); + args.writeReport = true; } else if (arg.startsWith("--report=")) { args.reportPath = requireArgValue(arg.slice("--report=".length), "--report"); + args.writeReport = true; } else { throw new Error(`unknown argument: ${arg}`); } @@ -651,7 +653,7 @@ function isMigrationSslError(error) { } async function writeReport(report, reportPath, root) { - const absolute = path.resolve(root, reportPath); + const absolute = ensureNotRepoReportsPath(root, reportPath, "--report"); await mkdir(path.dirname(absolute), { recursive: true }); await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/src/dev-runtime-postflight.mjs b/scripts/src/dev-runtime-postflight.mjs index bc15c827..d738c348 100644 --- a/scripts/src/dev-runtime-postflight.mjs +++ b/scripts/src/dev-runtime-postflight.mjs @@ -9,9 +9,10 @@ import { buildM3IoControlSourceReport, runM3IoControlLiveReport } from "./m3-io-control-e2e.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultReportPath = "reports/dev-gate/dev-runtime-postflight-report.json"; +const defaultReportPath = tempReportPath("dev-runtime-postflight-report.json"); const issue = "pikasTech/HWLAB#311"; const defaultHealthUrl = `${DEV_ENDPOINT}/health/live`; const defaultV1Url = `${DEV_ENDPOINT}/v1`; @@ -149,7 +150,6 @@ export function parseArgs(argv = []) { else if (arg === "--live") args.live = true; else if (arg === "--confirm-dev") args.confirmDev = true; else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true; - else if (arg === "--write-report") args.writeReport = true; else if (arg === "--pretty") args.pretty = true; else if (arg === "--fail-on-blocked") args.failOnBlocked = true; else if (arg === "--target") { @@ -161,8 +161,10 @@ export function parseArgs(argv = []) { } else if (arg === "--report") { index += 1; args.reportPath = requireArgValue(argv[index], "--report"); + args.writeReport = true; } else if (arg.startsWith("--report=")) { args.reportPath = requireArgValue(arg.slice("--report=".length), "--report"); + args.writeReport = true; } else if (arg === "--help" || arg === "-h") { args.help = true; } else { @@ -604,7 +606,7 @@ function sanitizeRedactedValue(value) { } async function writeReport(report, reportPath, root) { - const absolute = path.resolve(root, reportPath); + const absolute = ensureNotRepoReportsPath(root, reportPath, "--report"); await mkdir(path.dirname(absolute), { recursive: true }); await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/src/dev-runtime-provisioning.mjs b/scripts/src/dev-runtime-provisioning.mjs index 0253662e..b54da98c 100644 --- a/scripts/src/dev-runtime-provisioning.mjs +++ b/scripts/src/dev-runtime-provisioning.mjs @@ -15,9 +15,10 @@ import { buildPostgresPoolConfig } from "../../internal/db/runtime-store.mjs"; import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultReportPath = "reports/dev-gate/dev-runtime-provisioning-report.json"; +const defaultReportPath = tempReportPath("dev-runtime-provisioning-report.json"); const issue = "pikasTech/HWLAB#311"; export const DEV_DB_ADMIN_SECRET_REF = Object.freeze({ @@ -189,15 +190,16 @@ export function parseArgs(argv = []) { else if (arg === "--allow-live-db-read") args.allowLiveDbRead = true; else if (arg === "--confirm-dev") args.confirmDev = true; else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true; - else if (arg === "--write-report") args.writeReport = true; else if (arg === "--pretty") args.pretty = true; else if (arg === "--fail-on-blocked") args.failOnBlocked = true; else if (arg === "--help" || arg === "-h") args.help = true; else if (arg === "--report") { index += 1; args.reportPath = requireArgValue(argv[index], "--report"); + args.writeReport = true; } else if (arg.startsWith("--report=")) { args.reportPath = requireArgValue(arg.slice("--report=".length), "--report"); + args.writeReport = true; } else { throw new Error(`unknown argument: ${arg}`); } @@ -878,7 +880,7 @@ function isSslError(error) { } async function writeReport(report, reportPath, root) { - const absolute = path.resolve(root, reportPath); + const absolute = ensureNotRepoReportsPath(root, reportPath, "--report"); await mkdir(path.dirname(absolute), { recursive: true }); await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`); } diff --git a/scripts/src/report-paths.mjs b/scripts/src/report-paths.mjs new file mode 100644 index 00000000..b3205e12 --- /dev/null +++ b/scripts/src/report-paths.mjs @@ -0,0 +1,22 @@ +import path from "node:path"; + +export const TEMP_REPORT_ROOT = process.env.HWLAB_TEMP_REPORT_ROOT || "/tmp/hwlab-dev-gate"; +const forbiddenRepoReportDir = "reports"; + +export function tempReportPath(...segments) { + return path.join(TEMP_REPORT_ROOT, ...segments); +} + +export function resolveOutputPath(root, outputPath) { + return path.isAbsolute(outputPath) ? outputPath : path.resolve(root, outputPath); +} + +export function ensureNotRepoReportsPath(root, outputPath, label = "report path") { + const absoluteRoot = path.resolve(root); + const absoluteOutput = resolveOutputPath(absoluteRoot, outputPath); + const relative = path.relative(absoluteRoot, absoluteOutput).split(path.sep).join("/"); + if (relative === forbiddenRepoReportDir || relative.startsWith(`${forbiddenRepoReportDir}/`)) { + throw new Error(`${label} must not write under the forbidden repository report directory; use ${TEMP_REPORT_ROOT} or .state instead`); + } + return absoluteOutput; +} diff --git a/scripts/src/rpt004-mvp-e2e-harness.mjs b/scripts/src/rpt004-mvp-e2e-harness.mjs index 70713555..eea2ea6f 100644 --- a/scripts/src/rpt004-mvp-e2e-harness.mjs +++ b/scripts/src/rpt004-mvp-e2e-harness.mjs @@ -26,10 +26,12 @@ import { HWLAB_M3_IO_API_ROUTE, HWLAB_M3_IO_SKILL_NAME } from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs"; +import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const defaultJsonReportPath = "reports/dev-gate/rpt-004-mvp-e2e.json"; -const defaultMarkdownReportPath = "reports/dev-gate/rpt-004-mvp-e2e.md"; +const defaultJsonReportPath = tempReportPath("rpt-004-mvp-e2e.json"); +const defaultMarkdownReportPath = tempReportPath("rpt-004-mvp-e2e.md"); +const defaultArtifactReportPath = tempReportPath("dev-artifacts.json"); const issue = "pikasTech/HWLAB#316"; const taskId = "rpt004-mvp-e2e-harness"; const reportKind = "rpt-004-mvp-e2e-harness"; @@ -214,8 +216,6 @@ export function parseArgs(argv = []) { } else if (arg === "--markdown") { args.markdownPath = readArg(argv, ++index, arg); args.writeReport = true; - } else if (arg === "--write-report") { - args.writeReport = true; } else if (arg === "--no-write") { args.writeReport = false; } else if (arg === "--pretty") { @@ -612,7 +612,7 @@ function baseReport({ parsed, now, root }) { const liveMutationUsed = parsed.live === true && parsed.allowM3Write === true; return { $schema: "https://hwlab.pikastech.local/schemas/rpt-004-mvp-e2e-harness.schema.json", - $id: "https://hwlab.pikastech.local/reports/dev-gate/rpt-004-mvp-e2e.json", + $id: "https://hwlab.pikastech.local/dev-gate/rpt-004-mvp-e2e.json", reportVersion: "v1", reportKind, issue, @@ -677,7 +677,7 @@ function baseReport({ parsed, now, root }) { "node --test scripts/src/rpt004-mvp-e2e-harness.test.mjs", "node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write", "node scripts/rpt004-mvp-e2e-harness.mjs --live --url http://74.48.78.17:16666/ --api-url http://74.48.78.17:16667/", - "node scripts/validate-dev-gate-report.mjs reports/dev-gate/rpt-004-mvp-e2e.json" + `node scripts/validate-dev-gate-report.mjs ${defaultJsonReportPath}` ], localSmoke: { status: "not_run", @@ -743,7 +743,7 @@ async function buildExpectedArtifactState({ root, expectedCommit, fsJson }) { : (relativePath) => readJsonOptional(root, relativePath); const deploy = await readJson("deploy/deploy.json"); const catalog = await readJson("deploy/artifact-catalog.dev.json"); - const artifactReport = await readJson("reports/dev-gate/dev-artifacts.json"); + const artifactReport = await readJson(defaultArtifactReportPath); const deployCommit = sanitizeCommit(deploy?.commitId); const catalogCommit = sanitizeCommit(catalog?.commitId); const reportCommit = sanitizeCommit(artifactReport?.artifactPublish?.sourceCommitId ?? artifactReport?.commitId); @@ -1382,8 +1382,8 @@ function compactReport(report) { } async function writeReports(report, args, root) { - const jsonPath = path.resolve(root, args.reportPath); - const markdownPath = path.resolve(root, args.markdownPath); + const jsonPath = ensureNotRepoReportsPath(root, args.reportPath, "--report"); + const markdownPath = ensureNotRepoReportsPath(root, args.markdownPath, "--markdown"); report.artifacts = { jsonReportPath: relativeOrAbsolute(jsonPath, root), markdownReportPath: relativeOrAbsolute(markdownPath, root), diff --git a/scripts/src/rpt004-mvp-e2e-harness.test.mjs b/scripts/src/rpt004-mvp-e2e-harness.test.mjs index b03563a5..24754307 100644 --- a/scripts/src/rpt004-mvp-e2e-harness.test.mjs +++ b/scripts/src/rpt004-mvp-e2e-harness.test.mjs @@ -837,7 +837,7 @@ function fixtureArtifactJson() { return async (relativePath) => { if (relativePath === "deploy/deploy.json") return deploy; if (relativePath === "deploy/artifact-catalog.dev.json") return catalog; - if (relativePath === "reports/dev-gate/dev-artifacts.json") return artifactReport; + if (relativePath === "/tmp/hwlab-dev-gate/dev-artifacts.json") return artifactReport; return null; }; } diff --git a/scripts/validate-dev-gate-report-guard.test.mjs b/scripts/validate-dev-gate-report-guard.test.mjs deleted file mode 100644 index e933731b..00000000 --- a/scripts/validate-dev-gate-report-guard.test.mjs +++ /dev/null @@ -1,946 +0,0 @@ -import assert from "node:assert/strict"; -import { spawnSync } from "node:child_process"; -import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { test } from "node:test"; -import { fileURLToPath } from "node:url"; - -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const validator = path.join(repoRoot, "scripts/validate-dev-gate-report.mjs"); -const baseReportPath = path.join(repoRoot, "reports/dev-gate/dev-cloud-workbench-live.json"); -const baseDomOnlyReportPath = path.join(repoRoot, "reports/dev-gate/dev-cloud-workbench-dom-only.json"); -const baseAggregatorReportPath = path.join(repoRoot, "reports/dev-gate/dev-m5-gate-aggregator-v2.json"); -const baseReport = JSON.parse(await readFile(baseReportPath, "utf8")); -const baseDomOnlyReport = JSON.parse(await readFile(baseDomOnlyReportPath, "utf8")); -const baseAggregatorReport = JSON.parse(await readFile(baseAggregatorReportPath, "utf8")); - -function cloneBaseReport() { - return JSON.parse(JSON.stringify(baseReport)); -} - -function cloneBaseDomOnlyReport() { - return JSON.parse(JSON.stringify(baseDomOnlyReport)); -} - -function cloneBaseAggregatorReport() { - return JSON.parse(JSON.stringify(baseAggregatorReport)); -} - -function currentIdentityFields() { - return { - generatedAt: "2026-05-23T03:21:02.207Z", - sourceIdentity: { - status: "observed", - source: "git-head", - commitId: "7de6edd2c41f54e4265e822d2593fa67057fa41a", - shortCommitId: "7de6edd2c41f", - ref: "test/d601-dom-only-feedback-validation", - worktreeState: "clean", - dirty: false, - reportCommitId: "7de6edd2c41f", - summary: "Source identity was derived from the current worktree or source environment and is separate from the live runtime identity." - }, - runtimeIdentity: { - status: "observed", - source: "health-live", - endpoint: "http://74.48.78.17:16667/health/live", - serviceId: "hwlab-cloud-api", - environment: "dev", - healthStatus: "ok", - ready: true, - commitId: "7de6edd", - commitSource: "runtime-env", - imageTag: "7de6edd", - observedAt: "2026-05-23T03:21:02.207Z", - runtime: { - durable: true, - ready: true, - status: "ok", - blocker: "none", - connection: { - queryAttempted: true, - queryResult: "ok" - } - }, - readiness: { - ready: true, - status: "ok", - durability: { - ready: true, - status: "ready", - blocker: "none", - blockedLayer: "none", - queryResult: "ok" - } - }, - blockerCodes: [], - summary: "Live runtime identity was observed through the existing read-only health endpoint and is not inferred from source git HEAD." - }, - expectedRuntimeIdentity: { - status: "observed", - serviceId: "hwlab-cloud-api", - source: "deploy/artifact-catalog.dev.json+deploy/deploy.json", - commitId: "7de6edd", - imageTag: "7de6edd", - image: "127.0.0.1:5000/hwlab/hwlab-cloud-api:7de6edd", - summary: "Expected DEV runtime identity was derived from current source deploy desired state, not from live health." - }, - deploymentIdentity: { - status: "pass", - expectedCommit: "7de6edd", - expectedReportCommitId: "7de6edd2c41f", - expectedImageTag: "7de6edd", - expectedSource: "deploy/artifact-catalog.dev.json+deploy/deploy.json", - observedRuntimeCommit: "7de6edd", - observedImageTag: "7de6edd", - evidence: [ - "expected=7de6edd", - "expectedImageTag=7de6edd", - "runtimeCommit=7de6edd", - "imageTag=7de6edd" - ], - summary: "Live API runtime identity matches the current source desired runtime identity." - }, - webAssetIdentity: { - status: "pass", - assets: [ - { - path: "index.html", - status: "match", - httpStatus: 200, - contentType: "text/html; charset=utf-8", - sourceHash: "435edb7da5c539ff", - liveHash: "435edb7da5c539ff", - sourceLength: 13844, - liveLength: 13844 - }, - { - path: "styles.css", - status: "match", - httpStatus: 200, - contentType: "text/css; charset=utf-8", - sourceHash: "95574eda6090e34b", - liveHash: "95574eda6090e34b", - sourceLength: 21184, - liveLength: 21184 - }, - { - path: "app.mjs", - status: "match", - httpStatus: 200, - contentType: "text/javascript; charset=utf-8", - sourceHash: "3dd09b19fec790a4", - liveHash: "3dd09b19fec790a4", - sourceLength: 67912, - liveLength: 67912 - } - ], - mismatches: [], - summary: "Live 16666 primary web assets match the current clean source files." - } - }; -} - -function applyCurrentIdentity(report) { - const identity = currentIdentityFields(); - const apiRuntimeReadiness = { - status: "pass", - healthStatus: "ok", - ready: true, - runtimeDurable: true, - runtimeReady: true, - durabilityReady: true, - durableBlocked: false, - blockerCodes: [], - evidence: [ - "api.status=ok", - "ready=true", - "runtime.durable=true", - "runtime.ready=true", - "durability.ready=true" - ], - summary: "Live API health and runtime durability are ready for browser journey evidence." - }; - - Object.assign(report, { - commitId: identity.sourceIdentity.reportCommitId, - generatedAt: identity.generatedAt, - sourceIdentity: identity.sourceIdentity, - runtimeIdentity: identity.runtimeIdentity, - expectedRuntimeIdentity: identity.expectedRuntimeIdentity, - deploymentIdentity: identity.deploymentIdentity, - webAssetIdentity: identity.webAssetIdentity - }); - - ensureCurrentIdentityChecks(report, identity); - - const runtimeCheck = report.checks.find((check) => check.id === "live-runtime-current-main"); - if (runtimeCheck) { - Object.assign(runtimeCheck, { - status: identity.deploymentIdentity.status, - summary: identity.deploymentIdentity.summary, - evidence: identity.deploymentIdentity.evidence, - observations: identity.deploymentIdentity - }); - } - - const apiRuntimeReadinessCheck = report.checks.find((check) => check.id === "live-api-runtime-readiness"); - if (apiRuntimeReadinessCheck) { - Object.assign(apiRuntimeReadinessCheck, { - status: apiRuntimeReadiness.status, - summary: apiRuntimeReadiness.summary, - evidence: apiRuntimeReadiness.evidence, - observations: apiRuntimeReadiness - }); - } - - const webAssetCheck = report.checks.find((check) => check.id === "live-web-assets-current-main"); - if (webAssetCheck) { - Object.assign(webAssetCheck, { - status: identity.webAssetIdentity.status, - summary: identity.webAssetIdentity.summary, - evidence: identity.webAssetIdentity.assets.map((asset) => `${asset.path}: ${asset.status}`), - observations: identity.webAssetIdentity - }); - } -} - -function ensureCurrentIdentityChecks(report, identity) { - const httpIndex = report.checks.findIndex((check) => check.id === "live-http-html"); - const runtimeCheck = { - id: "live-runtime-current-main", - status: identity.deploymentIdentity.status, - summary: identity.deploymentIdentity.summary, - evidence: identity.deploymentIdentity.evidence, - observations: identity.deploymentIdentity - }; - const webAssetCheck = { - id: "live-web-assets-current-main", - status: identity.webAssetIdentity.status, - summary: identity.webAssetIdentity.summary, - evidence: identity.webAssetIdentity.assets.map((asset) => `${asset.path}: ${asset.status}`), - observations: identity.webAssetIdentity - }; - - upsertCheck(report, runtimeCheck, httpIndex >= 0 ? httpIndex : 0); - const updatedHttpIndex = report.checks.findIndex((check) => check.id === "live-http-html"); - upsertCheck(report, webAssetCheck, updatedHttpIndex >= 0 ? updatedHttpIndex + 1 : 1); -} - -function upsertCheck(report, nextCheck, insertIndex) { - const existingIndex = report.checks.findIndex((check) => check.id === nextCheck.id); - if (existingIndex >= 0) { - report.checks[existingIndex] = nextCheck; - return; - } - report.checks.splice(insertIndex, 0, nextCheck); -} - -function acceptedDomOnlyReport() { - const report = cloneBaseDomOnlyReport(); - applyCurrentIdentity(report); - report.status = "pass"; - report.mode = "dom-only"; - report.evidenceLevel = "DEV-LIVE-DOM-READONLY"; - report.devLive = false; - report.blockers = []; - report.devPreconditions.status = "pass"; - report.devPreconditions.summary = "Deployed browser DOM was inspected read-only; Code Agent chat was not posted."; - report.safety.liveMode = "dom-only-readonly-no-code-agent-post"; - report.safety.codeAgentBrowserJourneySkipped = true; - report.safety.codeAgentPostSent = false; - return report; -} - -function acceptedCodeAgentResponse() { - return { - status: "completed", - provider: "openai-responses", - model: "gpt-5.5", - backend: "hwlab-cloud-api/openai-responses", - traceId: "trc_completed", - hasReply: true, - error: null - }; -} - -function acceptedLiveReport() { - const report = cloneBaseReport(); - applyCurrentIdentity(report); - report.status = "pass"; - report.mode = "live"; - report.evidenceLevel = "DEV-LIVE-BROWSER"; - report.devLive = true; - report.blockers = []; - report.devPreconditions.status = "pass"; - report.devPreconditions.summary = "Deployed 16666 browser journey and same-origin Code Agent chat completed."; - report.safety.liveMode = "browser-user-journey-with-code-agent-post"; - delete report.safety.codeAgentBrowserJourneySkipped; - delete report.safety.codeAgentPostSent; - report.safety.retainedApiFields = [ - "status", - "provider", - "model", - "backend", - "traceId", - "hasReply", - "error.code", - "error.missingEnv", - "error.providerStatus" - ]; - report.safety.statement = "Live smoke opens the deployed workbench and sends one controlled Code Agent message; it does not call hardware write APIs and must not be used to infer M3 DEV-LIVE hardware-loop acceptance."; - - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - const response = acceptedCodeAgentResponse(); - Object.assign(journey, { - status: "pass", - summary: "Deployed browser journey opened the workbench, sent a Code Agent message, and observed a completed UI response.", - evidence: [ - "POST /v1/agent/chat HTTP 200", - "status=completed", - "provider=openai-responses", - "model=gpt-5.5", - "traceId=trc_completed", - "uiStatus=DEV-LIVE 回复", - "blocker=none" - ], - observations: { - request: { - method: "POST", - status: 200, - urlPath: "/v1/agent/chat" - }, - response, - classification: { - status: "pass" - }, - ui: { - agentChatStatus: "DEV-LIVE 回复", - inputCleared: true, - completedMessageVisible: true, - failedMessageVisible: false - }, - networkEvents: [ - { - method: "POST", - status: 200, - urlPath: "/v1/agent/chat", - body: JSON.parse(JSON.stringify(response)) - } - ] - } - }); - - return report; -} - -function fullAcceptanceReport() { - return acceptedLiveReport(); -} - -function preflightBlockedReport() { - const report = cloneBaseReport(); - report.status = "blocked"; - report.evidenceLevel = "BLOCKED"; - report.devLive = false; - report.generatedAt = report.runtimeIdentity.observedAt; - report.expectedRuntimeIdentity = { - status: "observed", - serviceId: "hwlab-cloud-api", - source: "deploy/artifact-catalog.dev.json+deploy/deploy.json", - commitId: "c7de474", - imageTag: "c7de474", - image: "127.0.0.1:5000/hwlab/hwlab-cloud-api:c7de474", - summary: "Expected DEV runtime identity was derived from current source deploy desired state, not from live health." - }; - report.deploymentIdentity = { - status: "pass", - expectedCommit: "c7de474", - expectedImageTag: "c7de474", - expectedSource: "deploy/artifact-catalog.dev.json+deploy/deploy.json", - observedRuntimeCommit: "c7de474", - observedImageTag: "c7de474", - summary: "Live API runtime identity matches the current source desired runtime identity.", - evidence: [ - "expected=c7de474", - "expectedImageTag=c7de474", - "runtimeCommit=c7de474", - "imageTag=c7de474" - ] - }; - report.webAssetIdentity = { - status: "blocked", - assets: [ - { - path: "index.html", - status: "mismatch", - httpStatus: 200, - contentType: "text/html; charset=utf-8", - sourceHash: "435edb7da5c539ff", - liveHash: "924f9d57e0bb61be", - sourceLength: 13844, - liveLength: 11375 - } - ], - mismatches: ["index.html"], - summary: "Deployment drift: live 16666 primary web assets differ from current source (index.html)." - }; - report.checks = [ - { - id: "live-runtime-current-main", - status: report.deploymentIdentity.status, - summary: report.deploymentIdentity.summary, - evidence: report.deploymentIdentity.evidence, - observations: report.deploymentIdentity - }, - report.checks.find((check) => check.id === "live-http-html"), - { - id: "live-web-assets-current-main", - status: report.webAssetIdentity.status, - summary: report.webAssetIdentity.summary, - evidence: ["index.html: mismatch"], - observations: report.webAssetIdentity - }, - { - id: "live-code-agent-browser-journey", - status: "blocked", - summary: "Browser Code Agent journey was not posted because deployed runtime/assets do not match current source identity.", - evidence: [ - "POST /v1/agent/chat not sent", - "runtimeIdentity=pass", - "webAssetIdentity=blocked" - ], - observations: { - request: { - method: "POST", - status: "not_sent", - urlPath: "/v1/agent/chat" - }, - response: { - status: "failed", - provider: "not_observed", - model: "not_observed", - backend: "not_observed", - traceId: "not_observed", - hasReply: false, - error: { - code: "deployment_identity_preflight", - missingEnv: [] - } - }, - classification: { - status: "blocked", - reason: "deployment_identity_preflight", - codeAgentBrowserJourneySkipped: true - } - } - } - ]; - report.blockers = [ - { - type: "observability_blocker", - scope: "live-web-assets-current-main", - status: "open", - summary: report.webAssetIdentity.summary - }, - { - type: "observability_blocker", - scope: "live-code-agent-browser-journey", - status: "open", - summary: "Browser Code Agent journey was not posted because deployed runtime/assets do not match current source identity." - } - ]; - report.devPreconditions.status = "blocked"; - report.devPreconditions.summary = "Deployed browser journey is blocked; see checks and blockers."; - report.safety.liveMode = "deployment-identity-preflight"; - report.safety.codeAgentBrowserJourneySkipped = true; - report.safety.retainedApiFields = ["runtime.commitId", "runtime.imageTag", "web asset hash/status"]; - report.safety.statement = "Live smoke blocked before the browser chat journey because deployed runtime/assets do not match the current source identity; it did not post Code Agent chat, call hardware write APIs, restart services, or read secret values."; - return report; -} - -async function withReport(report, fn) { - const directory = await mkdtemp(path.join(tmpdir(), "hwlab-dev-report-guard-")); - const reportPath = path.join(directory, "report.json"); - try { - await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); - return await fn(reportPath); - } finally { - await rm(directory, { recursive: true, force: true }); - } -} - -function runValidator(reportPath) { - return spawnSync(process.execPath, [validator, reportPath], { - cwd: repoRoot, - encoding: "utf8" - }); -} - -async function assertRejected(report, expectedMessage) { - await withReport(report, async (reportPath) => { - const result = runValidator(reportPath); - assert.notEqual(result.status, 0, "validator should reject stale deployed evidence"); - assert.match(`${result.stdout}\n${result.stderr}`, expectedMessage); - assert.doesNotMatch(`${result.stdout}\n${result.stderr}`, /sk-[A-Za-z0-9._-]{8,}/u); - }); -} - -test("accepts the sanitized deployed workbench evidence contract", async () => { - await withReport(cloneBaseReport(), async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("accepts full workbench acceptance only when API health and runtime durability are ready", async () => { - await withReport(fullAcceptanceReport(), async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("rejects full workbench acceptance when API health is degraded", async () => { - const report = fullAcceptanceReport(); - report.runtimeIdentity.healthStatus = "degraded"; - report.checks.find((check) => check.id === "live-api-runtime-readiness").observations.healthStatus = "degraded"; - await assertRejected(report, /degraded API or runtime durability blocker cannot be summarized as full DEV-LIVE accepted/); -}); - -test("rejects full workbench acceptance when runtime durability query is blocked", async () => { - const report = fullAcceptanceReport(); - Object.assign(report.runtimeIdentity.runtime, { - durable: false, - ready: false, - status: "degraded", - blocker: "runtime_durable_adapter_query_blocked", - connection: { - queryAttempted: true, - queryResult: "query_blocked" - } - }); - Object.assign(report.runtimeIdentity.readiness.durability, { - ready: false, - status: "blocked", - blocker: "runtime_durable_adapter_query_blocked", - blockedLayer: "durability_query", - queryResult: "query_blocked" - }); - report.runtimeIdentity.blockerCodes = ["runtime_durable_adapter_query_blocked"]; - await assertRejected(report, /degraded API or runtime durability blocker cannot be summarized as full DEV-LIVE accepted/); -}); - -test("rejects degraded workbench evidence with full-acceptance wording", async () => { - const report = acceptedLiveReport(); - report.status = "degraded"; - report.evidenceLevel = "DEV-LIVE-BROWSER-DEGRADED"; - report.devLive = false; - report.devPreconditions.status = "degraded"; - Object.assign(report.runtimeIdentity, { - healthStatus: "degraded", - ready: false - }); - Object.assign(report.runtimeIdentity.runtime, { - durable: false, - ready: false, - status: "degraded", - blocker: "runtime_durable_adapter_query_blocked", - connection: { - queryAttempted: true, - queryResult: "query_blocked" - } - }); - Object.assign(report.runtimeIdentity.readiness.durability, { - ready: false, - status: "blocked", - blocker: "runtime_durable_adapter_query_blocked", - blockedLayer: "durability_query", - queryResult: "query_blocked" - }); - report.runtimeIdentity.blockerCodes = ["runtime_durable_adapter_query_blocked"]; - const readiness = report.checks.find((check) => check.id === "live-api-runtime-readiness"); - Object.assign(readiness, { - status: "degraded", - summary: "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance.", - evidence: [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "runtime_durable_adapter_query_blocked", - "blocker=runtime_durable_adapter_query_blocked" - ], - observations: { - status: "degraded", - healthStatus: "degraded", - ready: false, - runtimeDurable: false, - runtimeReady: false, - durabilityReady: false, - durableBlocked: true, - blockerCodes: ["runtime_durable_adapter_query_blocked"], - evidence: [ - "api.status=degraded", - "ready=false", - "runtime.durable=false", - "runtime.ready=false", - "durability.ready=false", - "runtime_durable_adapter_query_blocked", - "blocker=runtime_durable_adapter_query_blocked" - ], - summary: "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance." - } - }); - report.reportLifecycle.summary = "Current deployed browser user journey report for the 16666 Cloud Workbench; deployed UI usable in degraded/read-only mode and does not substitute for M3/M4/M5 DEV-LIVE acceptance."; - report.devPreconditions.summary = "Deployed UI usable in degraded/read-only mode; M3/M4/M5 DEV-LIVE accepted."; - report.safety.statement = "Degraded/read-only mode only; Full DEV-LIVE accepted."; - await assertRejected(report, /degraded API\/runtime report must not claim full M3\/M4\/M5 DEV-LIVE acceptance/); -}); - -test("accepts preflight-blocked workbench evidence when Code Agent POST is marked not_sent", async () => { - await withReport(preflightBlockedReport(), async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("rejects preflight-blocked workbench evidence without not_sent Code Agent journey proof", async () => { - const report = preflightBlockedReport(); - report.checks = report.checks.filter((check) => check.id !== "live-code-agent-browser-journey"); - report.blockers = report.blockers.filter((blocker) => blocker.scope !== "live-code-agent-browser-journey"); - await assertRejected(report, /checks missing live-code-agent-browser-journey/); -}); - -test("rejects accepted workbench evidence when runtime identity is not observed", async () => { - const report = acceptedDomOnlyReport(); - Object.assign(report.runtimeIdentity, { - status: "not_observed", - serviceId: "not_observed", - environment: "not_observed", - healthStatus: "not_observed", - commitId: "not_observed", - commitSource: "not_observed", - imageTag: "not_observed", - reason: "test fixture" - }); - await assertRejected(report, /runtimeIdentity\.status/); -}); - -test("rejects accepted workbench evidence when runtime identity is stale", async () => { - const report = acceptedDomOnlyReport(); - report.runtimeIdentity.observedAt = "2026-05-22T12:00:00.000Z"; - await assertRejected(report, /runtimeIdentity\.observedAt must be fresh/); -}); - -test("rejects accepted workbench evidence that carries an allowed legacy port annotation", async () => { - const report = acceptedDomOnlyReport(); - report.deprecatedEndpoints = [ - { - endpoint: "http://74.48.78.17:6666", - status: "deprecated", - activeGreenEligible: false - } - ]; - report.checks.find((check) => check.id === "live-http-html").evidence.push("legacy probe http://74.48.78.17:6666/"); - await assertRejected(report, /deprecated public endpoint evidence/); -}); - -test("rejects accepted workbench evidence with a hard-coded runtime commit source", async () => { - const report = acceptedDomOnlyReport(); - report.runtimeIdentity.commitSource = "hard-coded"; - await assertRejected(report, /runtimeIdentity\.commitSource/); -}); - -test("rejects accepted workbench evidence that mixes provider_unavailable blockers", async () => { - const report = acceptedLiveReport(); - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - journey.observations.networkEvents[0].body.error = { - code: "provider_unavailable", - missingEnv: ["OPENAI_API_KEY"] - }; - await assertRejected(report, /provider_unavailable, OPENAI_API_KEY, or providerStatus blockers/); -}); - -function applyProviderBlockedJourney(report, { uiLabel = "发送失败" } = {}) { - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - report.status = "blocked"; - report.evidenceLevel = "BLOCKED"; - report.devLive = false; - report.devPreconditions.status = "blocked"; - report.devPreconditions.summary = "Deployed browser journey is blocked; see checks and blockers."; - report.blockers = [ - { - type: "runtime_blocker", - scope: "live-code-agent-browser-journey", - status: "open", - summary: "Code Agent provider returned provider_unavailable with provider HTTP 502." - } - ]; - report.safety.retainedApiFields = [ - "status", - "provider", - "model", - "backend", - "traceId", - "hasReply", - "error.code", - "error.missingEnv", - "error.providerStatus" - ]; - journey.status = "blocked"; - journey.summary = "Deployed browser journey reached Code Agent, but the provider returned a blocked/unavailable state instead of a completed reply."; - journey.evidence = [ - "POST /v1/agent/chat HTTP 200", - "status=failed", - "provider=openai-responses", - "model=gpt-5.5", - "traceId=trc_provider_502", - `uiStatus=${uiLabel}`, - "blocker=provider-upstream" - ]; - journey.observations.request.status = 200; - journey.observations.response = { - status: "failed", - provider: "openai-responses", - model: "gpt-5.5", - backend: "hwlab-cloud-api/openai-responses", - traceId: "trc_provider_502", - hasReply: false, - error: { - code: "provider_unavailable", - missingEnv: [], - providerStatus: 502 - } - }; - journey.observations.classification = { - status: "blocked", - blocker: "provider-upstream", - providerStatus: 502, - reason: "Code Agent HTTP non-2xx status 502 means the upstream response is blocked, not completed" - }; - journey.observations.ui.agentChatStatus = uiLabel; - journey.observations.ui.inputCleared = false; - journey.observations.ui.completedMessageVisible = false; - journey.observations.ui.failedMessageVisible = true; - journey.observations.networkEvents = [ - { - method: "POST", - status: 200, - urlPath: "/v1/agent/chat", - body: journey.observations.response - } - ]; - return report; -} - -test("accepts blocked workbench evidence for provider HTTP 502 without promoting completion", async () => { - const report = applyProviderBlockedJourney(acceptedLiveReport()); - - await withReport(report, async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("accepts sanitized 服务受阻 provider blocker wording only as blocked evidence", async () => { - const report = applyProviderBlockedJourney(acceptedLiveReport(), { uiLabel: "服务受阻" }); - - await withReport(report, async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("rejects sanitized 服务受阻 provider blocker evidence promoted to full workbench acceptance", async () => { - const report = fullAcceptanceReport(); - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - journey.status = "pass"; - journey.summary = "Incorrectly promoted provider blocker evidence."; - journey.evidence = [ - "POST /v1/agent/chat HTTP 200", - "status=failed", - "provider=openai-responses", - "model=gpt-5.5", - "traceId=trc_provider_502", - "uiStatus=服务受阻", - "blocker=provider-upstream" - ]; - journey.observations.response = { - status: "failed", - provider: "openai-responses", - model: "gpt-5.5", - backend: "hwlab-cloud-api/openai-responses", - traceId: "trc_provider_502", - hasReply: false, - error: { - code: "provider_unavailable", - missingEnv: [], - providerStatus: 502 - } - }; - journey.observations.classification = { - status: "blocked", - blocker: "provider-upstream", - providerStatus: 502, - reason: "Code Agent HTTP non-2xx status 502 means the upstream response is blocked, not completed" - }; - journey.observations.ui.agentChatStatus = "服务受阻"; - journey.observations.ui.inputCleared = false; - journey.observations.ui.completedMessageVisible = false; - journey.observations.ui.failedMessageVisible = true; - journey.observations.networkEvents = [ - { - method: "POST", - status: 200, - urlPath: "/v1/agent/chat", - body: journey.observations.response - } - ]; - - await assertRejected(report, /response\.status|provider_unavailable, OPENAI_API_KEY, or providerStatus blockers/); -}); - -test("rejects accepted workbench evidence that hides providerStatus 502 in network events", async () => { - const report = acceptedLiveReport(); - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - journey.observations.networkEvents[0].body.error = { - code: null, - missingEnv: [], - providerStatus: 502 - }; - await assertRejected(report, /providerStatus blockers/); -}); - -test("rejects accepted workbench evidence that uses the synthetic gpt-5 placeholder model", async () => { - const report = acceptedLiveReport(); - const journey = report.checks.find((check) => check.id === "live-code-agent-browser-journey"); - journey.evidence = journey.evidence.map((item) => item === "model=gpt-5.5" ? "model=gpt-5" : item); - journey.observations.response.model = "gpt-5"; - journey.observations.networkEvents[0].body.model = "gpt-5"; - await assertRejected(report, /synthetic placeholder model/); -}); - -test("accepts the current blocked M5 aggregator non-promotion contract", async () => { - await withReport(cloneBaseAggregatorReport(), async (reportPath) => { - const result = runValidator(reportPath); - assert.equal(result.status, 0, result.stderr); - assert.match(result.stdout, /validated 1 dev-gate report JSON file/); - }); -}); - -test("rejects aggregator DB acceptance promoted from frontend route evidence", async () => { - const report = cloneBaseAggregatorReport(); - const m4Source = report.sourceReports.devM4Agent; - m4Source.path = "reports/dev-gate/dev-edge-health.json"; - const check = report.dod.checks.find((item) => item.id === "cloud-api-db-ready"); - Object.assign(check, { - status: "pass", - evidenceLevel: "DEV-LIVE", - summary: "Frontend route HTTP 200 proves DB ready." - }); - Object.assign(report.currentDevLayering.dbLive, { - status: "pass", - evidenceLevel: "DEV-LIVE", - summary: "Promoted from active frontend/API route evidence." - }); - await assertRejected(report, /DB DEV-LIVE acceptance source reports/); -}); - -test("rejects aggregator stale DB runtime-env blocker when DB live is pass", async () => { - const report = cloneBaseAggregatorReport(); - report.blockers.push({ - id: "dev-gate-preflight:cloud-api-db-health-gate", - priority: "P1", - type: "runtime_blocker", - scope: "cloud-api-db-health-gate", - status: "open", - source: "reports/dev-gate/dev-preflight-report.json", - sourceIssue: "pikasTech/HWLAB#34", - summary: "cloud-api DB runtime env is not ready; missing HWLAB_CLOUD_DB_URL, HWLAB_CLOUD_DB_SSL_MODE.", - unblockOrder: 5, - unblocks: ["pikasTech/HWLAB#34", "pikasTech/HWLAB#33", "pikasTech/HWLAB#39"], - rationale: "Stale DB runtime-env blocker fixture." - }); - await assertRejected(report, /stale DB live\/runtime-env blocker/); -}); - -test("rejects aggregator M3 acceptance promoted from source evidence", async () => { - const report = cloneBaseAggregatorReport(); - const sourceEvidence = report.evidence.find((item) => - item.milestone === "M3" && item.category === "hardware-loop-cardinality" - ); - sourceEvidence.level = "DEV-LIVE"; - report.levels["DEV-LIVE"].push({ - milestone: "M3", - issue: sourceEvidence.issue, - taskId: sourceEvidence.taskId, - lifecycleState: "active", - status: sourceEvidence.status, - category: sourceEvidence.category, - reportPath: sourceEvidence.reportPath, - summary: sourceEvidence.summary - }); - await assertRejected(report, /M3 DEV-LIVE evidence must come from hardware-loop-live/); -}); - -test("rejects aggregator M4 acceptance promoted from dry-run evidence", async () => { - const report = cloneBaseAggregatorReport(); - const dryRunEvidence = report.evidence.find((item) => - item.milestone === "M4" && item.category === "agent-loop-dry-run" - ); - dryRunEvidence.level = "DEV-LIVE"; - report.levels["DEV-LIVE"].push({ - milestone: "M4", - issue: dryRunEvidence.issue, - taskId: dryRunEvidence.taskId, - lifecycleState: "active", - status: dryRunEvidence.status, - category: dryRunEvidence.category, - reportPath: dryRunEvidence.reportPath, - summary: dryRunEvidence.summary - }); - await assertRejected(report, /M4 DEV-LIVE evidence must come from agent-loop-live-preflight/); -}); - -test("rejects aggregator M5 acceptance promoted from fixture evidence", async () => { - const report = cloneBaseAggregatorReport(); - const fixtureEvidence = { - milestone: "M5", - issue: "pikasTech/HWLAB#39", - taskId: "dev-mvp-gate-report", - reportPath: "reports/dev-gate/dev-mvp-gate-report.json", - commitId: "fixture", - lifecycleState: "active", - level: "DEV-LIVE", - status: "pass", - category: "fixture-mvp-e2e", - evidence: ["fixture transcript marked green"], - commands: ["node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run"], - summary: "Fixture evidence was incorrectly promoted to M5 DEV-LIVE." - }; - report.evidence.push(fixtureEvidence); - report.levels["DEV-LIVE"].push({ - milestone: fixtureEvidence.milestone, - issue: fixtureEvidence.issue, - taskId: fixtureEvidence.taskId, - lifecycleState: fixtureEvidence.lifecycleState, - status: fixtureEvidence.status, - category: fixtureEvidence.category, - reportPath: fixtureEvidence.reportPath, - summary: fixtureEvidence.summary - }); - await assertRejected(report, /M5 DEV-LIVE evidence must come from mvp-e2e-live/); -}); - -test("rejects aggregator frontend fact that claims promotion authority", async () => { - const report = cloneBaseAggregatorReport(); - report.latestFrontendDevFact.promotesM3M4M5 = true; - await assertRejected(report, /latestFrontendDevFact\.promotesM3M4M5 must be false/); -}); diff --git a/scripts/validate-dev-gate-report.mjs b/scripts/validate-dev-gate-report.mjs index b2bc2be9..e3b39598 100644 --- a/scripts/validate-dev-gate-report.mjs +++ b/scripts/validate-dev-gate-report.mjs @@ -1,340 +1,12 @@ #!/usr/bin/env node import assert from "node:assert/strict"; -import { access, readdir, readFile } from "node:fs/promises"; -import { constants as fsConstants } from "node:fs"; +import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { - ACTIVE_DEV_BROWSER_ENDPOINT, - ACTIVE_DEV_PUBLIC_ENDPOINT, - REPORT_LIFECYCLE_VERSION, - deprecatedPublicEndpoints, - findDeprecatedPublicEndpoints, - findForbiddenActiveDeprecatedEndpoints, - reportIsHistorical -} from "../internal/dev-report-lifecycle.mjs"; +import { ensureNotRepoReportsPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const reportsDir = path.join(repoRoot, "reports/dev-gate"); - -const contractIssue = "pikasTech/HWLAB#31"; -const issueFamily = Object.freeze({ - DEV_CD_APPLY: "pikasTech/HWLAB#274", - DEV_DEPLOY_APPLY: "pikasTech/HWLAB#33", - DEV_GATE_PREFLIGHT: "pikasTech/HWLAB#34", - DEV_ARTIFACT_PUBLISH: "pikasTech/HWLAB#35", - DEV_EDGE_HEALTH: "pikasTech/HWLAB#36", - DEV_M4_AGENT_LOOP: "pikasTech/HWLAB#37", - DEV_M3_HARDWARE_LOOP: "pikasTech/HWLAB#38", - DEV_M5_GATE_REPORT: "pikasTech/HWLAB#39", - DEV_BASE_IMAGE_PREFLIGHT: "pikasTech/HWLAB#40", - DEV_EVIDENCE_BLOCKER_AGGREGATOR: "pikasTech/HWLAB#41", - DEV_M5_GATE_AGGREGATOR_V2: "pikasTech/HWLAB#58", - DEV_CLOUD_WORKBENCH_LIVE: "pikasTech/HWLAB#7", - DEV_CLOUD_WORKBENCH_LAYOUT: "pikasTech/HWLAB#273", - RPT004_MVP_E2E: "pikasTech/HWLAB#316" -}); -const allowedIssues = new Set([ - contractIssue, - ...Object.values(issueFamily) -]); -const requiredPreflightIssue = issueFamily.DEV_GATE_PREFLIGHT; -const requiredMilestoneIds = ["M0", "M1", "M2", "M3", "M4", "M5"]; -const evidenceLevels = new Set(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED"]); -const devCloudWorkbenchRuntimeIdentityMaxSkewMs = 15 * 60 * 1000; -const syntheticAcceptedCodeAgentModels = new Set(["gpt-5"]); -const deploymentPreflightCodeAgentErrorCode = "deployment_identity_preflight"; -const blockedCodeAgentUiLabels = new Set([ - "发送失败", - "服务受阻", - "等待超时", - "Provider 不可用", - "Runner 忙碌", - "Session 受阻", - "Runner 受阻", - "API 错误", - "BLOCKED 凭证缺口" -]); -const requiredValidationCommands = [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" -]; -const requiredAggregatorV2Commands = [ - "node --check scripts/dev-evidence-blocker-aggregator.mjs", - "node --check scripts/src/dev-evidence-blocker-aggregator.mjs", - "node scripts/dev-evidence-blocker-aggregator.mjs --check", - "node scripts/dev-evidence-blocker-aggregator.mjs --markdown", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" -]; -const requiredDevDeployApplySupports = [ - "pikasTech/HWLAB#63", - "pikasTech/HWLAB#50", - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#17", - "pikasTech/HWLAB#32", - "pikasTech/HWLAB#30" -]; -const requiredDevDeployApplyValidationCommands = [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-deploy-apply.mjs", - "node --check scripts/src/dev-deploy-apply.mjs", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" -]; -const requiredPreflightValidationCommands = [ - "node --check scripts/dev-gate-preflight.mjs", - "node --check scripts/src/dev-gate-preflight.mjs", - "node --check scripts/src/registry-capabilities.mjs", - "node --check scripts/refresh-artifact-catalog.mjs", - "node scripts/dev-gate-preflight.mjs" -]; -const requiredSmokeCommand = "node scripts/m1-contract-smoke.mjs"; -const requiredDryRunCommand = - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run"; -const requiredDocs = [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md" -]; -const requiredDevM3Issue = issueFamily.DEV_M3_HARDWARE_LOOP; -const requiredDevM3ValidationCommands = [ - "node --check scripts/dev-m3-hardware-loop-smoke.mjs", - "node --check scripts/validate-dev-m3-cardinality.mjs", - "node scripts/validate-dev-m3-cardinality.mjs", - "node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run", - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod" -]; -const legacyDevM3LiveCommand = - "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"; -const requiredDevM3Docs = [ - "docs/dev-acceptance-matrix.md", - "docs/m3-hardware-loop.md" -]; -const requiredDevEdgeIssue = issueFamily.DEV_EDGE_HEALTH; -const requiredDevEdgeValidationCommands = [ - "node --check scripts/dev-edge-health-smoke.mjs", - "node --check scripts/src/dev-edge-health-smoke-lib.mjs", - "node scripts/dev-edge-health-smoke.mjs --live --write-report" -]; -const requiredDevEdgeDocs = [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/dev-edge-health.md" -]; -const requiredDevM2Issue = "pikasTech/HWLAB#23"; -const requiredDevM2ValidationCommands = [ - "node --check scripts/m2-dev-deploy-smoke.mjs", - "node scripts/m2-dev-deploy-smoke.mjs --dry-run", - "node scripts/m2-dev-deploy-smoke.mjs --live --confirm-dev --confirmed-non-production --write-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" -]; -const requiredDevM2Docs = [ - "docs/m2-dev-deploy-smoke.md", - "docs/dev-acceptance-matrix.md", - "fixtures/dev-deploy-smoke/dev-deploy-smoke.json" -]; -const requiredDevCloudWorkbenchValidationCommands = [ - "node --check scripts/dev-cloud-workbench-smoke.mjs", - "node --check scripts/src/dev-cloud-workbench-smoke-lib.mjs", - "node scripts/dev-cloud-workbench-smoke.mjs --source", - "node scripts/dev-cloud-workbench-smoke.mjs --local-agent-fixture" -]; -const requiredDevCloudWorkbenchDomOnlyValidationCommands = [ - "node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json", - "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-dom-only.json" -]; -const requiredDevCloudWorkbenchLiveValidationCommands = [ - "node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json", - "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-live.json" -]; -const requiredDevCloudWorkbenchLayoutValidationCommands = [ - "node --check scripts/dev-cloud-workbench-layout-smoke.mjs", - "node --check scripts/src/dev-cloud-workbench-smoke-lib.mjs", - "npm run web:check", - "npm run web:layout", - "npm run web:layout:build", - "npm run web:layout:live" -]; -const requiredDevCloudWorkbenchDocs = [ - "docs/cloud-web-workbench.md", - "docs/reference/cloud-workbench.md", - "docs/reference/code-agent-chat-readiness.md" -]; -const reportFamilyTemplates = new Map([ - [ - "dev-cd-apply", - { - issue: issueFamily.DEV_CD_APPLY, - requiredDocs: [ - "docs/reference/deployment-publish.md", - "docs/reference/dev-runtime-boundary.md", - "docs/dev-artifact-publish.md", - "docs/dev-deploy-apply.md", - "docs/artifact-catalog.md" - ], - requiredValidationCommands: [ - "node --check scripts/dev-cd-apply.mjs", - "node --check scripts/src/dev-cd-apply.mjs", - "node --test scripts/src/dev-cd-apply.test.mjs", - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-deploy-apply.mjs", - "node --test scripts/src/dev-deploy-apply.test.mjs", - "node scripts/deploy-desired-state-plan.mjs --check", - "node scripts/validate-artifact-catalog.mjs", - "git diff --check" - ], - requiredSmokeCommand: "npm run check", - requiredDryRunCommand: "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report" - } - ], - [ - "dev-gate-report-contract", - { - issue: contractIssue, - requiredDocs: ["docs/dev-acceptance-matrix.md", "docs/m0-contract-audit.md"], - requiredValidationCommands, - requiredSmokeCommand, - requiredDryRunCommand - } - ], - [ - "dev-mvp-gate-report", - { - issue: issueFamily.DEV_M5_GATE_REPORT, - requiredDocs: [ - "docs/dev-gate-report.md", - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/m1-local-smoke.md", - "docs/m2-dev-deploy-smoke.md", - "docs/m3-hardware-loop.md", - "docs/m4-agent-loop.md", - "docs/m5-mvp-e2e.md", - "docs/runtime-boundary-guard.md", - "docs/topology-constraints.md", - "docs/artifact-catalog.md", - "docs/schema-drift-map.md", - "protocol/README.md", - "protocol/audit.md", - "protocol/evidence-chain.md", - "protocol/mvp-e2e-contract.md" - ], - requiredValidationCommands: [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - requiredSmokeCommand: "node scripts/m1-contract-smoke.mjs", - requiredDryRunCommand: - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" - } - ], - [ - "dev-deploy-apply", - { - issue: issueFamily.DEV_DEPLOY_APPLY, - requiredDocs: [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/dev-deploy-apply.md", - "docs/artifact-catalog.md", - "deploy/README.md" - ], - requiredValidationCommands: [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-deploy-apply.mjs", - "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked" - ], - requiredSmokeCommand, - requiredDryRunCommand - } - ], - [ - "dev-artifact-publish", - { - issue: issueFamily.DEV_ARTIFACT_PUBLISH, - requiredDocs: [ - "docs/dev-acceptance-matrix.md", - "docs/m0-contract-audit.md", - "docs/artifact-catalog.md", - "docs/dev-artifact-publish.md", - "docs/dev-base-image-preflight.md" - ], - requiredValidationCommands: [ - "node --check scripts/dev-artifact-publish.mjs", - "node --check scripts/src/dev-artifact-services.mjs", - "node --check scripts/src/registry-capabilities.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", - "node scripts/dev-artifact-publish.mjs --preflight --no-report", - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs" - ], - requiredSmokeCommand, - requiredDryRunCommand - } - ], - [ - "dev-m4-agent-loop", - { - issue: issueFamily.DEV_M4_AGENT_LOOP, - requiredDocs: ["docs/dev-acceptance-matrix.md", "docs/dev-m4-agent-loop.md"], - requiredValidationCommands: [ - "node --check scripts/validate-dev-gate-report.mjs", - "node scripts/validate-dev-gate-report.mjs", - "node --check scripts/dev-m4-agent-loop-smoke.mjs", - "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run", - "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" - ], - requiredSmokeCommand: "node scripts/m4-agent-loop-smoke.mjs", - requiredDryRunCommand: "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run" - } - ] -]); - -const statusValues = new Set(["pass", "blocked", "degraded", "skip", "not_run", "not_applicable", "not_sent", "failed"]); -const blockerTypes = new Set([ - "contract_blocker", - "environment_blocker", - "network_blocker", - "runtime_blocker", - "agent_blocker", - "observability_blocker", - "safety_blocker" -]); -const blockerStates = new Set(["open", "acknowledged", "closed"]); -const artifactSourceStates = new Set(["source-present", "intentionally-disabled"]); -const preflightConclusions = new Set(["ready", "blocked"]); -const registryCapabilityDimensionIds = new Set([ - "process-http-access", - "docker-daemon-push-access", - "k3s-pull-access" -]); -const registryCapabilityDimensionKeys = new Set([ - "processHttpAccess", - "dockerDaemonPushAccess", - "k3sPullAccess" -]); -const requiredPreflightSupports = [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#12", - "pikasTech/HWLAB#22", - "pikasTech/HWLAB#23", - "pikasTech/HWLAB#29", - "pikasTech/HWLAB#30", - "pikasTech/HWLAB#31", - "pikasTech/HWLAB#66" -]; -const protectedAggregatorLiveCategories = Object.freeze({ - M3: "hardware-loop-live", - M4: "agent-loop-live-preflight", - M5: "mvp-e2e-live" -}); -const protectedAggregatorMilestones = new Set(Object.keys(protectedAggregatorLiveCategories)); function assertObject(value, label) { assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`); @@ -343,3528 +15,101 @@ function assertObject(value, label) { function assertString(value, label) { assert.equal(typeof value, "string", `${label} must be a string`); assert.ok(value.trim().length > 0, `${label} must not be empty`); - assert.ok(!value.includes("\n"), `${label} must be a single line`); -} - -function assertArray(value, label) { - assert.ok(Array.isArray(value), `${label} must be an array`); -} - -function assertStatus(value, label) { - assertString(value, label); - assert.ok(statusValues.has(value), `${label} must be one of ${Array.from(statusValues).join(", ")}`); -} - -function assertTimestamp(value, label) { - assertString(value, label); - assert.ok(!Number.isNaN(Date.parse(value)), `${label} must be an RFC 3339 timestamp`); -} - -function assertDbLayerBooleans(value, label) { - assertObject(value, label); - for (const field of ["configReady", "liveConnected", "connectionAttempted"]) { - assertBoolean(value[field], `${label}.${field}`); - } - assertString(value.connectionResult, `${label}.connectionResult`); - assertNoDbSecretMaterial(value, label); -} - -function assertDbRuntimeReadiness(value, label) { - assertDbLayerBooleans(value, label); - for (const field of ["envInjected", "valuesRedacted", "secretMaterialRead", "liveDbEvidence"]) { - assertBoolean(value[field], `${label}.${field}`); - } - assert.equal(value.valuesRedacted, true, `${label}.valuesRedacted`); - assert.equal(value.secretMaterialRead, false, `${label}.secretMaterialRead`); - assertObject(value.secret, `${label}.secret`); - assertString(value.secret.secretName, `${label}.secret.secretName`); - assertString(value.secret.secretKey, `${label}.secret.secretKey`); - assert.equal(value.secret.redacted, true, `${label}.secret.redacted`); - assert.equal(value.secret.secretValueRead, false, `${label}.secret.secretValueRead`); - assertArray(value.evidence, `${label}.evidence`); - if (value.status === "blocked") { - assertString(value.blocker, `${label}.blocker`); - } -} - -function assertNoDbSecretMaterial(value, label) { - const serialized = JSON.stringify(value); - for (const forbidden of ["postgres://", "postgresql://", "password=", "://user:"]) { - assert.equal(serialized.includes(forbidden), false, `${label} must not include ${forbidden}`); - } -} - -function assertRepoRelativePath(value, label) { - assertString(value, label); - assert.ok(!path.isAbsolute(value), `${label} must be repo-relative`); - assert.ok(!value.startsWith(".."), `${label} must stay inside the repository`); - - const absolutePath = path.resolve(repoRoot, value); - assert.ok( - absolutePath.startsWith(`${repoRoot}${path.sep}`), - `${label} must stay inside the repository` - ); - - return absolutePath; -} - -function assertStringArray(value, label, { minLength = 0 } = {}) { - assertArray(value, label); - assert.ok(value.length >= minLength, `${label} must contain at least ${minLength} item(s)`); - for (const [index, item] of value.entries()) { - assertString(item, `${label}[${index}]`); - } -} - -function assertUnique(values, label) { - assert.equal(new Set(values).size, values.length, `${label} must be unique`); -} - -function assertReportLifecycle(report, raw, label) { - assertObject(report.reportLifecycle, `${label}.reportLifecycle`); - const lifecycle = report.reportLifecycle; - assert.equal(lifecycle.version, REPORT_LIFECYCLE_VERSION, `${label}.reportLifecycle.version`); - assert.ok(["active", "historical"].includes(lifecycle.state), `${label}.reportLifecycle.state`); - assert.equal(lifecycle.activeEndpoint, ACTIVE_DEV_PUBLIC_ENDPOINT, `${label}.reportLifecycle.activeEndpoint`); - assert.equal(lifecycle.activeBrowserEndpoint, ACTIVE_DEV_BROWSER_ENDPOINT, `${label}.reportLifecycle.activeBrowserEndpoint`); - assertString(lifecycle.summary, `${label}.reportLifecycle.summary`); - - if (lifecycle.state === "active") { - assert.equal(lifecycle.deprecatedEndpoint, null, `${label}.reportLifecycle.deprecatedEndpoint`); - assert.deepEqual( - findForbiddenActiveDeprecatedEndpoints(report, raw), - [], - `${label} active report must not use deprecated public endpoints as active evidence` - ); - return; - } - - assert.ok( - deprecatedPublicEndpoints.includes(lifecycle.deprecatedEndpoint), - `${label}.reportLifecycle.deprecatedEndpoint must name a deprecated public endpoint` - ); - assert.ok( - lifecycle.summary.toLowerCase().includes("historical") || - lifecycle.summary.toLowerCase().includes("deprecated"), - `${label}.reportLifecycle.summary must explain the historical/deprecated state` - ); - const deprecatedMatches = findDeprecatedPublicEndpoints(raw); - assert.ok( - deprecatedMatches.length === 0 || deprecatedMatches.includes(lifecycle.deprecatedEndpoint), - `${label}.reportLifecycle.deprecatedEndpoint must cover deprecated public endpoint evidence` - ); -} - -function primaryReportStatus(report) { - return report.status ?? - report.gateStatus ?? - report.conclusion ?? - report.artifactPublish?.status ?? - report.summary?.status ?? - (Array.isArray(report.blockers) && report.blockers.length > 0 ? "blocked" : "not_run"); -} - -function statusIsPass(value) { - return value === "pass"; -} - -function validateHistoricalReport(report, label) { - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assertString(report.issue, `${label}.issue`); - assert.ok(allowedIssues.has(report.issue), `${label}.issue must be a known HWLAB DEV gate issue`); - assertString(report.taskId ?? report.reportKind, `${label}.taskId`); - assert.ok( - ["blocked", "not_run", "not_applicable", "failed", "degraded"].includes(primaryReportStatus(report)), - `${label} historical report must not present active green status` - ); - assert.ok( - Array.isArray(report.blockers) && report.blockers.length >= 1, - `${label}.blockers must explain why the report is historical` - ); - assertBlockers(report.blockers, `${label}.blockers`); -} - -async function readJsonReport(relativePath) { - const raw = await readFile(path.join(repoRoot, relativePath), "utf8"); - return { report: JSON.parse(raw), raw }; -} - -async function collectReportFiles() { - if (process.argv.length > 2) { - return process.argv.slice(2).map((entry) => path.relative(repoRoot, path.resolve(process.cwd(), entry))); - } - return collectReportJsonFiles(reportsDir, "reports/dev-gate"); -} - -async function collectReportJsonFiles(directory, relativeDirectory) { - const entries = await readdir(directory, { withFileTypes: true }); - const nested = await Promise.all(entries.map(async (entry) => { - const relativePath = path.join(relativeDirectory, entry.name); - if (entry.isDirectory()) { - return collectReportJsonFiles(path.join(directory, entry.name), relativePath); - } - return entry.isFile() && entry.name.endsWith(".json") ? [relativePath] : []; - })); - return nested.flat().sort(); -} - -async function validateReport(relativePath) { - const { report, raw } = await readJsonReport(relativePath); - const label = relativePath; - assertObject(report, label); - assertReportLifecycle(report, raw, label); - if (reportIsHistorical(report)) { - validateHistoricalReport(report, label); - return; - } - - if (report.reportKind === "dev-gate-preflight") { - await validatePreflightReport(relativePath, report); - return; - } - if (report.reportKind === "dev-m5-gate-aggregator") { - await validateAggregatorV2Report(relativePath, report); - return; - } - if (report.reportKind === "rpt-004-mvp-e2e-harness" || report.taskId === "rpt004-mvp-e2e-harness") { - await validateRpt004MvpE2eReport(report, label); - return; - } - - if (report.issue === requiredDevM3Issue || report.taskId === "dev-m3-hardware-loop") { - await validateDevM3Report(report, label); - return; - } - if (report.issue === requiredDevEdgeIssue || report.taskId === "dev-edge-health") { - await validateDevEdgeReport(report, label); - return; - } - if (report.issue === requiredDevM2Issue || report.taskId === "m2-dev-deploy-smoke") { - await validateDevM2Report(report, label); - return; - } - if ( - report.issue === issueFamily.DEV_CLOUD_WORKBENCH_LIVE || - report.taskId === "dev-cloud-workbench-live" - ) { - await validateDevCloudWorkbenchLiveReport(report, label, raw); - return; - } - if ( - report.issue === issueFamily.DEV_CLOUD_WORKBENCH_LAYOUT || - report.taskId === "dev-cloud-workbench-layout" - ) { - await validateDevCloudWorkbenchLayoutReport(report, label); - return; - } - - const template = reportFamilyTemplates.get(report.taskId); - assert.ok( - template, - `${label}.taskId must be one of ${Array.from(reportFamilyTemplates.keys()).join(", ")}` - ); - - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "sourceContract", - "validationCommands", - "localSmoke", - "dryRun", - "devPreconditions", - "blockers" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, template.issue, `${label}.issue`); - assert.match(report.taskId, /^[a-z][a-z0-9-]*$/, `${label}.taskId`); - assert.match(report.commitId, /^[a-f0-9]{7,40}$/, `${label}.commitId`); - assert.match(report.acceptanceLevel, /^[a-z][a-z0-9_-]*$/, `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - assertStringArray(report.sourceContract.documents, `${label}.sourceContract.documents`, { - minLength: template.requiredDocs.length - }); - - const documents = new Set(report.sourceContract.documents); - assertUnique(report.sourceContract.documents, `${label}.sourceContract.documents`); - for (const requiredDoc of template.requiredDocs) { - assert.ok(documents.has(requiredDoc), `${label}.sourceContract.documents missing ${requiredDoc}`); - } - for (const [index, documentPath] of report.sourceContract.documents.entries()) { - const absolutePath = assertRepoRelativePath( - documentPath, - `${label}.sourceContract.documents[${index}]` - ); - await access(absolutePath, fsConstants.R_OK); - } - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of template.requiredValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - assertObject(report.localSmoke, `${label}.localSmoke`); - assertStatus(report.localSmoke.status, `${label}.localSmoke.status`); - assertStringArray(report.localSmoke.commands, `${label}.localSmoke.commands`, { minLength: 1 }); - assertUnique(report.localSmoke.commands, `${label}.localSmoke.commands`); - assert.ok( - report.localSmoke.commands.includes(template.requiredSmokeCommand), - `${label}.localSmoke.commands missing ${template.requiredSmokeCommand}` - ); - assertStringArray(report.localSmoke.evidence, `${label}.localSmoke.evidence`, { minLength: 1 }); - assertString(report.localSmoke.summary, `${label}.localSmoke.summary`); - - assertObject(report.dryRun, `${label}.dryRun`); - assertStatus(report.dryRun.status, `${label}.dryRun.status`); - assertStringArray(report.dryRun.commands, `${label}.dryRun.commands`, { minLength: 1 }); - assertUnique(report.dryRun.commands, `${label}.dryRun.commands`); - assert.ok( - report.dryRun.commands.includes(template.requiredDryRunCommand), - `${label}.dryRun.commands missing ${template.requiredDryRunCommand}` - ); - assertStringArray(report.dryRun.evidence, `${label}.dryRun.evidence`, { minLength: 1 }); - assertString(report.dryRun.summary, `${label}.dryRun.summary`); - - assertObject(report.devPreconditions, `${label}.devPreconditions`); - assertStatus(report.devPreconditions.status, `${label}.devPreconditions.status`); - assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, { - minLength: 1 - }); - assertString(report.devPreconditions.summary, `${label}.devPreconditions.summary`); - - assertArray(report.blockers, `${label}.blockers`); - assertUnique( - report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`), - `${label}.blockers` - ); - for (const [index, blocker] of report.blockers.entries()) { - const blockerLabel = `${label}.blockers[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["type", "scope", "status", "summary"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`); - assertString(blocker.scope, `${blockerLabel}.scope`); - assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`); - assertString(blocker.summary, `${blockerLabel}.summary`); - } - - if (Object.hasOwn(report, "notes")) { - assertString(report.notes, `${label}.notes`); - } - - if (Object.hasOwn(report, "gateStatus")) { - assertStatus(report.gateStatus, `${label}.gateStatus`); - } - - if (Object.hasOwn(report, "artifactPublish")) { - assertObject(report.artifactPublish, `${label}.artifactPublish`); - assertRegistryCapabilities(report.artifactPublish.registryCapabilities, `${label}.artifactPublish.registryCapabilities`); - assertRegistryCapabilities(report.artifactPublish.publishPlan?.registryCapabilities, `${label}.artifactPublish.publishPlan.registryCapabilities`); - assertArray(report.artifactPublish.services ?? [], `${label}.artifactPublish.services`); - for (const [index, service] of report.artifactPublish.services.entries()) { - const serviceLabel = `${label}.artifactPublish.services[${index}]`; - assertObject(service, serviceLabel); - assertString(service.serviceId, `${serviceLabel}.serviceId`); - if (Object.hasOwn(service, "sourceCommitId")) { - assertString(service.sourceCommitId, `${serviceLabel}.sourceCommitId`); - } - assertString(service.status, `${serviceLabel}.status`); - assertString(service.image, `${serviceLabel}.image`); - assertString(service.imageTag, `${serviceLabel}.imageTag`); - assertShaOrNotPublished(service.digest, `${serviceLabel}.digest`); - assertString(service.implementationState, `${serviceLabel}.implementationState`); - assertString(service.sourceState, `${serviceLabel}.sourceState`); - assert.ok( - artifactSourceStates.has(service.sourceState), - `${serviceLabel}.sourceState must be source-present or intentionally-disabled` - ); - if (Object.hasOwn(service, "distFreshness")) { - assertObject(service.distFreshness, `${serviceLabel}.distFreshness`); - assert.equal(service.distFreshness.status, "pass", `${serviceLabel}.distFreshness.status`); - assert.equal(service.distFreshness.distPath, "web/hwlab-cloud-web/dist", `${serviceLabel}.distFreshness.distPath`); - assert.equal( - service.distFreshness.buildCommand, - "node web/hwlab-cloud-web/scripts/build.mjs", - `${serviceLabel}.distFreshness.buildCommand` - ); - assert.equal( - service.distFreshness.freshnessCommand, - "node web/hwlab-cloud-web/scripts/build.mjs", - `${serviceLabel}.distFreshness.freshnessCommand` - ); - assertArray(service.distFreshness.files, `${serviceLabel}.distFreshness.files`); - assertArray(service.distFreshness.mismatches, `${serviceLabel}.distFreshness.mismatches`); - assert.equal(service.distFreshness.mismatches.length, 0, `${serviceLabel}.distFreshness.mismatches`); - } - if (service.sourceState === "intentionally-disabled") { - assert.equal(service.entrypoint, null, `${serviceLabel}.entrypoint must be null when intentionally disabled`); - } - } - } - - if (Object.hasOwn(report, "milestones")) { - assertArray(report.milestones, `${label}.milestones`); - assert.deepEqual( - report.milestones.map((milestone) => milestone.id), - requiredMilestoneIds, - `${label}.milestones must cover M0-M5 in order` - ); - assertUnique( - report.milestones.map((milestone) => milestone.id), - `${label}.milestones` - ); - - for (const [index, milestone] of report.milestones.entries()) { - const milestoneLabel = `${label}.milestones[${index}]`; - assertObject(milestone, milestoneLabel); - for (const field of ["id", "status", "commands", "evidence", "summary"]) { - assert.ok(Object.hasOwn(milestone, field), `${milestoneLabel} missing ${field}`); - } - assert.match(milestone.id, /^M[0-5]$/, `${milestoneLabel}.id`); - assertStatus(milestone.status, `${milestoneLabel}.status`); - assertStringArray(milestone.commands, `${milestoneLabel}.commands`, { minLength: 1 }); - assertUnique(milestone.commands, `${milestoneLabel}.commands`); - assertStringArray(milestone.evidence, `${milestoneLabel}.evidence`, { minLength: 1 }); - assertUnique(milestone.evidence, `${milestoneLabel}.evidence`); - assertString(milestone.summary, `${milestoneLabel}.summary`); - if (Object.hasOwn(milestone, "blocker")) { - assertString(milestone.blocker, `${milestoneLabel}.blocker`); - } - } - } - - if (Object.hasOwn(report.devPreconditions, "commands")) { - assertStringArray(report.devPreconditions.commands, `${label}.devPreconditions.commands`, { - minLength: 1 - }); - assertUnique(report.devPreconditions.commands, `${label}.devPreconditions.commands`); - } - - if (Object.hasOwn(report.devPreconditions, "evidence")) { - assertStringArray(report.devPreconditions.evidence, `${label}.devPreconditions.evidence`, { - minLength: 1 - }); - assertUnique(report.devPreconditions.evidence, `${label}.devPreconditions.evidence`); - } - - if (report.taskId === "dev-deploy-apply") { - assertDevDeployApplyReport(report, label); - } - if (report.taskId === "dev-cd-apply") { - assertDevCdApplyReport(report, label); - } -} - -async function validateRpt004MvpE2eReport(report, label) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "reportKind", - "issue", - "taskId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "mode", - "status", - "conclusion", - "commanderSummary", - "sourceContract", - "validationCommands", - "devPreconditions", - "target", - "runPolicy", - "expected", - "dimensions", - "checks", - "blockers", - "safety" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.reportKind, "rpt-004-mvp-e2e-harness", `${label}.reportKind`); - assert.equal(report.issue, issueFamily.RPT004_MVP_E2E, `${label}.issue`); - assert.equal(report.taskId, "rpt004-mvp-e2e-harness", `${label}.taskId`); - assert.equal(report.acceptanceLevel, "rpt004_mvp_e2e", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assert.ok(["source-check", "dev-live"].includes(report.mode), `${label}.mode`); - assert.ok(["pass", "blocked", "not_current"].includes(report.status), `${label}.status`); - - assertObject(report.conclusion, `${label}.conclusion`); - assertObject(report.commanderSummary, `${label}.commanderSummary`); - assert.equal(report.commanderSummary.reportClass, "live-mutating", `${label}.commanderSummary.reportClass`); - assert.equal(typeof report.commanderSummary.allowedLiveMutationUsed, "boolean", `${label}.commanderSummary.allowedLiveMutationUsed`); - assert.ok( - ["CHECK_ONLY", "BLOCKED", "NOT_CURRENT", "MVP_PASS"].includes(report.conclusion.classification), - `${label}.conclusion.classification` - ); - assert.equal(typeof report.conclusion.mvpPass, "boolean", `${label}.conclusion.mvpPass`); - if (report.status !== "pass" || report.conclusion.classification !== "MVP_PASS") { - assert.equal(report.conclusion.mvpPass, false, `${label}.conclusion.mvpPass must stay false outside MVP_PASS`); - } - if (report.conclusion.classification === "NOT_CURRENT") { - assert.equal(report.status, "not_current", `${label}.status for NOT_CURRENT`); - } - - assertCommandList(report.validationCommands, `${label}.validationCommands`, { minLength: 5 }); - for (const requiredCommand of [ - "node --check scripts/rpt004-mvp-e2e-harness.mjs", - "node --check scripts/src/rpt004-mvp-e2e-harness.mjs", - "node --test scripts/src/rpt004-mvp-e2e-harness.test.mjs", - "node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write", - "node scripts/rpt004-mvp-e2e-harness.mjs --live --url http://74.48.78.17:16666/ --api-url http://74.48.78.17:16667/" - ]) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - assertObject(report.target, `${label}.target`); - assert.equal(report.target.environment, "dev", `${label}.target.environment`); - assert.equal(report.target.browserUrl, "http://74.48.78.17:16666/", `${label}.target.browserUrl`); - assert.equal(report.target.apiUrl, "http://74.48.78.17:16667/", `${label}.target.apiUrl`); - assert.equal(report.target.m3IoUrl, "http://74.48.78.17:16667/v1/m3/io", `${label}.target.m3IoUrl`); - - assertObject(report.expected, `${label}.expected`); - assert.match(report.expected.commitId, /^[a-f0-9]{7,40}$/u, `${label}.expected.commitId`); - assert.equal(report.expected.serviceCount, report.expected.requiredServiceCount, `${label}.expected.serviceCount`); - - const requiredDimensions = [ - "artifact", - "revision", - "health", - "workbench", - "layout", - "m3TrustedLoop", - "durableEvidence", - "codeAgentSkillPath", - "codeAgentSession", - "skillCliM3Route" - ]; - assertObject(report.dimensions, `${label}.dimensions`); - for (const id of requiredDimensions) { - assert.ok(Object.hasOwn(report.dimensions, id), `${label}.dimensions missing ${id}`); - } - - assertArray(report.checks, `${label}.checks`); - const minimumCheckCount = report.mode === "dev-live" ? 4 : 2; - assert.ok( - report.checks.length >= minimumCheckCount, - `${label}.checks must include at least ${minimumCheckCount} entries` - ); - const checkIds = new Set(report.checks.map((check) => check.id)); - assert.ok(checkIds.has("expected-artifact-current"), `${label}.checks missing expected-artifact-current`); - if (report.mode === "dev-live") { - for (const id of [ - "live-revision-current", - "live-health-ready", - "16666-default-chinese-workbench", - "layout-smoke-hit-target", - "code-agent-skill-path", - "code-agent-session-thread", - "skill-cli-hwlab-api-m3-route", - "m3-v1-io-trusted-loop", - "operation-audit-evidence-durable-green" - ]) { - assert.ok(checkIds.has(id) || report.status !== "pass", `${label}.checks missing ${id}`); - } - } - for (const [index, check] of report.checks.entries()) { - assertObject(check, `${label}.checks[${index}]`); - assertString(check.id, `${label}.checks[${index}].id`); - assert.ok(["pass", "blocked", "not_current", "not_run"].includes(check.status), `${label}.checks[${index}].status`); - assertString(check.summary, `${label}.checks[${index}].summary`); - } - - assertBlockers(report.blockers, `${label}.blockers`); - assertObject(report.safety, `${label}.safety`); - assert.equal(report.safety.devOnly, true, `${label}.safety.devOnly`); - assert.equal(report.safety.prodTouched, false, `${label}.safety.prodTouched`); - assert.equal(report.safety.deployApplyAttempted, false, `${label}.safety.deployApplyAttempted`); - assert.equal(report.safety.rolloutAttempted, false, `${label}.safety.rolloutAttempted`); - assert.equal(report.safety.secretValuesPrinted, false, `${label}.safety.secretValuesPrinted`); - assert.equal(report.safety.openAiFallbackCountsAsPass, false, `${label}.safety.openAiFallbackCountsAsPass`); - assert.equal(report.safety.liveM3WritesRequireExplicitFlag, true, `${label}.safety.liveM3WritesRequireExplicitFlag`); - assert.equal(report.liveMutationUsed, report.runPolicy.allowM3Write === true, `${label}.liveMutationUsed`); - - if (report.artifacts?.artifactReport) { - assert.match(report.artifacts.artifactReport, /not applicable/u, `${label}.artifacts.artifactReport`); - } -} - -async function assertDocumentSet(documentsValue, label, requiredDocs) { - assertStringArray(documentsValue, label, { minLength: requiredDocs.length }); - const documents = new Set(documentsValue); - assertUnique(documentsValue, label); - for (const requiredDoc of requiredDocs) { - assert.ok(documents.has(requiredDoc), `${label} missing ${requiredDoc}`); - } - for (const [index, documentPath] of documentsValue.entries()) { - const absolutePath = assertRepoRelativePath(documentPath, `${label}[${index}]`); - await access(absolutePath, fsConstants.R_OK); - } -} - -function assertBlockers(blockers, label) { - assertArray(blockers, label); - assertUnique( - blockers.map((blocker) => `${blocker.type}::${blocker.scope}`), - label - ); - for (const [index, blocker] of blockers.entries()) { - const blockerLabel = `${label}[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["type", "scope", "status", "summary"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`); - assertString(blocker.scope, `${blockerLabel}.scope`); - assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`); - assertString(blocker.summary, `${blockerLabel}.summary`); - } -} - -function assertCommandList(value, label, { minLength = 1 } = {}) { - assertStringArray(value, label, { minLength }); - assertUnique(value, label); - for (const command of value) { - assert.ok(!command.includes("--prod"), `${label} must not include PROD command`); - assert.ok(!command.includes("--production"), `${label} must not include production command`); - } -} - -function assertDevDeployApplyReport(report, label) { - assert.equal(report.issue, issueFamily.DEV_DEPLOY_APPLY, `${label}.issue`); - - assertStringArray(report.supports, `${label}.supports`, { - minLength: requiredDevDeployApplySupports.length - }); - for (const supportedIssue of requiredDevDeployApplySupports) { - assert.ok(report.supports.includes(supportedIssue), `${label}.supports missing ${supportedIssue}`); - } - - for (const requiredCommand of requiredDevDeployApplyValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - assertObject(report.devDeployApply, `${label}.devDeployApply`); - const plan = report.devDeployApply; - for (const field of [ - "conclusion", - "applyBoundary", - "target", - "artifactPlan", - "workloadPlan", - "servicePlan", - "templateJobReplacementPolicy", - "templateJobReplacements", - "manualCommands", - "rollbackHint", - "remainingBlockers" - ]) { - assert.ok(Object.hasOwn(plan, field), `${label}.devDeployApply missing ${field}`); - } - - assertObject(plan.conclusion, `${label}.devDeployApply.conclusion`); - assert.ok(["blocked", "ready"].includes(plan.conclusion.status), `${label}.devDeployApply.conclusion.status`); - assertString(plan.conclusion.reason, `${label}.devDeployApply.conclusion.reason`); - assert.equal(plan.conclusion.blockerCount, report.blockers.length, `${label}.devDeployApply.conclusion.blockerCount`); - - assertObject(plan.applyBoundary, `${label}.devDeployApply.applyBoundary`); - assert.equal(plan.applyBoundary.defaultNoWrite, plan.mode !== "apply", `${label}.devDeployApply.applyBoundary.defaultNoWrite`); - assert.equal(plan.applyBoundary.mutationAttempted, report.devDeployApply.mutationAttempted, `${label}.devDeployApply.applyBoundary.mutationAttempted`); - assertStringArray(plan.applyBoundary.applyRequiresFlags, `${label}.devDeployApply.applyBoundary.applyRequiresFlags`, { - minLength: 3 - }); - for (const flag of ["--apply", "--confirm-dev", "--confirmed-non-production"]) { - assert.ok(plan.applyBoundary.applyRequiresFlags.includes(flag), `${label}.devDeployApply.applyBoundary.applyRequiresFlags missing ${flag}`); - } - assertStringArray(plan.applyBoundary.forbiddenActions, `${label}.devDeployApply.applyBoundary.forbiddenActions`, { - minLength: 6 - }); - for (const forbiddenAction of ["prod-deploy", "secret-read", "runtime-substitute", "heavy-master-e2e"]) { - assert.ok(plan.applyBoundary.forbiddenActions.includes(forbiddenAction), `${label}.devDeployApply.applyBoundary.forbiddenActions missing ${forbiddenAction}`); - } - assertString(plan.applyBoundary.writeScope, `${label}.devDeployApply.applyBoundary.writeScope`); - assertString(plan.applyBoundary.noWriteScope, `${label}.devDeployApply.applyBoundary.noWriteScope`); - - assertObject(plan.target, `${label}.devDeployApply.target`); - assert.equal(plan.target.environment, "dev", `${label}.devDeployApply.target.environment`); - assert.equal(plan.target.namespace, "hwlab-dev", `${label}.devDeployApply.target.namespace`); - assert.equal(plan.target.endpoint, "http://74.48.78.17:16667", `${label}.devDeployApply.target.endpoint`); - assert.equal(plan.target.prodDisabled, true, `${label}.devDeployApply.target.prodDisabled`); - - assertObject(plan.artifactPlan, `${label}.devDeployApply.artifactPlan`); - for (const field of ["expectedArtifactCommit", "deployCommitId", "catalogCommitId", "sourceCommitId"]) { - assertString(plan.artifactPlan[field], `${label}.devDeployApply.artifactPlan.${field}`); - } - assertStringArray(plan.artifactPlan.serviceCommitIds, `${label}.devDeployApply.artifactPlan.serviceCommitIds`, { - minLength: 1 - }); - assert.equal(typeof plan.artifactPlan.matchesSourceCommit, "boolean", `${label}.devDeployApply.artifactPlan.matchesSourceCommit`); - assert.equal(typeof plan.artifactPlan.published, "boolean", `${label}.devDeployApply.artifactPlan.published`); - assert.equal(typeof plan.artifactPlan.registryVerified, "boolean", `${label}.devDeployApply.artifactPlan.registryVerified`); - assert.ok(Number.isInteger(plan.artifactPlan.imageCount), `${label}.devDeployApply.artifactPlan.imageCount`); - assertStringArray(plan.artifactPlan.unpublishedServices, `${label}.devDeployApply.artifactPlan.unpublishedServices`); - - assertArray(plan.workloadPlan, `${label}.devDeployApply.workloadPlan`); - assert.ok(plan.workloadPlan.length >= 1, `${label}.devDeployApply.workloadPlan must not be empty`); - assertArray(plan.servicePlan, `${label}.devDeployApply.servicePlan`); - assert.ok(plan.servicePlan.length >= 1, `${label}.devDeployApply.servicePlan must not be empty`); - for (const [index, workload] of plan.workloadPlan.entries()) { - const workloadLabel = `${label}.devDeployApply.workloadPlan[${index}]`; - assertObject(workload, workloadLabel); - for (const field of ["kind", "name", "namespace", "serviceIds", "containers"]) { - assert.ok(Object.hasOwn(workload, field), `${workloadLabel} missing ${field}`); - } - assert.equal(workload.namespace, "hwlab-dev", `${workloadLabel}.namespace`); - assertStringArray(workload.serviceIds, `${workloadLabel}.serviceIds`, { minLength: 1 }); - assertArray(workload.containers, `${workloadLabel}.containers`); - } - - assertObject(plan.templateJobReplacementPolicy, `${label}.devDeployApply.templateJobReplacementPolicy`); - assert.equal(plan.templateJobReplacementPolicy.namespace, "hwlab-dev", `${label}.devDeployApply.templateJobReplacementPolicy.namespace`); - assert.equal(plan.templateJobReplacementPolicy.status, "active", `${label}.devDeployApply.templateJobReplacementPolicy.status`); - assertArray(plan.templateJobReplacementPolicy.allowedJobs, `${label}.devDeployApply.templateJobReplacementPolicy.allowedJobs`); - const allowedTemplateJobNames = plan.templateJobReplacementPolicy.allowedJobs.map((job) => job.name); - assert.deepEqual( - allowedTemplateJobNames, - ["hwlab-agent-worker-template", "hwlab-cli-template"], - `${label}.devDeployApply.templateJobReplacementPolicy.allowedJobs` - ); - - assertArray(plan.templateJobReplacements, `${label}.devDeployApply.templateJobReplacements`); - for (const [index, replacement] of plan.templateJobReplacements.entries()) { - const replacementLabel = `${label}.devDeployApply.templateJobReplacements[${index}]`; - assertObject(replacement, replacementLabel); - for (const field of ["namespace", "jobName", "allowed", "desiredSuspended", "oldImages", "newImages", "replace", "result", "reason"]) { - assert.ok(Object.hasOwn(replacement, field), `${replacementLabel} missing ${field}`); - } - assert.equal(replacement.namespace, "hwlab-dev", `${replacementLabel}.namespace`); - assert.ok(allowedTemplateJobNames.includes(replacement.jobName), `${replacementLabel}.jobName must be allowlisted`); - assert.equal(replacement.allowed, true, `${replacementLabel}.allowed`); - assert.equal(replacement.desiredSuspended, true, `${replacementLabel}.desiredSuspended`); - assertArray(replacement.oldImages, `${replacementLabel}.oldImages`); - assertArray(replacement.newImages, `${replacementLabel}.newImages`); - assert.equal(typeof replacement.replace, "boolean", `${replacementLabel}.replace`); - assert.ok( - ["not_needed", "planned", "replaced", "not_found", "not_evaluated", "blocked", "delete_failed", "delete_succeeded_apply_failed"].includes(replacement.result), - `${replacementLabel}.result` - ); - assertString(replacement.reason, `${replacementLabel}.reason`); - } - - if (Object.hasOwn(plan, "cloudWebRollout")) { - assertObject(plan.cloudWebRollout, `${label}.devDeployApply.cloudWebRollout`); - for (const field of [ - "serviceId", - "namespace", - "status", - "sourceCommitId", - "image", - "imageTag", - "digest", - "publishState", - "rolloutRevision", - "liveImage", - "imageMatchesDesired", - "verificationCommand", - "readCommand" - ]) { - assert.ok(Object.hasOwn(plan.cloudWebRollout, field), `${label}.devDeployApply.cloudWebRollout missing ${field}`); - } - assert.equal(plan.cloudWebRollout.serviceId, "hwlab-cloud-web", `${label}.devDeployApply.cloudWebRollout.serviceId`); - assert.equal(plan.cloudWebRollout.namespace, "hwlab-dev", `${label}.devDeployApply.cloudWebRollout.namespace`); - assert.ok(["observed", "blocked", "not_evaluated"].includes(plan.cloudWebRollout.status), `${label}.devDeployApply.cloudWebRollout.status`); - assertString(plan.cloudWebRollout.sourceCommitId, `${label}.devDeployApply.cloudWebRollout.sourceCommitId`); - assertString(plan.cloudWebRollout.image, `${label}.devDeployApply.cloudWebRollout.image`); - assertString(plan.cloudWebRollout.imageTag, `${label}.devDeployApply.cloudWebRollout.imageTag`); - assertShaOrNotPublished(plan.cloudWebRollout.digest, `${label}.devDeployApply.cloudWebRollout.digest`); - if (plan.cloudWebRollout.rolloutRevision !== null) { - assertString(plan.cloudWebRollout.rolloutRevision, `${label}.devDeployApply.cloudWebRollout.rolloutRevision`); - } - if (plan.cloudWebRollout.liveImage !== null) { - assertString(plan.cloudWebRollout.liveImage, `${label}.devDeployApply.cloudWebRollout.liveImage`); - } - assert.equal(typeof plan.cloudWebRollout.imageMatchesDesired, "boolean", `${label}.devDeployApply.cloudWebRollout.imageMatchesDesired`); - assert.match(plan.cloudWebRollout.verificationCommand, /rollout status deployment\/hwlab-cloud-web/u, `${label}.devDeployApply.cloudWebRollout.verificationCommand`); - assert.match(plan.cloudWebRollout.verificationCommand, /^KUBECONFIG=(?:[A-Za-z0-9_@%+=:,./-]+|'[^']+') kubectl /u, `${label}.devDeployApply.cloudWebRollout.verificationCommand`); - assert.match(plan.cloudWebRollout.readCommand, /get deployment hwlab-cloud-web/u, `${label}.devDeployApply.cloudWebRollout.readCommand`); - assert.match(plan.cloudWebRollout.readCommand, /^KUBECONFIG=(?:[A-Za-z0-9_@%+=:,./-]+|'[^']+') kubectl /u, `${label}.devDeployApply.cloudWebRollout.readCommand`); - if (plan.cloudWebRollout.status === "blocked") { - assertString(plan.cloudWebRollout.blocker, `${label}.devDeployApply.cloudWebRollout.blocker`); - } - } - - assertObject(plan.manualCommands, `${label}.devDeployApply.manualCommands`); - assert.ok(["blocked", "ready"].includes(plan.manualCommands.status), `${label}.devDeployApply.manualCommands.status`); - assertCommandList(plan.manualCommands.beforeHumanApproval, `${label}.devDeployApply.manualCommands.beforeHumanApproval`); - assertCommandList(plan.manualCommands.afterHumanApproval, `${label}.devDeployApply.manualCommands.afterHumanApproval`, { - minLength: 0 - }); - assertString(plan.manualCommands.summary, `${label}.devDeployApply.manualCommands.summary`); - if (plan.conclusion.status === "blocked") { - assert.equal(plan.manualCommands.status, "blocked", `${label}.devDeployApply.manualCommands.status`); - assert.equal(plan.manualCommands.afterHumanApproval.length, 0, `${label}.devDeployApply.manualCommands.afterHumanApproval`); - } - if (plan.conclusion.status === "ready") { - assert.equal(plan.manualCommands.status, "ready", `${label}.devDeployApply.manualCommands.status`); - assert.ok( - plan.manualCommands.afterHumanApproval.includes("node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report"), - `${label}.devDeployApply.manualCommands.afterHumanApproval missing transaction apply command` - ); - } - - assertObject(plan.rollbackHint, `${label}.devDeployApply.rollbackHint`); - assert.equal(plan.rollbackHint.namespace, "hwlab-dev", `${label}.devDeployApply.rollbackHint.namespace`); - assertString(plan.rollbackHint.strategy, `${label}.devDeployApply.rollbackHint.strategy`); - assertCommandList(plan.rollbackHint.captureBeforeApply, `${label}.devDeployApply.rollbackHint.captureBeforeApply`); - assertCommandList(plan.rollbackHint.deploymentRollbackCommands, `${label}.devDeployApply.rollbackHint.deploymentRollbackCommands`); - assertCommandList(plan.rollbackHint.jobCleanupCommands, `${label}.devDeployApply.rollbackHint.jobCleanupCommands`); - assertCommandList(plan.rollbackHint.postRollbackChecks, `${label}.devDeployApply.rollbackHint.postRollbackChecks`); - - assertArray(plan.remainingBlockers, `${label}.devDeployApply.remainingBlockers`); - assert.equal(plan.remainingBlockers.length, report.blockers.length, `${label}.devDeployApply.remainingBlockers.length`); - for (const [index, blocker] of plan.remainingBlockers.entries()) { - const blockerLabel = `${label}.devDeployApply.remainingBlockers[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["type", "scope", "status", "summary", "unblockHint"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be known`); - assertString(blocker.unblockHint, `${blockerLabel}.unblockHint`); - } -} - -function assertDevCdApplyReport(report, label) { - assert.equal(report.issue, issueFamily.DEV_CD_APPLY, `${label}.issue`); - assertStringArray(report.supports, `${label}.supports`, { minLength: 4 }); - for (const supportedIssue of [ - "pikasTech/HWLAB#274", - "pikasTech/HWLAB#116", - "pikasTech/HWLAB#235" - ]) { - assert.ok(report.supports.includes(supportedIssue), `${label}.supports missing ${supportedIssue}`); - } - - assertObject(report.transaction, `${label}.transaction`); - for (const field of ["transactionId", "ownerTaskId", "targetNamespace", "lockName", "lockBackend", "startedAt", "finishedAt", "ttlSeconds", "phases"]) { - assert.ok(Object.hasOwn(report.transaction, field), `${label}.transaction missing ${field}`); - } - assert.equal(report.transaction.targetNamespace, "hwlab-dev", `${label}.transaction.targetNamespace`); - assert.equal(report.transaction.lockName, "hwlab-dev-cd-lock", `${label}.transaction.lockName`); - assert.equal(report.transaction.lockBackend, "Lease", `${label}.transaction.lockBackend`); - assertString(report.transaction.transactionId, `${label}.transaction.transactionId`); - assertString(report.transaction.ownerTaskId, `${label}.transaction.ownerTaskId`); - assert.ok(Number.isInteger(report.transaction.ttlSeconds), `${label}.transaction.ttlSeconds`); - assertArray(report.transaction.phases, `${label}.transaction.phases`); - - assertObject(report.devCdApply, `${label}.devCdApply`); - for (const field of ["mode", "target", "deployJson", "lock", "steps", "liveBefore", "liveVerify", "reportPaths", "safety"]) { - assert.ok(Object.hasOwn(report.devCdApply, field), `${label}.devCdApply missing ${field}`); - } - assert.equal(report.devCdApply.mode, "apply", `${label}.devCdApply.mode`); - - assertObject(report.devCdApply.target, `${label}.devCdApply.target`); - assert.equal(report.devCdApply.target.namespace, "hwlab-dev", `${label}.devCdApply.target.namespace`); - assert.equal(report.devCdApply.target.apiEndpoint, "http://74.48.78.17:16667", `${label}.devCdApply.target.apiEndpoint`); - assert.equal(report.devCdApply.target.browserEndpoint, "http://74.48.78.17:16666/", `${label}.devCdApply.target.browserEndpoint`); - assertString(report.devCdApply.target.shortCommitId, `${label}.devCdApply.target.shortCommitId`); - - assertObject(report.devCdApply.deployJson, `${label}.devCdApply.deployJson`); - for (const side of ["before", "after"]) { - assertObject(report.devCdApply.deployJson[side], `${label}.devCdApply.deployJson.${side}`); - assert.equal(report.devCdApply.deployJson[side].path, "deploy/deploy.json", `${label}.devCdApply.deployJson.${side}.path`); - assertString(report.devCdApply.deployJson[side].hash, `${label}.devCdApply.deployJson.${side}.hash`); - assertString(report.devCdApply.deployJson[side].commitId, `${label}.devCdApply.deployJson.${side}.commitId`); - } - - assertObject(report.devCdApply.lock, `${label}.devCdApply.lock`); - assert.equal(typeof report.devCdApply.lock.acquired, "boolean", `${label}.devCdApply.lock.acquired`); - if (report.devCdApply.lock.acquired) { - assert.ok( - report.transaction.phases.some((phase) => phase.phase === "publishing"), - `${label}.transaction.phases must include publishing after lock acquisition` - ); - } else { - assert.equal(report.status, "blocked", `${label}.status must be blocked when lock was not acquired`); - assert.equal(report.devCdApply.safety.mutationAttempted, false, `${label}.devCdApply.safety.mutationAttempted`); - } - - assertArray(report.devCdApply.steps, `${label}.devCdApply.steps`); - for (const [index, step] of report.devCdApply.steps.entries()) { - const stepLabel = `${label}.devCdApply.steps[${index}]`; - assertObject(step, stepLabel); - for (const field of ["id", "phase", "status", "command", "code", "startedAt", "finishedAt"]) { - assert.ok(Object.hasOwn(step, field), `${stepLabel} missing ${field}`); - } - assert.ok(["publishing", "applying", "verifying"].includes(step.phase), `${stepLabel}.phase`); - assert.ok(["pass", "blocked"].includes(step.status), `${stepLabel}.status`); - assertString(step.command, `${stepLabel}.command`); - } - - assertObject(report.devCdApply.liveVerify, `${label}.devCdApply.liveVerify`); - assert.ok(["pass", "blocked", "not_run"].includes(report.devCdApply.liveVerify.status), `${label}.devCdApply.liveVerify.status`); - assertArray(report.devCdApply.liveVerify.endpoints, `${label}.devCdApply.liveVerify.endpoints`); - assertObject(report.devCdApply.liveVerify.summary, `${label}.devCdApply.liveVerify.summary`); - if (report.devCdApply.liveVerify.status !== "not_run") { - const urls = report.devCdApply.liveVerify.endpoints.map((endpoint) => endpoint.url); - assert.ok(urls.includes("http://74.48.78.17:16666/health/live"), `${label}.devCdApply.liveVerify missing 16666 health`); - assert.ok(urls.includes("http://74.48.78.17:16667/health/live"), `${label}.devCdApply.liveVerify missing 16667 health`); - } - - assertObject(report.devCdApply.reportPaths, `${label}.devCdApply.reportPaths`); - assert.equal(report.devCdApply.reportPaths.artifacts, "reports/dev-gate/dev-artifacts.json", `${label}.devCdApply.reportPaths.artifacts`); - assert.equal(report.devCdApply.reportPaths.deployApply, "reports/dev-gate/dev-deploy-report.json", `${label}.devCdApply.reportPaths.deployApply`); - - assertObject(report.devCdApply.safety, `${label}.devCdApply.safety`); - assert.equal(report.devCdApply.safety.prodTouched, false, `${label}.devCdApply.safety.prodTouched`); - assert.equal(report.devCdApply.safety.secretValuesRead, false, `${label}.devCdApply.safety.secretValuesRead`); - assert.equal(report.devCdApply.safety.secretValuesPrinted, false, `${label}.devCdApply.safety.secretValuesPrinted`); - assert.equal(report.devCdApply.safety.oldPublicPortsUsed, false, `${label}.devCdApply.safety.oldPublicPortsUsed`); - assert.equal(report.devCdApply.safety.singleLock, "Lease/hwlab-dev/hwlab-dev-cd-lock", `${label}.devCdApply.safety.singleLock`); -} - - -function assertShaOrNotPublished(value, label) { - assertString(value, label); - assert.ok( - value === "not_published" || /^sha256:[a-f0-9]{64}$/.test(value), - `${label} must be not_published or a sha256 digest` - ); -} - -function assertRegistryCapabilityDimension(dimension, label, expectedId) { - assertObject(dimension, label); - for (const field of [ - "id", - "status", - "requiredForPublish", - "requiredForDeploy", - "endpoint", - "probeKind", - "summary", - "evidence" - ]) { - assert.ok(Object.hasOwn(dimension, field), `${label} missing ${field}`); - } - assert.equal(dimension.id, expectedId, `${label}.id`); - assert.ok(registryCapabilityDimensionIds.has(dimension.id), `${label}.id must be a known registry capability dimension`); - assertStatus(dimension.status, `${label}.status`); - assert.equal(typeof dimension.requiredForPublish, "boolean", `${label}.requiredForPublish`); - assert.equal(typeof dimension.requiredForDeploy, "boolean", `${label}.requiredForDeploy`); - assertString(dimension.endpoint, `${label}.endpoint`); - assertString(dimension.probeKind, `${label}.probeKind`); - assertString(dimension.summary, `${label}.summary`); - assertObject(dimension.evidence, `${label}.evidence`); -} - -function assertRegistryCapabilities(capabilities, label) { - assertObject(capabilities, label); - for (const field of [ - "version", - "registryPrefix", - "generatedAt", - "classification", - "dimensions", - "interpretation" - ]) { - assert.ok(Object.hasOwn(capabilities, field), `${label} missing ${field}`); - } - assert.equal(capabilities.version, "v1", `${label}.version`); - assertString(capabilities.registryPrefix, `${label}.registryPrefix`); - assertTimestamp(capabilities.generatedAt, `${label}.generatedAt`); - assertStatus(capabilities.classification, `${label}.classification`); - assertObject(capabilities.dimensions, `${label}.dimensions`); - assert.deepEqual( - new Set(Object.keys(capabilities.dimensions)), - registryCapabilityDimensionKeys, - `${label}.dimensions must contain processHttpAccess, dockerDaemonPushAccess, and k3sPullAccess` - ); - assertRegistryCapabilityDimension(capabilities.dimensions.processHttpAccess, `${label}.dimensions.processHttpAccess`, "process-http-access"); - assertRegistryCapabilityDimension(capabilities.dimensions.dockerDaemonPushAccess, `${label}.dimensions.dockerDaemonPushAccess`, "docker-daemon-push-access"); - assertRegistryCapabilityDimension(capabilities.dimensions.k3sPullAccess, `${label}.dimensions.k3sPullAccess`, "k3s-pull-access"); - assert.equal(capabilities.dimensions.processHttpAccess.requiredForPublish, false, `${label}.dimensions.processHttpAccess.requiredForPublish`); - assert.equal(capabilities.dimensions.dockerDaemonPushAccess.requiredForPublish, true, `${label}.dimensions.dockerDaemonPushAccess.requiredForPublish`); - assert.equal(capabilities.dimensions.k3sPullAccess.requiredForDeploy, true, `${label}.dimensions.k3sPullAccess.requiredForDeploy`); - assertObject(capabilities.interpretation, `${label}.interpretation`); - for (const key of registryCapabilityDimensionKeys) { - assertString(capabilities.interpretation[key], `${label}.interpretation.${key}`); - } -} - -function assertArtifactIdentity(identity, label, target) { - assertObject(identity, label); - for (const field of [ - "source", - "deployManifest", - "artifactCatalog", - "services", - "serviceCommitIds", - "matchesSource", - "publishVerified", - "refreshCommands" - ]) { - assert.ok(Object.hasOwn(identity, field), `${label} missing ${field}`); - } - - assertObject(identity.source, `${label}.source`); - assert.equal(identity.source.ref, target.ref, `${label}.source.ref`); - assert.equal(identity.source.commitId, target.commitId, `${label}.source.commitId`); - assert.equal(identity.source.shortCommitId, target.shortCommitId, `${label}.source.shortCommitId`); - - assertObject(identity.deployManifest, `${label}.deployManifest`); - assert.equal(identity.deployManifest.path, "deploy/deploy.json", `${label}.deployManifest.path`); - assert.match(identity.deployManifest.commitId, /^[a-f0-9]{7,40}$/, `${label}.deployManifest.commitId`); - assert.equal(typeof identity.deployManifest.matchesSource, "boolean", `${label}.deployManifest.matchesSource`); - - assertObject(identity.artifactCatalog, `${label}.artifactCatalog`); - assert.equal(identity.artifactCatalog.path, "deploy/artifact-catalog.dev.json", `${label}.artifactCatalog.path`); - assert.match(identity.artifactCatalog.commitId, /^[a-f0-9]{7,40}$/, `${label}.artifactCatalog.commitId`); - assert.ok(["contract-skeleton", "published"].includes(identity.artifactCatalog.artifactState), `${label}.artifactCatalog.artifactState`); - assert.equal(typeof identity.artifactCatalog.ciPublished, "boolean", `${label}.artifactCatalog.ciPublished`); - assert.equal(typeof identity.artifactCatalog.registryVerified, "boolean", `${label}.artifactCatalog.registryVerified`); - assertString(identity.artifactCatalog.provenance, `${label}.artifactCatalog.provenance`); - assert.equal(typeof identity.artifactCatalog.matchesSource, "boolean", `${label}.artifactCatalog.matchesSource`); - assertObject(identity.artifactCatalog.digestCounts, `${label}.artifactCatalog.digestCounts`); - for (const field of ["sha256", "notPublished", "invalid"]) { - assert.equal(typeof identity.artifactCatalog.digestCounts[field], "number", `${label}.artifactCatalog.digestCounts.${field}`); - } - - assertArray(identity.services, `${label}.services`); - assert.ok(identity.services.length >= 1, `${label}.services must not be empty`); - assertUnique(identity.services.map((service) => service.serviceId), `${label}.services`); - for (const [index, service] of identity.services.entries()) { - const serviceLabel = `${label}.services[${index}]`; - assertObject(service, serviceLabel); - for (const field of ["serviceId", "commitId", "matchesSource", "image", "imageTag", "digest", "publishState"]) { - assert.ok(Object.hasOwn(service, field), `${serviceLabel} missing ${field}`); - } - assertString(service.serviceId, `${serviceLabel}.serviceId`); - assert.match(service.commitId, /^[a-f0-9]{7,40}$/, `${serviceLabel}.commitId`); - assert.equal(typeof service.matchesSource, "boolean", `${serviceLabel}.matchesSource`); - assertString(service.image, `${serviceLabel}.image`); - assertString(service.imageTag, `${serviceLabel}.imageTag`); - assertShaOrNotPublished(service.digest, `${serviceLabel}.digest`); - assert.ok(["skeleton-only", "published"].includes(service.publishState), `${serviceLabel}.publishState`); - } - - assertStringArray(identity.serviceCommitIds, `${label}.serviceCommitIds`, { minLength: 1 }); - assert.equal(typeof identity.matchesSource, "boolean", `${label}.matchesSource`); - assert.equal(typeof identity.publishVerified, "boolean", `${label}.publishVerified`); - assertObject(identity.refreshCommands, `${label}.refreshCommands`); - assert.equal( - identity.refreshCommands.blocked, - `node scripts/refresh-artifact-catalog.mjs --target-ref ${target.ref} --blocked`, - `${label}.refreshCommands.blocked` - ); - assert.equal( - identity.refreshCommands.published, - `node scripts/refresh-artifact-catalog.mjs --target-ref ${target.ref} --publish-report reports/dev-gate/dev-artifacts.json`, - `${label}.refreshCommands.published` - ); - - if (identity.publishVerified) { - assert.equal(identity.artifactCatalog.ciPublished, true, `${label}.artifactCatalog.ciPublished`); - assert.equal(identity.artifactCatalog.registryVerified, true, `${label}.artifactCatalog.registryVerified`); - assert.equal(identity.artifactCatalog.digestCounts.sha256, identity.services.length, `${label}.artifactCatalog.digestCounts.sha256`); - assert.equal(identity.artifactCatalog.digestCounts.notPublished, 0, `${label}.artifactCatalog.digestCounts.notPublished`); - assert.equal(identity.artifactCatalog.digestCounts.invalid, 0, `${label}.artifactCatalog.digestCounts.invalid`); - } -} -async function validateDevEdgeReport(report, label) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "sourceContract", - "validationCommands", - "localSmoke", - "dryRun", - "devPreconditions", - "blockers", - "edgeHealth" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, requiredDevEdgeIssue, `${label}.issue`); - assert.equal(report.taskId, "dev-edge-health", `${label}.taskId`); - assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`); - assert.equal(report.acceptanceLevel, "dev_edge_health", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - await assertDocumentSet( - report.sourceContract.documents, - `${label}.sourceContract.documents`, - requiredDevEdgeDocs - ); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 3 }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of requiredDevEdgeValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - for (const section of ["localSmoke", "dryRun", "devPreconditions"]) { - assertObject(report[section], `${label}.${section}`); - assertStatus(report[section].status, `${label}.${section}.status`); - assertString(report[section].summary, `${label}.${section}.summary`); - } - assertStringArray(report.localSmoke.commands, `${label}.localSmoke.commands`, { minLength: 1 }); - assertStringArray(report.localSmoke.evidence, `${label}.localSmoke.evidence`, { minLength: 1 }); - assertStringArray(report.dryRun.commands, `${label}.dryRun.commands`, { minLength: 1 }); - assertStringArray(report.dryRun.evidence, `${label}.dryRun.evidence`, { minLength: 1 }); - assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, { - minLength: 1 - }); - assertBlockers(report.blockers, `${label}.blockers`); - - assertObject(report.edgeHealth, `${label}.edgeHealth`); - assertTimestamp(report.edgeHealth.generatedAt, `${label}.edgeHealth.generatedAt`); - assert.equal(report.edgeHealth.endpoint, "http://74.48.78.17:16667", `${label}.edgeHealth.endpoint`); - assert.ok( - ["pass", "blocker", "not_run"].includes(report.edgeHealth.status), - `${label}.edgeHealth.status must be pass, blocker, or not_run` - ); - assertString(report.edgeHealth.classification, `${label}.edgeHealth.classification`); - assertObject(report.edgeHealth.contracts, `${label}.edgeHealth.contracts`); - assertDbLayerBooleans( - report.edgeHealth.contracts?.deploy?.cloudApiDb, - `${label}.edgeHealth.contracts.deploy.cloudApiDb` - ); - assertDbRuntimeReadiness( - report.edgeHealth.runtimeDbReadiness, - `${label}.edgeHealth.runtimeDbReadiness` - ); - assertArray(report.edgeHealth.publicTcp ?? [], `${label}.edgeHealth.publicTcp`); - assertArray(report.edgeHealth.publicHttp ?? [], `${label}.edgeHealth.publicHttp`); - assertObject(report.edgeHealth.kubernetes ?? {}, `${label}.edgeHealth.kubernetes`); -} - -async function validateDevM2Report(report, label) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "status", - "generatedAt", - "endpoint", - "frontendEndpoint", - "sourceContract", - "validationCommands", - "localSmoke", - "dryRun", - "devPreconditions", - "blockers", - "runtimeSmoke" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, requiredDevM2Issue, `${label}.issue`); - assert.equal(report.taskId, "m2-dev-deploy-smoke", `${label}.taskId`); - assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`); - assert.equal(report.acceptanceLevel, "dev_m2_deploy_smoke", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assert.equal(report.status, "pass", `${label}.status`); - assertTimestamp(report.generatedAt, `${label}.generatedAt`); - assert.equal(report.endpoint, "http://74.48.78.17:16667", `${label}.endpoint`); - assert.equal(report.frontendEndpoint, "http://74.48.78.17:16666", `${label}.frontendEndpoint`); - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - await assertDocumentSet( - report.sourceContract.documents, - `${label}.sourceContract.documents`, - requiredDevM2Docs - ); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { - minLength: requiredDevM2ValidationCommands.length - }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of requiredDevM2ValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - for (const section of ["localSmoke", "dryRun", "devPreconditions"]) { - assertObject(report[section], `${label}.${section}`); - assertStatus(report[section].status, `${label}.${section}.status`); - assertStringArray(report[section].commands, `${label}.${section}.commands`, { minLength: 1 }); - assertStringArray(report[section].evidence, `${label}.${section}.evidence`, { minLength: 1 }); - assertString(report[section].summary, `${label}.${section}.summary`); - } - assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, { - minLength: 1 - }); - for (const command of [ - "curl -fsS --max-time 10 http://74.48.78.17:16667/health", - "curl -fsS --max-time 10 http://74.48.78.17:16667/health/live", - "curl -fsS --max-time 10 http://74.48.78.17:16666/" - ]) { - assert.ok( - report.devPreconditions.commands.includes(command), - `${label}.devPreconditions.commands missing ${command}` - ); - } - assertBlockers(report.blockers, `${label}.blockers`); - - assertObject(report.runtimeSmoke, `${label}.runtimeSmoke`); - assert.equal(report.runtimeSmoke.mode, "live-read-only", `${label}.runtimeSmoke.mode`); - assert.equal(report.runtimeSmoke.status, "pass", `${label}.runtimeSmoke.status`); - assertTimestamp(report.runtimeSmoke.generatedAt, `${label}.runtimeSmoke.generatedAt`); - assert.equal(report.runtimeSmoke.apiEndpoint, "http://74.48.78.17:16667", `${label}.runtimeSmoke.apiEndpoint`); - assert.equal(report.runtimeSmoke.frontendEndpoint, "http://74.48.78.17:16666", `${label}.runtimeSmoke.frontendEndpoint`); - - assertObject(report.runtimeSmoke.safety, `${label}.runtimeSmoke.safety`); - assert.equal(report.runtimeSmoke.safety.environment, "dev", `${label}.runtimeSmoke.safety.environment`); - for (const field of [ - "prodTouched", - "secretsRead", - "restarts", - "deployAttempted", - "heavyE2E", - "unideskRuntimeSubstitute" - ]) { - assert.equal(report.runtimeSmoke.safety[field], false, `${label}.runtimeSmoke.safety.${field}`); - } - - assertArray(report.runtimeSmoke.probes, `${label}.runtimeSmoke.probes`); - assert.ok(report.runtimeSmoke.probes.length >= 3, `${label}.runtimeSmoke.probes must include api and frontend probes`); - assertObject(report.runtimeSmoke.legacyPublicEndpoint, `${label}.runtimeSmoke.legacyPublicEndpoint`); - assert.equal( - report.runtimeSmoke.legacyPublicEndpoint.endpoint, - "http://74.48.78.17:6667", - `${label}.runtimeSmoke.legacyPublicEndpoint.endpoint` - ); - assert.equal( - report.runtimeSmoke.legacyPublicEndpoint.status, - "deprecated", - `${label}.runtimeSmoke.legacyPublicEndpoint.status` - ); - assert.equal( - report.runtimeSmoke.legacyPublicEndpoint.activeGreenEligible, - false, - `${label}.runtimeSmoke.legacyPublicEndpoint.activeGreenEligible` - ); -} - -async function validateDevCloudWorkbenchLiveReport(report, label, raw) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "status", - "generatedAt", - "mode", - "url", - "evidenceLevel", - "devLive", - "sourceIdentity", - "runtimeIdentity", - "sourceContract", - "validationCommands", - "localSmoke", - "dryRun", - "devPreconditions", - "checks", - "blockers", - "safety" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, issueFamily.DEV_CLOUD_WORKBENCH_LIVE, `${label}.issue`); - assert.equal(report.taskId, "dev-cloud-workbench-live", `${label}.taskId`); - assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`); - assert.equal(report.acceptanceLevel, "dev_cloud_workbench_live", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assertStatus(report.status, `${label}.status`); - assert.equal( - report.blockers.length === 0 ? ["pass", "degraded"].includes(report.status) : report.status === "blocked", - true, - `${label}.status must match blockers` - ); - assertTimestamp(report.generatedAt, `${label}.generatedAt`); - assert.ok(["live", "dom-only"].includes(report.mode), `${label}.mode`); - assert.equal(report.url, "http://74.48.78.17:16666/", `${label}.url`); - if (report.mode === "dom-only") { - assert.equal(report.evidenceLevel, report.status === "pass" ? "DEV-LIVE-DOM-READONLY" : "BLOCKED", `${label}.evidenceLevel`); - assert.equal(report.devLive, false, `${label}.devLive`); - } else { - assert.equal( - report.evidenceLevel, - report.status === "pass" - ? "DEV-LIVE-BROWSER" - : report.status === "degraded" ? "DEV-LIVE-BROWSER-DEGRADED" : "BLOCKED", - `${label}.evidenceLevel` - ); - assert.equal(report.devLive, report.status === "pass", `${label}.devLive`); - } - assertDevCloudWorkbenchIdentity(report, label); - const apiRuntimeReadiness = assertDevCloudWorkbenchApiRuntimeReadiness(report, label); - assertDevCloudWorkbenchEndpoints(report, label); - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - await assertDocumentSet( - report.sourceContract.documents, - `${label}.sourceContract.documents`, - requiredDevCloudWorkbenchDocs - ); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { - minLength: requiredDevCloudWorkbenchValidationCommands.length - }); - const requiredModeCommands = report.mode === "dom-only" - ? requiredDevCloudWorkbenchDomOnlyValidationCommands - : requiredDevCloudWorkbenchLiveValidationCommands; - const requiredCommands = [ - ...requiredDevCloudWorkbenchValidationCommands, - ...requiredModeCommands - ]; - assertStringArray(report.validationCommands, `${label}.validationCommands`, { - minLength: requiredCommands.length - }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of requiredCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - for (const section of ["localSmoke", "dryRun", "devPreconditions"]) { - assertObject(report[section], `${label}.${section}`); - assertStatus(report[section].status, `${label}.${section}.status`); - assertStringArray(report[section].commands, `${label}.${section}.commands`, { minLength: 1 }); - assertStringArray(report[section].evidence ?? [], `${label}.${section}.evidence`, { minLength: section === "devPreconditions" ? 0 : 1 }); - assertString(report[section].summary, `${label}.${section}.summary`); - } - assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, { - minLength: 4 - }); - - assertArray(report.checks, `${label}.checks`); - assert.ok(report.checks.length >= 3, `${label}.checks must include HTTP plus either deployment identity or browser journey checks`); - assertUnique(report.checks.map((check) => check.id), `${label}.checks ids`); - const checks = new Map(report.checks.map((check, index) => { - const checkLabel = `${label}.checks[${index}]`; - assertObject(check, checkLabel); - for (const field of ["id", "status", "summary"]) { - assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`); - } - assertString(check.id, `${checkLabel}.id`); - assertStatus(check.status, `${checkLabel}.status`); - assertString(check.summary, `${checkLabel}.summary`); - if (Object.hasOwn(check, "evidence")) { - assertStringArray(check.evidence, `${checkLabel}.evidence`, { minLength: 1 }); - } - if (Object.hasOwn(check, "observations")) { - assertObject(check.observations, `${checkLabel}.observations`); - } - return [check.id, check]; - })); - const deploymentIdentityPreflight = - report.status === "blocked" && - report.safety?.liveMode === "deployment-identity-preflight" && - checks.has("live-runtime-current-main") && - checks.has("live-web-assets-current-main"); - if (deploymentIdentityPreflight) { - assertDevCloudWorkbenchDeploymentPreflight(report, label, checks); - return; - } - - if (report.mode === "dom-only") { - assertDevCloudWorkbenchDomOnlyReport(report, label, raw, checks); - return; - } - - for (const requiredCheck of [ - "live-http-html", - "live-browser-dom", - "live-api-runtime-readiness", - "live-code-agent-browser-journey" - ]) { - assert.ok(checks.has(requiredCheck), `${label}.checks missing ${requiredCheck}`); - } - - const hasDeploymentIdentityChecks = - checks.has("live-runtime-current-main") || - checks.has("live-web-assets-current-main") || - Object.hasOwn(report, "deploymentIdentity") || - Object.hasOwn(report, "webAssetIdentity"); - if (hasDeploymentIdentityChecks || report.status === "pass") { - assert.ok(checks.has("live-runtime-current-main"), `${label}.checks missing live-runtime-current-main`); - assert.ok(checks.has("live-web-assets-current-main"), `${label}.checks missing live-web-assets-current-main`); - assertDevCloudWorkbenchIdentityChecks( - report, - label, - checks.get("live-runtime-current-main"), - checks.get("live-web-assets-current-main"), - { requirePass: report.status === "pass" } - ); - } - - const httpCheck = checks.get("live-http-html"); - assert.equal(httpCheck.status, "pass", `${label}.live-http-html.status`); - assert.ok(httpCheck.evidence.includes("HTTP 200"), `${label}.live-http-html.evidence missing HTTP 200`); - assert.ok(httpCheck.evidence.includes("text/html; charset=utf-8"), `${label}.live-http-html.evidence missing content-type`); - - const dom = checks.get("live-browser-dom").observations; - assertObject(dom, `${label}.live-browser-dom.observations`); - assert.equal(dom.title, "HWLAB 云工作台", `${label}.live-browser-dom.title`); - assert.equal(dom.workspaceHidden, false, `${label}.live-browser-dom.workspaceHidden`); - assert.equal(dom.gateHidden, true, `${label}.live-browser-dom.gateHidden`); - assert.equal(dom.helpHidden, true, `${label}.live-browser-dom.helpHidden`); - assertObject(dom.coreControlsVisible, `${label}.live-browser-dom.coreControlsVisible`); - for (const control of ["commandInput", "commandSend", "agentChatStatus", "conversationList", "hardwareList"]) { - assert.equal(dom.coreControlsVisible[control], true, `${label}.live-browser-dom.coreControlsVisible.${control}`); - } - - const apiRuntimeReadinessCheck = checks.get("live-api-runtime-readiness"); - assert.equal( - apiRuntimeReadinessCheck.status, - apiRuntimeReadiness.status, - `${label}.live-api-runtime-readiness.status` - ); - assert.deepEqual( - apiRuntimeReadinessCheck.observations, - apiRuntimeReadiness, - `${label}.live-api-runtime-readiness.observations` - ); - - const journey = checks.get("live-code-agent-browser-journey"); - const acceptedJourneyStatuses = report.status === "degraded" ? ["pass", "degraded"] : [report.status]; - assert.ok( - acceptedJourneyStatuses.includes(journey.status), - `${label}.live-code-agent-browser-journey.status` - ); - assertObject(journey.observations, `${label}.live-code-agent-browser-journey.observations`); - assertObject(journey.observations.request, `${label}.live-code-agent-browser-journey.request`); - assert.equal(journey.observations.request.method, "POST", `${label}.live-code-agent-browser-journey.request.method`); - assert.equal(journey.observations.request.urlPath, "/v1/agent/chat", `${label}.live-code-agent-browser-journey.request.urlPath`); - assertObject(journey.observations.response, `${label}.live-code-agent-browser-journey.response`); - if (report.status === "blocked" && journey.status === "blocked") { - assertDevCloudWorkbenchCodeAgentBlocked(report, label, journey); - return; - } - if (journey.status === "degraded") { - assertDevCloudWorkbenchCodeAgentBackendTimeout(report, label, journey); - assertFreshDevCloudWorkbenchLiveEvidence(report, label, raw, journey); - return; - } - assert.equal(journey.observations.response.status, "completed", `${label}.live-code-agent-browser-journey.response.status`); - for (const field of ["provider", "model", "backend", "traceId"]) { - assertString(journey.observations.response[field], `${label}.live-code-agent-browser-journey.response.${field}`); - } - assert.equal(journey.observations.response.hasReply, true, `${label}.live-code-agent-browser-journey.response.hasReply`); - assert.equal(journey.observations.response.error, null, `${label}.live-code-agent-browser-journey.response.error`); - for (const forbiddenField of ["conversationId", "sessionId", "messageId", "reply"]) { - assert.equal( - Object.hasOwn(journey.observations.response, forbiddenField), - false, - `${label}.live-code-agent-browser-journey.response must not retain ${forbiddenField}` - ); - } - assertObject(journey.observations.ui, `${label}.live-code-agent-browser-journey.ui`); - assert.ok( - ["已回复", "DEV-LIVE 回复"].includes(journey.observations.ui.agentChatStatus), - `${label}.live-code-agent-browser-journey.ui.agentChatStatus` - ); - assert.equal(journey.observations.ui.completedMessageVisible, true, `${label}.live-code-agent-browser-journey.ui.completedMessageVisible`); - assert.equal(journey.observations.ui.failedMessageVisible, false, `${label}.live-code-agent-browser-journey.ui.failedMessageVisible`); - - assertBlockers(report.blockers, `${label}.blockers`); - - assertObject(report.safety, `${label}.safety`); - for (const field of ["prodTouched", "servicesRestarted", "heavyE2E", "hardwareWriteApis", "sourceIsDevLive"]) { - assert.equal(report.safety[field], false, `${label}.safety.${field}`); - } - assert.equal(report.safety.liveMode, "browser-user-journey-with-code-agent-post", `${label}.safety.liveMode`); - assertCodeAgentRetainedApiFields(report.safety.retainedApiFields, `${label}.safety.retainedApiFields`); - assertString(report.safety.statement, `${label}.safety.statement`); - if (report.status === "degraded") { - assertDevCloudWorkbenchDegradedSummary(report, label, apiRuntimeReadiness); - } - assertFreshDevCloudWorkbenchLiveEvidence(report, label, raw, journey); -} - -function assertDevCloudWorkbenchCodeAgentBlocked(report, label, journey) { - const response = journey.observations.response; - assert.ok(["failed", "blocked"].includes(response.status), `${label}.live-code-agent-browser-journey.response.status`); - assert.equal(response.hasReply, false, `${label}.live-code-agent-browser-journey.response.hasReply`); - assertObject(response.error, `${label}.live-code-agent-browser-journey.response.error`); - assert.ok( - ["provider_unavailable", "provider_blocked", "untrusted_completion"].includes(response.error.code), - `${label}.live-code-agent-browser-journey.response.error.code` - ); - if (response.error.providerStatus !== null && response.error.providerStatus !== undefined) { - assert.ok( - Number.isInteger(response.error.providerStatus) && response.error.providerStatus >= 100 && response.error.providerStatus <= 599, - `${label}.live-code-agent-browser-journey.response.error.providerStatus` - ); - } - for (const field of ["provider", "model", "backend", "traceId"]) { - assertString(response[field], `${label}.live-code-agent-browser-journey.response.${field}`); - } - for (const forbiddenField of ["conversationId", "sessionId", "messageId", "reply"]) { - assert.equal( - Object.hasOwn(response, forbiddenField), - false, - `${label}.live-code-agent-browser-journey.response must not retain ${forbiddenField}` - ); - } - - assertObject(journey.observations.ui, `${label}.live-code-agent-browser-journey.ui`); - assert.ok( - blockedCodeAgentUiLabels.has(journey.observations.ui.agentChatStatus), - `${label}.live-code-agent-browser-journey.ui.agentChatStatus` - ); - assert.equal(journey.observations.ui.completedMessageVisible, false, `${label}.live-code-agent-browser-journey.ui.completedMessageVisible`); - assert.equal(journey.observations.ui.failedMessageVisible, true, `${label}.live-code-agent-browser-journey.ui.failedMessageVisible`); - assertBlockers(report.blockers, `${label}.blockers`); - assert.ok( - report.blockers.some((blocker) => blocker.scope === "live-code-agent-browser-journey"), - `${label}.blockers must include live-code-agent-browser-journey` - ); - - assertObject(report.safety, `${label}.safety`); - for (const field of ["prodTouched", "servicesRestarted", "heavyE2E", "hardwareWriteApis", "sourceIsDevLive"]) { - assert.equal(report.safety[field], false, `${label}.safety.${field}`); - } - assert.equal(report.safety.liveMode, "browser-user-journey-with-code-agent-post", `${label}.safety.liveMode`); - assertCodeAgentRetainedApiFields(report.safety.retainedApiFields, `${label}.safety.retainedApiFields`); - assertString(report.safety.statement, `${label}.safety.statement`); -} - -function assertDevCloudWorkbenchCodeAgentBackendTimeout(report, label, journey) { - assert.equal(report.status, "degraded", `${label}.status`); - assertObject(journey.observations.classification, `${label}.live-code-agent-browser-journey.classification`); - assert.equal(journey.observations.classification.terminalState, "backend-timeout", `${label}.live-code-agent-browser-journey.classification.terminalState`); - assert.ok( - Number.isInteger(journey.observations.classification.backendTimeoutMs) && - journey.observations.classification.backendTimeoutMs >= 4500, - `${label}.live-code-agent-browser-journey.classification.backendTimeoutMs` - ); - assert.match( - journey.observations.classification.chineseSummary ?? "", - /后端超时/u, - `${label}.live-code-agent-browser-journey.classification.chineseSummary` - ); - assertArray(journey.observations.prompts, `${label}.live-code-agent-browser-journey.prompts`, { minLength: 1 }); - assert.ok( - journey.observations.prompts.every((prompt) => prompt.legacyWindow?.permanentFailureAround4500ms === false), - `${label}.live-code-agent-browser-journey.prompts must not include old 4500ms permanent failure` - ); - assert.equal(journey.observations.legacyFailureWindowMs, 4500, `${label}.live-code-agent-browser-journey.legacyFailureWindowMs`); - assertObject(report.safety, `${label}.safety`); - assert.equal(report.safety.liveMode, "browser-user-journey-with-code-agent-post", `${label}.safety.liveMode`); - assertCodeAgentRetainedApiFields(report.safety.retainedApiFields, `${label}.safety.retainedApiFields`); -} - -function assertDevCloudWorkbenchApiRuntimeReadiness(report, label) { - assertObject(report.runtimeIdentity, `${label}.runtimeIdentity`); - if (Object.hasOwn(report.runtimeIdentity, "ready")) { - assertBoolean(report.runtimeIdentity.ready, `${label}.runtimeIdentity.ready`); - } - - if (Object.hasOwn(report.runtimeIdentity, "runtime")) { - assertObject(report.runtimeIdentity.runtime, `${label}.runtimeIdentity.runtime`); - for (const field of ["durable", "ready"]) { - if (Object.hasOwn(report.runtimeIdentity.runtime, field)) { - assertBoolean(report.runtimeIdentity.runtime[field], `${label}.runtimeIdentity.runtime.${field}`); - } - } - if (Object.hasOwn(report.runtimeIdentity.runtime, "status")) { - assertString(report.runtimeIdentity.runtime.status, `${label}.runtimeIdentity.runtime.status`); - } - if (Object.hasOwn(report.runtimeIdentity.runtime, "blocker")) { - assertString(report.runtimeIdentity.runtime.blocker, `${label}.runtimeIdentity.runtime.blocker`); - } - } - - if (Object.hasOwn(report.runtimeIdentity, "readiness")) { - assertObject(report.runtimeIdentity.readiness, `${label}.runtimeIdentity.readiness`); - if (Object.hasOwn(report.runtimeIdentity.readiness, "ready")) { - assertBoolean(report.runtimeIdentity.readiness.ready, `${label}.runtimeIdentity.readiness.ready`); - } - if (Object.hasOwn(report.runtimeIdentity.readiness, "status")) { - assertString(report.runtimeIdentity.readiness.status, `${label}.runtimeIdentity.readiness.status`); - } - if (Object.hasOwn(report.runtimeIdentity.readiness, "durability")) { - assertObject(report.runtimeIdentity.readiness.durability, `${label}.runtimeIdentity.readiness.durability`); - if (Object.hasOwn(report.runtimeIdentity.readiness.durability, "ready")) { - assertBoolean(report.runtimeIdentity.readiness.durability.ready, `${label}.runtimeIdentity.readiness.durability.ready`); - } - for (const field of ["status", "blocker", "blockedLayer", "queryResult"]) { - if (Object.hasOwn(report.runtimeIdentity.readiness.durability, field)) { - assertString(report.runtimeIdentity.readiness.durability[field], `${label}.runtimeIdentity.readiness.durability.${field}`); - } - } - } - } - - if (Object.hasOwn(report.runtimeIdentity, "blockerCodes")) { - assertStringArray(report.runtimeIdentity.blockerCodes, `${label}.runtimeIdentity.blockerCodes`); - } - - const runtime = report.runtimeIdentity.runtime ?? {}; - const readiness = report.runtimeIdentity.readiness ?? {}; - const durability = readiness.durability ?? {}; - const blockerCodes = Array.isArray(report.runtimeIdentity.blockerCodes) ? report.runtimeIdentity.blockerCodes : []; - const durableBlocked = runtime.blocker === "runtime_durable_adapter_query_blocked" || - durability.blocker === "runtime_durable_adapter_query_blocked" || - blockerCodes.includes("runtime_durable_adapter_query_blocked") || - durability.blockedLayer === "durability_query" || - runtime.connection?.queryResult === "query_blocked" || - durability.queryResult === "query_blocked"; - const degraded = report.runtimeIdentity.healthStatus === "degraded" || - report.runtimeIdentity.ready === false || - runtime.ready === false || - durability.ready === false || - runtime.durable === false || - durableBlocked; - - const status = degraded ? "degraded" : "pass"; - const result = { - status, - healthStatus: report.runtimeIdentity.healthStatus, - ready: report.runtimeIdentity.ready === true, - runtimeDurable: runtime.durable === true, - runtimeReady: runtime.ready === true, - durabilityReady: durability.ready === true, - durableBlocked, - blockerCodes, - evidence: [ - `api.status=${report.runtimeIdentity.healthStatus}`, - `ready=${report.runtimeIdentity.ready === true}`, - `runtime.durable=${runtime.durable === true}`, - `runtime.ready=${runtime.ready === true}`, - `durability.ready=${durability.ready === true}`, - durableBlocked ? "runtime_durable_adapter_query_blocked" : null, - ...blockerCodes.map((code) => `blocker=${code}`) - ].filter(Boolean), - summary: degraded - ? "Live API is reachable but degraded/read-only; runtime durability is not ready, so this cannot be full DEV-LIVE acceptance." - : "Live API health and runtime durability are ready for browser journey evidence." - }; - - if (["pass", "degraded"].includes(report.status)) { - assert.equal(report.runtimeIdentity.status, "observed", `${label}.runtimeIdentity.status`); - assert.equal(report.runtimeIdentity.source, "health-live", `${label}.runtimeIdentity.source`); - assert.equal(report.runtimeIdentity.endpoint, `${ACTIVE_DEV_PUBLIC_ENDPOINT}/health/live`, `${label}.runtimeIdentity.endpoint`); - assert.equal(report.runtimeIdentity.serviceId, "hwlab-cloud-api", `${label}.runtimeIdentity.serviceId`); - assert.equal(report.runtimeIdentity.environment, "dev", `${label}.runtimeIdentity.environment`); - assert.match(report.runtimeIdentity.commitId, /^[a-f0-9]{7,40}$/, `${label}.runtimeIdentity.commitId`); - assert.notEqual(report.runtimeIdentity.commitId, "unknown", `${label}.runtimeIdentity.commitId`); - assert.notEqual(report.runtimeIdentity.commitSource, "source-git-head", `${label}.runtimeIdentity.commitSource`); - assert.notEqual(report.runtimeIdentity.commitSource, "hard-coded", `${label}.runtimeIdentity.commitSource`); - assert.notEqual(report.runtimeIdentity.commitSource, "literal", `${label}.runtimeIdentity.commitSource`); - - const generatedAtMs = Date.parse(report.generatedAt); - const runtimeObservedAtMs = Date.parse(report.runtimeIdentity.observedAt); - assert.ok( - Math.abs(generatedAtMs - runtimeObservedAtMs) <= devCloudWorkbenchRuntimeIdentityMaxSkewMs, - `${label}.runtimeIdentity.observedAt must be fresh relative to generatedAt` - ); - } - - if (report.status === "pass") { - assert.equal( - degraded, - false, - `${label}.runtimeIdentity degraded API or runtime durability blocker cannot be summarized as full DEV-LIVE accepted` - ); - } - - if (degraded) { - assert.ok( - ["degraded", "blocked"].includes(report.status), - `${label}.status must be degraded or blocked when API health or runtime durability is degraded` - ); - assert.equal(report.devLive, false, `${label}.devLive must remain false for degraded/read-only workbench evidence`); - assert.equal( - report.evidenceLevel, - report.status === "degraded" ? "DEV-LIVE-BROWSER-DEGRADED" : "BLOCKED", - `${label}.evidenceLevel` - ); - } - - return result; -} - -function assertDevCloudWorkbenchDegradedSummary(report, label, apiRuntimeReadiness) { - assert.equal(apiRuntimeReadiness.status, "degraded", `${label}.live-api-runtime-readiness.status`); - assert.equal(report.devPreconditions.status, "degraded", `${label}.devPreconditions.status`); - const summaryText = [ - report.reportLifecycle.summary, - report.devPreconditions.summary, - report.safety.statement - ].join(" "); - assert.match( - summaryText, - /degraded\/read-only mode|degraded\/read-only|read-only mode/u, - `${label} degraded API/runtime report must be summarized as deployed UI usable in degraded/read-only mode` - ); - assert.doesNotMatch( - summaryText, - /full DEV-LIVE accepted|full DEV-LIVE acceptance passed|M3\/M4\/M5 DEV-LIVE accepted|M[345]\s+(?:DEV-LIVE\s+)?(?:accepted|acceptance passed|green)/u, - `${label} degraded API/runtime report must not claim full M3/M4/M5 DEV-LIVE acceptance` - ); -} - -function assertCodeAgentRetainedApiFields(value, label) { - assertArray(value, label); - for (const field of ["status", "provider", "model", "backend", "traceId", "hasReply", "error.code", "error.missingEnv"]) { - assert.ok(value.includes(field), `${label} missing ${field}`); - } - for (const field of value) { - assert.ok( - ["status", "provider", "model", "backend", "traceId", "hasReply", "error.code", "error.missingEnv", "error.providerStatus"].includes(field), - `${label} contains unexpected retained field ${field}` - ); - } -} - -async function validateDevCloudWorkbenchLayoutReport(report, label) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "status", - "mode", - "sourceMode", - "url", - "evidenceLevel", - "devLive", - "sourceContract", - "validationCommands", - "localSmoke", - "dryRun", - "devPreconditions", - "checks", - "blockers", - "artifacts", - "safety" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, issueFamily.DEV_CLOUD_WORKBENCH_LAYOUT, `${label}.issue`); - assert.equal(report.taskId, "dev-cloud-workbench-layout", `${label}.taskId`); - assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`); - assert.equal(report.acceptanceLevel, "dev_cloud_workbench_layout", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assertStatus(report.status, `${label}.status`); - assert.ok(["pass", "blocked", "skip"].includes(report.status), `${label}.status`); - assert.equal(report.mode, "layout-browser", `${label}.mode`); - assert.ok(["source-static", "local-build", "dev-live"].includes(report.sourceMode), `${label}.sourceMode`); - assert.equal(report.devLive, false, `${label}.devLive`); - assert.equal( - report.evidenceLevel, - report.sourceMode === "dev-live" ? report.status === "pass" ? "DEV-LIVE-LAYOUT" : "BLOCKED" : "SOURCE", - `${label}.evidenceLevel` - ); - if (report.sourceMode === "dev-live") { - assert.equal(report.url, "http://74.48.78.17:16666/", `${label}.url`); - } - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - await assertDocumentSet( - report.sourceContract.documents, - `${label}.sourceContract.documents`, - requiredDevCloudWorkbenchDocs - ); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { - minLength: requiredDevCloudWorkbenchLayoutValidationCommands.length - }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of requiredDevCloudWorkbenchLayoutValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - for (const section of ["localSmoke", "dryRun", "devPreconditions"]) { - assertObject(report[section], `${label}.${section}`); - assertStatus(report[section].status, `${label}.${section}.status`); - assertStringArray(report[section].commands, `${label}.${section}.commands`, { minLength: 1 }); - assertStringArray(report[section].evidence ?? [], `${label}.${section}.evidence`, { - minLength: section === "devPreconditions" ? 0 : 1 - }); - assertString(report[section].summary, `${label}.${section}.summary`); - } - assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, { - minLength: 4 - }); - - assertArray(report.viewports, `${label}.viewports`); - assert.deepEqual( - report.viewports.map((viewport) => `${viewport.width}x${viewport.height}`), - ["1366x768", "1024x768", "390x844"], - `${label}.viewports` - ); - for (const [index, viewport] of report.viewports.entries()) { - assertObject(viewport, `${label}.viewports[${index}]`); - assertString(viewport.id, `${label}.viewports[${index}].id`); - assert.equal(Number.isInteger(viewport.width), true, `${label}.viewports[${index}].width`); - assert.equal(Number.isInteger(viewport.height), true, `${label}.viewports[${index}].height`); - } - - assertArray(report.checks, `${label}.checks`); - if (report.status === "skip") { - assert.ok(report.checks.length >= 1, `${label}.checks must explain the skipped layout dependency`); - } else { - assert.ok(report.checks.length >= 11, `${label}.checks must cover desktop, mobile, and gate layout checks`); - } - assertUnique(report.checks.map((check) => check.id), `${label}.checks ids`); - const checks = new Map(report.checks.map((check, index) => { - const checkLabel = `${label}.checks[${index}]`; - assertObject(check, checkLabel); - for (const field of ["id", "status", "summary"]) { - assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`); - } - assertString(check.id, `${checkLabel}.id`); - assertStatus(check.status, `${checkLabel}.status`); - assertString(check.summary, `${checkLabel}.summary`); - if (Object.hasOwn(check, "viewport")) { - assertViewportObject(check.viewport, `${checkLabel}.viewport`); - } - if (Object.hasOwn(check, "observations")) { - assertObject(check.observations, `${checkLabel}.observations`); - } - return [check.id, check]; - })); - - if (report.status !== "skip") { - for (const requiredCheck of [ - "layout-desktop-default", - "layout-narrow-desktop-default", - "layout-mobile-default", - "layout-feedback-352-resource-explorer-removed", - "layout-left-sidebar-collapse", - "layout-right-sidebar-resize-desktop", - "layout-right-sidebar-resize-narrow-desktop", - "layout-right-sidebar-resize-mobile", - "layout-gate-desktop", - "layout-gate-narrow-desktop", - "layout-gate-mobile" - ]) { - assert.ok(checks.has(requiredCheck), `${label}.checks missing ${requiredCheck}`); - } - } - - assertBlockers(report.blockers, `${label}.blockers`); - assertArray(report.failures ?? [], `${label}.failures`); - for (const [index, failure] of (report.failures ?? []).entries()) { - assertLayoutFailure(failure, `${label}.failures[${index}]`); - } - if (report.status === "pass") { - assert.equal(report.blockers.length, 0, `${label}.blockers`); - assert.equal((report.failures ?? []).length, 0, `${label}.failures`); - } else { - assert.ok(report.blockers.length > 0 || (report.failures ?? []).length > 0, `${label} blocked/skip status must carry blockers or failures`); - } - - if (Object.hasOwn(report, "skipped")) { - assertArray(report.skipped, `${label}.skipped`); - for (const [index, skipped] of report.skipped.entries()) { - assertObject(skipped, `${label}.skipped[${index}]`); - assertString(skipped.checkId, `${label}.skipped[${index}].checkId`); - assert.equal(skipped.failureType, "skip", `${label}.skipped[${index}].failureType`); - assertString(skipped.summary, `${label}.skipped[${index}].summary`); - } - } - - assertObject(report.artifacts, `${label}.artifacts`); - assertString(report.artifacts.screenshotDir, `${label}.artifacts.screenshotDir`); - if (Object.hasOwn(report.artifacts, "reportPath") && report.artifacts.reportPath !== null) { - assertString(report.artifacts.reportPath, `${label}.artifacts.reportPath`); - } - assertArray(report.artifacts.screenshots ?? [], `${label}.artifacts.screenshots`); - for (const [index, screenshot] of (report.artifacts.screenshots ?? []).entries()) { - assertObject(screenshot, `${label}.artifacts.screenshots[${index}]`); - assertString(screenshot.selector, `${label}.artifacts.screenshots[${index}].selector`); - assertViewportObject(screenshot.viewport, `${label}.artifacts.screenshots[${index}].viewport`); - assertString(screenshot.path, `${label}.artifacts.screenshots[${index}].path`); - } - - assertObject(report.safety, `${label}.safety`); - assert.equal(report.safety.layoutOnly, true, `${label}.safety.layoutOnly`); - assert.equal(report.safety.codeAgentPostSent, false, `${label}.safety.codeAgentPostSent`); - assert.equal(report.safety.hardwareWriteApis, false, `${label}.safety.hardwareWriteApis`); - assert.equal(report.safety.sourceIsDevLive, false, `${label}.safety.sourceIsDevLive`); - assert.match(report.safety.hitTestMethod, /elementsFromPoint/u, `${label}.safety.hitTestMethod`); - assert.match(report.safety.statement, /not claim M3 DEV-LIVE hardware-loop acceptance/u, `${label}.safety.statement`); -} - -function assertViewportObject(value, label) { - assertObject(value, label); - assert.equal(Number.isInteger(value.width), true, `${label}.width`); - assert.equal(Number.isInteger(value.height), true, `${label}.height`); -} - -function assertLayoutFailure(value, label) { - assertObject(value, label); - if (Object.hasOwn(value, "checkId")) { - assertString(value.checkId, `${label}.checkId`); - } - assert.ok(Object.hasOwn(value, "viewport"), `${label} missing viewport`); - if (value.viewport !== null) { - assertViewportObject(value.viewport, `${label}.viewport`); - } - assert.ok(Object.hasOwn(value, "selector"), `${label} missing selector`); - if (value.selector !== null) { - assertString(value.selector, `${label}.selector`); - } - assertString(value.failureType, `${label}.failureType`); - assert.ok( - [ - "overlap", - "covered-hit-target", - "overflow", - "outer-scroll-regression", - "screenshot-diff", - "blocked", - "blocked/skip", - "skip" - ].includes(value.failureType), - `${label}.failureType` - ); - assertString(value.summary, `${label}.summary`); -} - -function assertDevCloudWorkbenchDeploymentPreflight(report, label, checks) { - for (const requiredCheck of [ - "live-runtime-current-main", - "live-http-html", - "live-web-assets-current-main", - "live-code-agent-browser-journey" - ]) { - assert.ok(checks.has(requiredCheck), `${label}.checks missing ${requiredCheck}`); - } - - const runtimeCheck = checks.get("live-runtime-current-main"); - const webAssetCheck = checks.get("live-web-assets-current-main"); - const journey = checks.get("live-code-agent-browser-journey"); - assert.ok(["pass", "blocked"].includes(runtimeCheck.status), `${label}.live-runtime-current-main.status`); - assert.ok(["pass", "blocked"].includes(webAssetCheck.status), `${label}.live-web-assets-current-main.status`); - assert.equal(journey.status, "blocked", `${label}.live-code-agent-browser-journey.status`); - assert.ok( - runtimeCheck.status === "blocked" || webAssetCheck.status === "blocked", - `${label}.deployment preflight requires runtime or web asset drift` - ); - assertObject(runtimeCheck.observations, `${label}.live-runtime-current-main.observations`); - assertObject(webAssetCheck.observations, `${label}.live-web-assets-current-main.observations`); - assertObject(report.expectedRuntimeIdentity, `${label}.expectedRuntimeIdentity`); - for (const field of ["status", "serviceId", "source", "commitId", "imageTag", "image", "summary"]) { - assertString(report.expectedRuntimeIdentity[field], `${label}.expectedRuntimeIdentity.${field}`); - } - assert.ok(["observed", "not_observed"].includes(report.expectedRuntimeIdentity.status), `${label}.expectedRuntimeIdentity.status`); - assertObject(report.deploymentIdentity, `${label}.deploymentIdentity`); - assert.equal(report.deploymentIdentity.status, runtimeCheck.status, `${label}.deploymentIdentity.status`); - assertString(report.deploymentIdentity.expectedCommit, `${label}.deploymentIdentity.expectedCommit`); - assertString(report.deploymentIdentity.expectedImageTag, `${label}.deploymentIdentity.expectedImageTag`); - assertString(report.deploymentIdentity.expectedSource, `${label}.deploymentIdentity.expectedSource`); - assertString(report.deploymentIdentity.observedRuntimeCommit, `${label}.deploymentIdentity.observedRuntimeCommit`); - assertString(report.deploymentIdentity.observedImageTag, `${label}.deploymentIdentity.observedImageTag`); - if (report.deploymentIdentity.status === "blocked") { - assertString(report.deploymentIdentity.reason, `${label}.deploymentIdentity.reason`); - } - assertString(report.deploymentIdentity.summary, `${label}.deploymentIdentity.summary`); - assertStringArray(report.deploymentIdentity.evidence, `${label}.deploymentIdentity.evidence`, { minLength: 4 }); - assert.deepEqual( - runtimeCheck.observations, - report.deploymentIdentity, - `${label}.live-runtime-current-main.observations` - ); - - assertObject(report.webAssetIdentity, `${label}.webAssetIdentity`); - assert.equal(report.webAssetIdentity.status, webAssetCheck.status, `${label}.webAssetIdentity.status`); - assertArray(report.webAssetIdentity.assets, `${label}.webAssetIdentity.assets`); - assertStringArray(report.webAssetIdentity.mismatches, `${label}.webAssetIdentity.mismatches`, { - minLength: report.webAssetIdentity.status === "blocked" ? 1 : 0 - }); - assertString(report.webAssetIdentity.summary, `${label}.webAssetIdentity.summary`); - for (const [index, asset] of report.webAssetIdentity.assets.entries()) { - const assetLabel = `${label}.webAssetIdentity.assets[${index}]`; - assertObject(asset, assetLabel); - for (const field of ["path", "status", "contentType", "sourceHash", "liveHash"]) { - assertString(asset[field], `${assetLabel}.${field}`); - } - assert.ok(["match", "mismatch", "fetch_failed"].includes(asset.status), `${assetLabel}.status`); - assert.ok(Number.isInteger(asset.sourceLength), `${assetLabel}.sourceLength`); - assert.ok(Number.isInteger(asset.liveLength), `${assetLabel}.liveLength`); - } - assert.deepEqual( - webAssetCheck.observations, - report.webAssetIdentity, - `${label}.live-web-assets-current-main.observations` - ); - assertDevCloudWorkbenchDeploymentPreflightJourney(report, label, journey); - - assertBlockers(report.blockers, `${label}.blockers`); - if (runtimeCheck.status === "blocked") { - assert.ok( - report.blockers.some((blocker) => blocker.scope === "live-runtime-current-main"), - `${label}.blockers must include runtime identity drift` - ); - } - if (webAssetCheck.status === "blocked") { - assert.ok( - report.blockers.some((blocker) => blocker.scope === "live-web-assets-current-main"), - `${label}.blockers must include web asset drift` - ); - } - - assertObject(report.safety, `${label}.safety`); - for (const field of ["prodTouched", "servicesRestarted", "heavyE2E", "hardwareWriteApis", "sourceIsDevLive", "codeAgentBrowserJourneySkipped"]) { - assert.equal(report.safety[field], field === "codeAgentBrowserJourneySkipped", `${label}.safety.${field}`); - } - assert.equal(report.safety.liveMode, "deployment-identity-preflight", `${label}.safety.liveMode`); - assert.deepEqual( - report.safety.retainedApiFields, - ["runtime.commitId", "runtime.imageTag", "web asset hash/status"], - `${label}.safety.retainedApiFields` - ); - assertString(report.safety.statement, `${label}.safety.statement`); -} - -function assertDevCloudWorkbenchDeploymentPreflightJourney(report, label, journey) { - assertStringArray(journey.evidence, `${label}.live-code-agent-browser-journey.evidence`, { minLength: 3 }); - assert.ok( - journey.evidence.includes("POST /v1/agent/chat not sent"), - `${label}.live-code-agent-browser-journey.evidence missing not_sent proof` - ); - assertObject(journey.observations, `${label}.live-code-agent-browser-journey.observations`); - assertObject(journey.observations.request, `${label}.live-code-agent-browser-journey.request`); - assert.equal(journey.observations.request.method, "POST", `${label}.live-code-agent-browser-journey.request.method`); - assert.equal(journey.observations.request.status, "not_sent", `${label}.live-code-agent-browser-journey.request.status`); - assert.equal(journey.observations.request.urlPath, "/v1/agent/chat", `${label}.live-code-agent-browser-journey.request.urlPath`); - assertObject(journey.observations.response, `${label}.live-code-agent-browser-journey.response`); - assert.equal(journey.observations.response.status, "failed", `${label}.live-code-agent-browser-journey.response.status`); - assert.equal(journey.observations.response.hasReply, false, `${label}.live-code-agent-browser-journey.response.hasReply`); - assertObject(journey.observations.response.error, `${label}.live-code-agent-browser-journey.response.error`); - assert.equal( - journey.observations.response.error.code, - deploymentPreflightCodeAgentErrorCode, - `${label}.live-code-agent-browser-journey.response.error.code` - ); - for (const field of ["provider", "model", "backend", "traceId"]) { - assert.equal(journey.observations.response[field], "not_observed", `${label}.live-code-agent-browser-journey.response.${field}`); - } - assertObject(journey.observations.classification, `${label}.live-code-agent-browser-journey.classification`); - assert.equal(journey.observations.classification.status, "blocked", `${label}.live-code-agent-browser-journey.classification.status`); - assert.equal( - journey.observations.classification.reason, - deploymentPreflightCodeAgentErrorCode, - `${label}.live-code-agent-browser-journey.classification.reason` - ); - assert.equal( - journey.observations.classification.codeAgentBrowserJourneySkipped, - true, - `${label}.live-code-agent-browser-journey.classification.codeAgentBrowserJourneySkipped` - ); - const journeyText = JSON.stringify(journey.observations); - assert.equal( - /"status"\s*:\s*200|completed|provider_unavailable|OPENAI_API_KEY/u.test(journeyText), - false, - `${label}.live-code-agent-browser-journey must not retain sent, completed, or secret-dependent evidence during deployment preflight` - ); - assert.ok( - report.blockers.some((blocker) => blocker.scope === "live-code-agent-browser-journey"), - `${label}.blockers must include live-code-agent-browser-journey` - ); -} - -function assertDevCloudWorkbenchDomOnlyReport(report, label, raw, checks) { - assert.equal(report.mode, "dom-only", `${label}.mode`); - for (const requiredCheck of [ - "live-runtime-current-main", - "live-api-runtime-readiness", - "live-http-html", - "live-web-assets-current-main", - "live-browser-dom", - "live-gate-route", - "live-help-markdown-route", - "live-code-agent-browser-journey" - ]) { - assert.ok(checks.has(requiredCheck), `${label}.checks missing ${requiredCheck}`); - } - - const runtimeCheck = checks.get("live-runtime-current-main"); - const apiRuntimeReadinessCheck = checks.get("live-api-runtime-readiness"); - const webAssetCheck = checks.get("live-web-assets-current-main"); - const apiRuntimeReadiness = assertDevCloudWorkbenchApiRuntimeReadiness(report, label); - assert.ok(["pass", "blocked"].includes(runtimeCheck.status), `${label}.live-runtime-current-main.status`); - assert.equal( - apiRuntimeReadinessCheck.status, - apiRuntimeReadiness.status, - `${label}.live-api-runtime-readiness.status` - ); - assert.deepEqual( - apiRuntimeReadinessCheck.observations, - apiRuntimeReadiness, - `${label}.live-api-runtime-readiness.observations` - ); - assert.ok(["pass", "blocked"].includes(webAssetCheck.status), `${label}.live-web-assets-current-main.status`); - assertObject(runtimeCheck.observations, `${label}.live-runtime-current-main.observations`); - assertObject(webAssetCheck.observations, `${label}.live-web-assets-current-main.observations`); - assertObject(report.expectedRuntimeIdentity, `${label}.expectedRuntimeIdentity`); - assertObject(report.deploymentIdentity, `${label}.deploymentIdentity`); - assert.equal(report.deploymentIdentity.status, runtimeCheck.status, `${label}.deploymentIdentity.status`); - assertObject(report.webAssetIdentity, `${label}.webAssetIdentity`); - assert.equal(report.webAssetIdentity.status, webAssetCheck.status, `${label}.webAssetIdentity.status`); - assertArray(report.webAssetIdentity.assets, `${label}.webAssetIdentity.assets`); - assertString(report.webAssetIdentity.summary, `${label}.webAssetIdentity.summary`); - assertDevCloudWorkbenchIdentityChecks(report, label, runtimeCheck, webAssetCheck, { - requirePass: report.status === "pass" - }); - - const domCheck = checks.get("live-browser-dom"); - if (domCheck.status === "pass") { - assertDevCloudWorkbenchDomObservations(domCheck.observations, `${label}.live-browser-dom`); - } else { - assert.ok(["blocked", "skip"].includes(domCheck.status), `${label}.live-browser-dom.status`); - } - - const helpCheck = checks.get("live-help-markdown-route"); - assert.ok(["pass", "blocked", "skip"].includes(helpCheck.status), `${label}.live-help-markdown-route.status`); - - const gateCheck = checks.get("live-gate-route"); - if (gateCheck.status === "pass") { - assertDevCloudWorkbenchGateRoute(gateCheck.observations, `${label}.live-gate-route`); - } else { - assert.ok(["blocked", "skip"].includes(gateCheck.status), `${label}.live-gate-route.status`); - } - - assertDevCloudWorkbenchDomOnlyJourney(report, label, checks.get("live-code-agent-browser-journey")); - assertBlockers(report.blockers, `${label}.blockers`); - - assertObject(report.safety, `${label}.safety`); - for (const field of ["prodTouched", "servicesRestarted", "heavyE2E", "hardwareWriteApis", "sourceIsDevLive"]) { - assert.equal(report.safety[field], false, `${label}.safety.${field}`); - } - assert.equal(report.safety.liveMode, "dom-only-readonly-no-code-agent-post", `${label}.safety.liveMode`); - assert.equal(report.safety.codeAgentBrowserJourneySkipped, true, `${label}.safety.codeAgentBrowserJourneySkipped`); - assert.equal(report.safety.codeAgentPostSent, false, `${label}.safety.codeAgentPostSent`); - assertStringArray(report.safety.retainedApiFields, `${label}.safety.retainedApiFields`, { minLength: 4 }); - assertString(report.safety.statement, `${label}.safety.statement`); - assert.equal(/Secret|M3\/M4\/M5|\/v1\/agent\/chat/u.test(report.safety.statement), true, `${label}.safety.statement`); - if (apiRuntimeReadiness.status === "degraded") { - assert.equal(report.status, "blocked", `${label}.status`); - assert.match( - report.safety.statement, - /must not be used to infer M3\/M4\/M5 acceptance|claims M3\/M4\/M5 acceptance/u, - `${label}.safety.statement` - ); - } - - const serializedReport = JSON.stringify(report); - assert.equal(/"method"\s*:\s*"POST"[\s\S]{0,200}"status"\s*:\s*(200|201|202)|"completed"/u.test(serializedReport), false, `${label} must not include a sent or completed Code Agent POST`); - assertFreshDevCloudWorkbenchDomOnlyEvidence(report, label, raw); -} - -function assertDevCloudWorkbenchDomObservations(dom, label) { - assertObject(dom, `${label}.observations`); - assert.equal(dom.title, "HWLAB 云工作台", `${label}.title`); - assert.equal(dom.workspaceHidden, false, `${label}.workspaceHidden`); - assert.equal(dom.gateHidden, true, `${label}.gateHidden`); - assert.equal(dom.helpHidden, true, `${label}.helpHidden`); - assert.equal(dom.navLabelsPresent, true, `${label}.navLabelsPresent`); - assert.equal(dom.defaultBackstageHidden, true, `${label}.defaultBackstageHidden`); - assert.equal(dom.gateRouteAvailable, true, `${label}.gateRouteAvailable`); - if (Object.hasOwn(dom, "codeAgentPostGuard")) { - assertObject(dom.codeAgentPostGuard, `${label}.codeAgentPostGuard`); - assert.equal(dom.codeAgentPostGuard.installed, true, `${label}.codeAgentPostGuard.installed`); - assert.equal(dom.codeAgentPostGuard.blockedPath, "/v1/agent/chat", `${label}.codeAgentPostGuard.blockedPath`); - assert.equal(dom.codeAgentPostGuard.attemptedCount, 0, `${label}.codeAgentPostGuard.attemptedCount`); - } - assert.equal(dom.wiringLayout, "two-column-long-table", `${label}.wiringLayout`); - assertStringArray(dom.wiringHeaderDevices, `${label}.wiringHeaderDevices`, { minLength: 2 }); - assert.equal(dom.wiringHeaderDevices.includes("res_boxsimu_1"), true, `${label}.wiringHeaderDevices missing res_boxsimu_1`); - assert.equal(dom.wiringHeaderDevices.includes("res_boxsimu_2"), true, `${label}.wiringHeaderDevices missing res_boxsimu_2`); - assert.equal(dom.wiringHeaderCount, 2, `${label}.wiringHeaderCount`); - assertStringArray(dom.legacyWiringHeaders, `${label}.legacyWiringHeaders`); - assert.equal(dom.legacyWiringHeaders.length, 0, `${label}.legacyWiringHeaders must not restore legacy wide columns`); - assertArray(dom.wiringBodyCellCounts, `${label}.wiringBodyCellCounts`); - for (const count of dom.wiringBodyCellCounts) { - assert.equal(count, 2, `${label}.wiringBodyCellCounts must stay two-column`); - } - assertArray(dom.m3WiringRows, `${label}.m3WiringRows`); - assert.ok( - dom.m3WiringRows.some((row) => - Array.isArray(row) && - row.length === 2 && - row[0].includes("DO1") && - row[0].includes("hwlab-patch-panel") && - row[1].includes("DI1") && - row[1].includes("证据来源") && - row[1].includes("轨迹/证据") - ), - `${label}.m3WiringRows missing required M3 route` - ); - assert.match(dom.wiringSummaryText, /hwlab-patch-panel/u, `${label}.wiringSummaryText missing patch-panel`); - assert.match(dom.wiringSummaryText, /状态/u, `${label}.wiringSummaryText missing status`); - assert.match(dom.wiringSummaryText, /证据来源/u, `${label}.wiringSummaryText missing evidence source`); - assert.match(dom.wiringSummaryText, /轨迹\/证据/u, `${label}.wiringSummaryText missing trace/evidence`); - assertObject(dom.coreControlsVisible, `${label}.coreControlsVisible`); - for (const control of ["commandInput", "commandSend", "agentChatStatus", "conversationList", "hardwareList", "wiringTab", "recordsTab"]) { - assert.equal(dom.coreControlsVisible[control], true, `${label}.coreControlsVisible.${control}`); - } -} - -function assertDevCloudWorkbenchGateRoute(gate, label) { - assertObject(gate, `${label}.observations`); - assert.equal(gate.pathname, "/gate", `${label}.pathname`); - assert.equal(gate.title, "HWLAB 云工作台", `${label}.title`); - assert.equal(gate.workspaceHidden, true, `${label}.workspaceHidden`); - assert.equal(gate.gateHidden, false, `${label}.gateHidden`); - assert.equal(gate.helpHidden, true, `${label}.helpHidden`); - assert.equal(gate.heading, "内部复核", `${label}.heading`); - assert.equal(gate.tableCount, 1, `${label}.tableCount`); - assert.equal(gate.mainTableCount, 1, `${label}.mainTableCount`); - assert.equal(gate.requiredHeadersPresent, true, `${label}.requiredHeadersPresent`); - assert.equal(gate.rowsPresent, true, `${label}.rowsPresent`); - assert.equal(gate.statusLabelsChinese, true, `${label}.statusLabelsChinese`); - assert.equal(gate.liveBackendOrBlockerVisible, true, `${label}.liveBackendOrBlockerVisible`); - assert.equal(gate.legacyLayoutAbsent, true, `${label}.legacyLayoutAbsent`); - assert.equal(gate.userOperationsAbsent, true, `${label}.userOperationsAbsent`); - assert.equal(gate.blockedRowsGreen, false, `${label}.blockedRowsGreen`); - assert.equal(gate.oldDashboardTermsAbsent, true, `${label}.oldDashboardTermsAbsent`); - assert.equal(gate.backendRouteRequested, true, `${label}.backendRouteRequested`); - assertStringArray(gate.tableHeaders, `${label}.tableHeaders`, { minLength: 8 }); - for (const column of ["类别", "检查项", "状态", "归因对象", "关键细节", "证据/trace", "更新时间", "下一步"]) { - assert.ok(gate.tableHeaders.includes(column), `${label}.tableHeaders missing ${column}`); - } - assertStringArray(gate.tableStatuses, `${label}.tableStatuses`, { minLength: 1 }); - for (const status of gate.tableStatuses) { - assert.ok(["通过", "阻塞", "失败", "待验证", "信息"].includes(status), `${label}.tableStatuses unexpected ${status}`); - } - assertArray(gate.tableRows, `${label}.tableRows`); - assertObject(gate.controls, `${label}.controls`); - assert.equal(gate.controls.statusFilter, true, `${label}.controls.statusFilter`); - assert.equal(gate.controls.search, true, `${label}.controls.search`); - assert.equal(gate.controls.refresh, true, `${label}.controls.refresh`); - assert.ok(gate.controls.controlCount <= 4, `${label}.controls.controlCount`); - if (Object.hasOwn(gate, "codeAgentPostGuard")) { - assertObject(gate.codeAgentPostGuard, `${label}.codeAgentPostGuard`); - assert.equal(gate.codeAgentPostGuard.installed, true, `${label}.codeAgentPostGuard.installed`); - assert.equal(gate.codeAgentPostGuard.blockedPath, "/v1/agent/chat", `${label}.codeAgentPostGuard.blockedPath`); - assert.equal(gate.codeAgentPostGuard.attemptedCount, 0, `${label}.codeAgentPostGuard.attemptedCount`); - } -} - -function assertDevCloudWorkbenchDomOnlyJourney(report, label, journey) { - assert.equal(journey.status, "not_applicable", `${label}.live-code-agent-browser-journey.status`); - assertStringArray(journey.evidence, `${label}.live-code-agent-browser-journey.evidence`, { minLength: 3 }); - assert.ok( - journey.evidence.includes("POST /v1/agent/chat not sent"), - `${label}.live-code-agent-browser-journey.evidence missing not-sent proof` - ); - assertObject(journey.observations, `${label}.live-code-agent-browser-journey.observations`); - assertObject(journey.observations.request, `${label}.live-code-agent-browser-journey.request`); - assert.equal(journey.observations.request.method, "POST", `${label}.live-code-agent-browser-journey.request.method`); - assert.equal(journey.observations.request.status, "not_applicable", `${label}.live-code-agent-browser-journey.request.status`); - assert.equal(journey.observations.request.urlPath, "/v1/agent/chat", `${label}.live-code-agent-browser-journey.request.urlPath`); - assertObject(journey.observations.response, `${label}.live-code-agent-browser-journey.response`); - assert.equal(journey.observations.response.status, "not_applicable", `${label}.live-code-agent-browser-journey.response.status`); - assert.equal(journey.observations.response.hasReply, false, `${label}.live-code-agent-browser-journey.response.hasReply`); - assert.equal(journey.observations.response.error, null, `${label}.live-code-agent-browser-journey.response.error`); - for (const field of ["provider", "model", "backend", "traceId"]) { - assert.equal(journey.observations.response[field], "not_observed", `${label}.live-code-agent-browser-journey.response.${field}`); - } - assertObject(journey.observations.classification, `${label}.live-code-agent-browser-journey.classification`); - assert.equal(journey.observations.classification.status, "not_applicable", `${label}.live-code-agent-browser-journey.classification.status`); - assert.equal(journey.observations.classification.reason, "dom_only_no_code_agent_post", `${label}.live-code-agent-browser-journey.classification.reason`); - assert.equal(journey.observations.classification.codeAgentBrowserJourneySkipped, true, `${label}.live-code-agent-browser-journey.classification.codeAgentBrowserJourneySkipped`); - assertArray(journey.observations.networkEvents ?? [], `${label}.live-code-agent-browser-journey.networkEvents`); - assert.equal((journey.observations.networkEvents ?? []).length, 0, `${label}.live-code-agent-browser-journey.networkEvents`); - assert.equal(report.devLive, false, `${label}.devLive`); -} - -function assertDevCloudWorkbenchIdentity(report, label) { - assertObject(report.sourceIdentity, `${label}.sourceIdentity`); - for (const field of ["status", "source", "commitId", "shortCommitId", "ref", "worktreeState", "reportCommitId", "summary"]) { - assert.ok(Object.hasOwn(report.sourceIdentity, field), `${label}.sourceIdentity missing ${field}`); - assertString(report.sourceIdentity[field], `${label}.sourceIdentity.${field}`); - } - assert.ok(["observed", "unknown"].includes(report.sourceIdentity.status), `${label}.sourceIdentity.status`); - assert.ok(["git-head", "environment", "unknown"].includes(report.sourceIdentity.source), `${label}.sourceIdentity.source`); - assert.match(report.sourceIdentity.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.sourceIdentity.commitId`); - assert.match(report.sourceIdentity.shortCommitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.sourceIdentity.shortCommitId`); - assert.ok(["clean", "dirty", "unknown"].includes(report.sourceIdentity.worktreeState), `${label}.sourceIdentity.worktreeState`); - if (report.sourceIdentity.source === "environment") { - assertString(report.sourceIdentity.environmentKey, `${label}.sourceIdentity.environmentKey`); - } - if (report.sourceIdentity.worktreeState === "clean" && report.sourceIdentity.commitId !== "unknown") { - assert.match(report.sourceIdentity.reportCommitId, /^[a-f0-9]{7,40}$/, `${label}.sourceIdentity.reportCommitId`); - assert.equal(report.commitId, report.sourceIdentity.reportCommitId, `${label}.commitId must match clean source reportCommitId`); - } else { - assert.equal(report.sourceIdentity.reportCommitId, "unknown", `${label}.sourceIdentity.reportCommitId`); - assert.equal(report.commitId, "unknown", `${label}.commitId must be unknown when source worktree identity is dirty or unknown`); - } - if (report.sourceIdentity.dirty !== null) { - assertBoolean(report.sourceIdentity.dirty, `${label}.sourceIdentity.dirty`); - assert.equal(report.sourceIdentity.dirty, report.sourceIdentity.worktreeState === "dirty", `${label}.sourceIdentity.dirty`); - } - - assertObject(report.runtimeIdentity, `${label}.runtimeIdentity`); - for (const field of ["status", "source", "endpoint", "serviceId", "environment", "healthStatus", "commitId", "commitSource", "imageTag", "observedAt", "summary"]) { - assert.ok(Object.hasOwn(report.runtimeIdentity, field), `${label}.runtimeIdentity missing ${field}`); - assertString(report.runtimeIdentity[field], `${label}.runtimeIdentity.${field}`); - } - assert.ok(["observed", "not_observed"].includes(report.runtimeIdentity.status), `${label}.runtimeIdentity.status`); - assert.equal(report.runtimeIdentity.source, "health-live", `${label}.runtimeIdentity.source`); - assert.equal(report.runtimeIdentity.endpoint, "http://74.48.78.17:16667/health/live", `${label}.runtimeIdentity.endpoint`); - assertTimestamp(report.runtimeIdentity.observedAt, `${label}.runtimeIdentity.observedAt`); - assert.match(report.runtimeIdentity.commitId, /^([a-f0-9]{7,40}|unknown|not_observed)$/, `${label}.runtimeIdentity.commitId`); - if (report.runtimeIdentity.status === "not_observed") { - assert.equal(report.runtimeIdentity.commitId, "not_observed", `${label}.runtimeIdentity.commitId`); - assertString(report.runtimeIdentity.reason, `${label}.runtimeIdentity.reason`); - } else { - assert.ok( - ["hwlab-cloud-api", "unknown"].includes(report.runtimeIdentity.serviceId), - `${label}.runtimeIdentity.serviceId` - ); - assert.ok(["dev", "unknown"].includes(report.runtimeIdentity.environment), `${label}.runtimeIdentity.environment`); - } - assert.ok( - report.sourceIdentity.commitId === "unknown" || - report.runtimeIdentity.commitId === "unknown" || - report.runtimeIdentity.commitId === "not_observed" || - report.runtimeIdentity.commitSource !== "source-git-head", - `${label}.runtimeIdentity must not present source git HEAD as the deployed live revision` - ); -} - -function assertDevCloudWorkbenchIdentityChecks(report, label, runtimeCheck, webAssetCheck, { requirePass = false } = {}) { - assertObject(runtimeCheck.observations, `${label}.live-runtime-current-main.observations`); - assertObject(webAssetCheck.observations, `${label}.live-web-assets-current-main.observations`); - assertObject(report.deploymentIdentity, `${label}.deploymentIdentity`); - assertObject(report.expectedRuntimeIdentity, `${label}.expectedRuntimeIdentity`); - assertObject(report.webAssetIdentity, `${label}.webAssetIdentity`); - - assert.equal(report.deploymentIdentity.status, runtimeCheck.status, `${label}.deploymentIdentity.status`); - assert.deepEqual(runtimeCheck.observations, report.deploymentIdentity, `${label}.live-runtime-current-main.observations`); - assert.equal(report.webAssetIdentity.status, webAssetCheck.status, `${label}.webAssetIdentity.status`); - assert.deepEqual(webAssetCheck.observations, report.webAssetIdentity, `${label}.live-web-assets-current-main.observations`); - - for (const field of ["status", "serviceId", "source", "commitId", "imageTag", "image", "summary"]) { - assertString(report.expectedRuntimeIdentity[field], `${label}.expectedRuntimeIdentity.${field}`); - } - assert.ok(["observed", "not_observed"].includes(report.expectedRuntimeIdentity.status), `${label}.expectedRuntimeIdentity.status`); - assertString(report.deploymentIdentity.expectedCommit, `${label}.deploymentIdentity.expectedCommit`); - assertString(report.deploymentIdentity.expectedImageTag, `${label}.deploymentIdentity.expectedImageTag`); - assertString(report.deploymentIdentity.expectedSource, `${label}.deploymentIdentity.expectedSource`); - assertString(report.deploymentIdentity.observedRuntimeCommit, `${label}.deploymentIdentity.observedRuntimeCommit`); - assertString(report.deploymentIdentity.observedImageTag, `${label}.deploymentIdentity.observedImageTag`); - if (report.deploymentIdentity.status === "blocked") { - assertString(report.deploymentIdentity.reason, `${label}.deploymentIdentity.reason`); - } - assertString(report.deploymentIdentity.summary, `${label}.deploymentIdentity.summary`); - assertStringArray(report.deploymentIdentity.evidence, `${label}.deploymentIdentity.evidence`, { minLength: 4 }); - - assertArray(report.webAssetIdentity.assets, `${label}.webAssetIdentity.assets`); - assertStringArray(report.webAssetIdentity.mismatches, `${label}.webAssetIdentity.mismatches`, { - minLength: report.webAssetIdentity.status === "blocked" ? 1 : 0 - }); - assertString(report.webAssetIdentity.summary, `${label}.webAssetIdentity.summary`); - for (const [index, asset] of report.webAssetIdentity.assets.entries()) { - const assetLabel = `${label}.webAssetIdentity.assets[${index}]`; - assertObject(asset, assetLabel); - for (const field of ["path", "status", "contentType", "sourceHash", "liveHash"]) { - assertString(asset[field], `${assetLabel}.${field}`); - } - assert.ok(["match", "mismatch", "fetch_failed"].includes(asset.status), `${assetLabel}.status`); - assert.ok(Number.isInteger(asset.sourceLength), `${assetLabel}.sourceLength`); - assert.ok(Number.isInteger(asset.liveLength), `${assetLabel}.liveLength`); - } - - if (!requirePass) return; - - assert.equal(runtimeCheck.status, "pass", `${label}.live-runtime-current-main.status`); - assert.equal(report.deploymentIdentity.status, "pass", `${label}.deploymentIdentity.status`); - assert.equal(webAssetCheck.status, "pass", `${label}.live-web-assets-current-main.status`); - assert.equal(report.webAssetIdentity.status, "pass", `${label}.webAssetIdentity.status`); - assert.deepEqual(report.webAssetIdentity.mismatches, [], `${label}.webAssetIdentity.mismatches`); -} - -function assertDevCloudWorkbenchEndpoints(report, label) { - assertObject(report.endpoints, `${label}.endpoints`); - assert.equal(report.endpoints.frontend, ACTIVE_DEV_BROWSER_ENDPOINT, `${label}.endpoints.frontend`); - assert.equal(report.endpoints.api, ACTIVE_DEV_PUBLIC_ENDPOINT, `${label}.endpoints.api`); - assert.equal(report.endpoints.edge, ACTIVE_DEV_PUBLIC_ENDPOINT, `${label}.endpoints.edge`); -} - -function assertFreshDevCloudWorkbenchLiveEvidence(report, label, raw, journey) { - const deprecatedEndpoints = findDeprecatedPublicEndpoints(raw); - assert.deepEqual( - deprecatedEndpoints, - [], - `${label} deployed workbench report must not contain deprecated public endpoint evidence` - ); - - if (!["pass", "degraded"].includes(report.status)) { - return; - } - - assert.equal(report.devLive, report.status === "pass", `${label}.devLive`); - assert.equal( - report.evidenceLevel, - report.status === "pass" ? "DEV-LIVE-BROWSER" : "DEV-LIVE-BROWSER-DEGRADED", - `${label}.evidenceLevel` - ); - assert.equal(report.safety.sourceIsDevLive, false, `${label}.safety.sourceIsDevLive`); - if (report.status === "pass") { - assert.notEqual(report.runtimeIdentity.healthStatus, "degraded", `${label}.runtimeIdentity.healthStatus`); - assert.equal(report.runtimeIdentity.ready, true, `${label}.runtimeIdentity.ready`); - assert.equal(report.runtimeIdentity.runtime?.durable, true, `${label}.runtimeIdentity.runtime.durable`); - assert.equal(report.runtimeIdentity.runtime?.ready, true, `${label}.runtimeIdentity.runtime.ready`); - assert.equal(report.runtimeIdentity.readiness?.durability?.ready, true, `${label}.runtimeIdentity.readiness.durability.ready`); - assert.notEqual( - report.runtimeIdentity.runtime?.blocker, - "runtime_durable_adapter_query_blocked", - `${label}.runtimeIdentity.runtime.blocker` - ); - assert.notEqual( - report.runtimeIdentity.readiness?.durability?.blocker, - "runtime_durable_adapter_query_blocked", - `${label}.runtimeIdentity.readiness.durability.blocker` - ); - assert.equal( - (report.runtimeIdentity.blockerCodes ?? []).includes("runtime_durable_adapter_query_blocked"), - false, - `${label}.runtimeIdentity.blockerCodes` - ); - } - assert.equal( - report.sourceIdentity.reportCommitId === "unknown" || report.sourceIdentity.worktreeState === "clean", - true, - `${label}.sourceIdentity.reportCommitId must be unknown unless the source worktree is clean` - ); - - if (journey.status === "degraded" && journey.observations.classification?.terminalState === "backend-timeout") { - return; - } - - const journeyText = JSON.stringify(journey.observations); - assert.equal( - /provider_unavailable|OPENAI_API_KEY|providerStatus|provider HTTP|HTTP 502/u.test(journeyText), - false, - `${label}.live-code-agent-browser-journey must not mix provider_unavailable, OPENAI_API_KEY, or providerStatus blockers into accepted evidence` - ); - - assertAcceptedCodeAgentPayload( - journey.observations.response, - `${label}.live-code-agent-browser-journey.response` - ); - const networkEvents = journey.observations.networkEvents ?? []; - assertArray(networkEvents, `${label}.live-code-agent-browser-journey.networkEvents`); - assert.ok(networkEvents.length >= 1, `${label}.live-code-agent-browser-journey.networkEvents`); - for (const [index, event] of networkEvents.entries()) { - const eventLabel = `${label}.live-code-agent-browser-journey.networkEvents[${index}]`; - assertObject(event, eventLabel); - assert.equal(event.method, "POST", `${eventLabel}.method`); - assert.equal(event.status, 200, `${eventLabel}.status`); - assert.equal(event.urlPath, "/v1/agent/chat", `${eventLabel}.urlPath`); - assertAcceptedCodeAgentPayload(event.body, `${eventLabel}.body`); - } -} - -function assertFreshDevCloudWorkbenchDomOnlyEvidence(report, label, raw) { - const deprecatedEndpoints = findDeprecatedPublicEndpoints(raw); - assert.deepEqual( - deprecatedEndpoints, - [], - `${label} deployed workbench report must not contain deprecated public endpoint evidence` - ); - - if (report.status !== "pass") { - return; - } - - assert.equal(report.devLive, false, `${label}.devLive`); - assert.equal(report.evidenceLevel, "DEV-LIVE-DOM-READONLY", `${label}.evidenceLevel`); - assert.equal(report.safety.sourceIsDevLive, false, `${label}.safety.sourceIsDevLive`); - assert.equal(report.runtimeIdentity.status, "observed", `${label}.runtimeIdentity.status`); - assert.equal(report.runtimeIdentity.source, "health-live", `${label}.runtimeIdentity.source`); - assert.equal(report.runtimeIdentity.endpoint, `${ACTIVE_DEV_PUBLIC_ENDPOINT}/health/live`, `${label}.runtimeIdentity.endpoint`); - assert.equal(report.runtimeIdentity.serviceId, "hwlab-cloud-api", `${label}.runtimeIdentity.serviceId`); - assert.equal(report.runtimeIdentity.environment, "dev", `${label}.runtimeIdentity.environment`); - assert.match(report.runtimeIdentity.commitId, /^[a-f0-9]{7,40}$/, `${label}.runtimeIdentity.commitId`); - assert.notEqual(report.runtimeIdentity.commitId, "unknown", `${label}.runtimeIdentity.commitId`); - assert.notEqual(report.runtimeIdentity.commitSource, "source-git-head", `${label}.runtimeIdentity.commitSource`); - assert.notEqual(report.runtimeIdentity.commitSource, "hard-coded", `${label}.runtimeIdentity.commitSource`); - assert.notEqual(report.runtimeIdentity.commitSource, "literal", `${label}.runtimeIdentity.commitSource`); - - const generatedAtMs = Date.parse(report.generatedAt); - const runtimeObservedAtMs = Date.parse(report.runtimeIdentity.observedAt); - assert.ok( - Math.abs(generatedAtMs - runtimeObservedAtMs) <= devCloudWorkbenchRuntimeIdentityMaxSkewMs, - `${label}.runtimeIdentity.observedAt must be fresh relative to generatedAt` - ); - - assert.equal( - report.sourceIdentity.reportCommitId === "unknown" || report.sourceIdentity.worktreeState === "clean", - true, - `${label}.sourceIdentity.reportCommitId must be unknown unless the source worktree is clean` - ); -} - -function assertAcceptedCodeAgentPayload(payload, label) { - assertObject(payload, label); - assert.equal(payload.status, "completed", `${label}.status`); - assertString(payload.provider, `${label}.provider`); - assertString(payload.model, `${label}.model`); - assertString(payload.backend, `${label}.backend`); - assertString(payload.traceId, `${label}.traceId`); - assert.equal(payload.hasReply, true, `${label}.hasReply`); - assert.equal(payload.error, null, `${label}.error`); - assert.equal( - syntheticAcceptedCodeAgentModels.has(payload.model), - false, - `${label}.model must not use the known synthetic placeholder model` - ); - for (const forbiddenField of ["conversationId", "sessionId", "messageId", "reply", "content", "text", "output"]) { - assert.equal( - Object.hasOwn(payload, forbiddenField), - false, - `${label} must not retain ${forbiddenField}` - ); - } -} - -async function validateDevM3Report(report, label) { - for (const field of [ - "$schema", - "$id", - "reportVersion", - "issue", - "taskId", - "commitId", - "acceptanceLevel", - "devOnly", - "prodDisabled", - "sourceContract", - "validationCommands", - "runtimeTarget", - "safetyGates", - "liveChecks", - "liveOperation", - "blockers", - "summary" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.issue, requiredDevM3Issue, `${label}.issue`); - assert.equal(report.taskId, "dev-m3-hardware-loop", `${label}.taskId`); - assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`); - assert.equal(report.acceptanceLevel, "dev_m3_hardware_loop", `${label}.acceptanceLevel`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - - assertObject(report.sourceContract, `${label}.sourceContract`); - assertStatus(report.sourceContract.status, `${label}.sourceContract.status`); - assertStringArray(report.sourceContract.documents, `${label}.sourceContract.documents`, { - minLength: requiredDevM3Docs.length - }); - assertString(report.sourceContract.summary, `${label}.sourceContract.summary`); - const documents = new Set(report.sourceContract.documents); - assertUnique(report.sourceContract.documents, `${label}.sourceContract.documents`); - for (const requiredDoc of requiredDevM3Docs) { - assert.ok(documents.has(requiredDoc), `${label}.sourceContract.documents missing ${requiredDoc}`); - } - for (const [index, documentPath] of report.sourceContract.documents.entries()) { - const absolutePath = assertRepoRelativePath( - documentPath, - `${label}.sourceContract.documents[${index}]` - ); - await access(absolutePath, fsConstants.R_OK); - } - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 }); - assertUnique(report.validationCommands, `${label}.validationCommands`); - for (const requiredCommand of requiredDevM3ValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - assert.ok( - report.validationCommands.includes(legacyDevM3LiveCommand), - `${label}.validationCommands missing legacy compatibility command ${legacyDevM3LiveCommand}` - ); - - assertObject(report.runtimeTarget, `${label}.runtimeTarget`); - assert.equal(report.runtimeTarget.endpoint, "http://74.48.78.17:16667", `${label}.runtimeTarget.endpoint`); - if (Object.hasOwn(report.runtimeTarget, "frontendEndpoint")) { - assert.equal( - report.runtimeTarget.frontendEndpoint, - "http://74.48.78.17:16666", - `${label}.runtimeTarget.frontendEndpoint` - ); - } - assert.equal(report.runtimeTarget.namespace, "hwlab-dev", `${label}.runtimeTarget.namespace`); - assert.equal(report.runtimeTarget.environment, "dev", `${label}.runtimeTarget.environment`); - assert.equal(report.runtimeTarget.requiredBoxSimulators, 2, `${label}.runtimeTarget.requiredBoxSimulators`); - assert.equal(report.runtimeTarget.requiredGatewaySimulators, 2, `${label}.runtimeTarget.requiredGatewaySimulators`); - if (Object.hasOwn(report.runtimeTarget, "requiredPatchPanels")) { - assert.equal(report.runtimeTarget.requiredPatchPanels, 1, `${label}.runtimeTarget.requiredPatchPanels`); - } - if (Object.hasOwn(report.runtimeTarget, "requiredRoute")) { - assert.equal( - report.runtimeTarget.requiredRoute, - "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", - `${label}.runtimeTarget.requiredRoute` - ); - } - assert.equal(report.runtimeTarget.realHardwareAllowed, false, `${label}.runtimeTarget.realHardwareAllowed`); - assert.equal(report.runtimeTarget.prodAllowed, false, `${label}.runtimeTarget.prodAllowed`); - - assertObject(report.safetyGates, `${label}.safetyGates`); - for (const field of [ - "liveFlagRequired", - "confirmDevRequired", - "expectNonProdRequired", - "confirmedNonProductionRequired", - "dryRunPlanSupported", - "prodForbidden", - "realHardwareForbidden", - "secretReadForbidden", - "forcePushForbidden", - "unideskRuntimeSubstitutionForbidden" - ]) { - assert.equal(report.safetyGates[field], true, `${label}.safetyGates.${field}`); - } - if (Object.hasOwn(report.safetyGates, "dryRunCallsLiveEndpoints")) { - assert.equal(report.safetyGates.dryRunCallsLiveEndpoints, false, `${label}.safetyGates.dryRunCallsLiveEndpoints`); - } - if (Object.hasOwn(report.safetyGates, "defaultSourceReadOnly")) { - assert.equal(report.safetyGates.defaultSourceReadOnly, true, `${label}.safetyGates.defaultSourceReadOnly`); - } - - assertArray(report.liveChecks, `${label}.liveChecks`); - assert.ok(report.liveChecks.length >= 8, `${label}.liveChecks must include DEV M3 checks`); - assertUnique(report.liveChecks.map((check) => check.id), `${label}.liveChecks`); - for (const [index, check] of report.liveChecks.entries()) { - const checkLabel = `${label}.liveChecks[${index}]`; - assertObject(check, checkLabel); - for (const field of ["id", "status", "summary", "evidence"]) { - assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`); - } - assertString(check.id, `${checkLabel}.id`); - assertStatus(check.status, `${checkLabel}.status`); - assertString(check.summary, `${checkLabel}.summary`); - assertStringArray(check.evidence, `${checkLabel}.evidence`, { minLength: 1 }); - if (Object.hasOwn(check, "blockerClass")) { - assert.ok(blockerTypes.has(check.blockerClass), `${checkLabel}.blockerClass must be known`); - } - } - - if (Object.hasOwn(report, "localRuntimeObservations")) { - assertArray(report.localRuntimeObservations, `${label}.localRuntimeObservations`); - for (const [index, observation] of report.localRuntimeObservations.entries()) { - const observationLabel = `${label}.localRuntimeObservations[${index}]`; - assertObject(observation, observationLabel); - for (const field of ["id", "status", "command", "summary"]) { - assert.ok(Object.hasOwn(observation, field), `${observationLabel} missing ${field}`); - assertString(observation[field], `${observationLabel}.${field}`); - } - } - } - - if (Object.hasOwn(report, "d601Observability")) { - assertObject(report.d601Observability, `${label}.d601Observability`); - for (const field of [ - "runnerKubeconfigReadable", - "runnerKubeconfigProbeExitCode", - "runnerKubeconfigProbeStderr", - "d601PublicEndpointsReachable", - "d601K3sUnavailable", - "classification", - "sourceIssue", - "inferenceRule" - ]) { - assert.ok(Object.hasOwn(report.d601Observability, field), `${label}.d601Observability missing ${field}`); - } - assertBoolean(report.d601Observability.runnerKubeconfigReadable, `${label}.d601Observability.runnerKubeconfigReadable`); - if (Object.hasOwn(report.d601Observability, "runnerKubeconfigProbeOk")) { - assertBoolean(report.d601Observability.runnerKubeconfigProbeOk, `${label}.d601Observability.runnerKubeconfigProbeOk`); - } - assert.ok( - Number.isInteger(report.d601Observability.runnerKubeconfigProbeExitCode) || - report.d601Observability.runnerKubeconfigProbeExitCode === null, - `${label}.d601Observability.runnerKubeconfigProbeExitCode` - ); - assertString(report.d601Observability.runnerKubeconfigProbeStderr, `${label}.d601Observability.runnerKubeconfigProbeStderr`); - assertBoolean(report.d601Observability.d601PublicEndpointsReachable, `${label}.d601Observability.d601PublicEndpointsReachable`); - assertBoolean(report.d601Observability.d601K3sUnavailable, `${label}.d601Observability.d601K3sUnavailable`); - if ( - report.d601Observability.runnerKubeconfigReadable === false && - report.d601Observability.d601PublicEndpointsReachable === true - ) { - assert.equal( - report.d601Observability.d601K3sUnavailable, - false, - `${label}.d601Observability runner gap must not imply D601 k3s unavailable` - ); - } - assertString(report.d601Observability.classification, `${label}.d601Observability.classification`); - assertString(report.d601Observability.sourceIssue, `${label}.d601Observability.sourceIssue`); - assertString(report.d601Observability.inferenceRule, `${label}.d601Observability.inferenceRule`); - } - - if (Object.hasOwn(report, "readOnlySupplementalEvidence")) { - assertArray(report.readOnlySupplementalEvidence, `${label}.readOnlySupplementalEvidence`); - for (const [index, evidence] of report.readOnlySupplementalEvidence.entries()) { - const evidenceLabel = `${label}.readOnlySupplementalEvidence[${index}]`; - assertObject(evidence, evidenceLabel); - for (const field of ["id", "status", "source", "summary", "evidence", "requiredFollowUp"]) { - assert.ok(Object.hasOwn(evidence, field), `${evidenceLabel} missing ${field}`); - } - assertString(evidence.id, `${evidenceLabel}.id`); - assert.ok( - ["pass", "gap", "source-ready", "manifest-ready"].includes(evidence.status), - `${evidenceLabel}.status must be pass, gap, source-ready, or manifest-ready` - ); - if (Array.isArray(evidence.source)) { - assertStringArray(evidence.source, `${evidenceLabel}.source`, { minLength: 1 }); - for (const [sourceIndex, sourcePath] of evidence.source.entries()) { - assertRepoRelativePath(sourcePath, `${evidenceLabel}.source[${sourceIndex}]`); - } - } else { - assertRepoRelativePath(evidence.source, `${evidenceLabel}.source`); - } - assertString(evidence.summary, `${evidenceLabel}.summary`); - assertObject(evidence.evidence, `${evidenceLabel}.evidence`); - assertString(evidence.requiredFollowUp, `${evidenceLabel}.requiredFollowUp`); - if (Object.hasOwn(evidence, "validationCommand")) { - assertString(evidence.validationCommand, `${evidenceLabel}.validationCommand`); - } - if (Object.hasOwn(evidence, "blockerClass") && evidence.blockerClass !== null) { - assert.ok(blockerTypes.has(evidence.blockerClass), `${evidenceLabel}.blockerClass must be known`); - } - } - } - - if (Object.hasOwn(report, "dryRunPlan")) { - const plan = report.dryRunPlan; - assertObject(plan, `${label}.dryRunPlan`); - assert.equal(plan.mode, "plan-only", `${label}.dryRunPlan.mode`); - assert.equal(plan.evidenceLevel, "DRY-RUN", `${label}.dryRunPlan.evidenceLevel`); - assert.equal(plan.route, "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1", `${label}.dryRunPlan.route`); - assert.equal(plan.dryRunCallsLiveEndpoints, false, `${label}.dryRunPlan.dryRunCallsLiveEndpoints`); - assert.equal(plan.liveWriteWillRun, false, `${label}.dryRunPlan.liveWriteWillRun`); - if (Object.hasOwn(plan, "failureClassifications")) { - assertStringArray(plan.failureClassifications, `${label}.dryRunPlan.failureClassifications`, { minLength: 5 }); - for (const classification of [ - "target_missing", - "identity_not_distinct", - "patch_panel_wiring_missing", - "operation_failed", - "evidence_missing" - ]) { - assert.ok( - plan.failureClassifications.includes(classification), - `${label}.dryRunPlan.failureClassifications missing ${classification}` - ); - } - } - assertStringArray(plan.liveWritePreconditions, `${label}.dryRunPlan.liveWritePreconditions`, { minLength: 4 }); - assertStringArray(plan.evidenceFields, `${label}.dryRunPlan.evidenceFields`, { minLength: 4 }); - assertArray(plan.endpointPlan, `${label}.dryRunPlan.endpointPlan`); - assert.ok(plan.endpointPlan.length >= 8, `${label}.dryRunPlan.endpointPlan must enumerate precondition and live endpoints`); - assert.ok( - plan.endpointPlan.some((endpoint) => endpoint.serviceId === "hwlab-patch-panel" && endpoint.path === "/signals/route" && endpoint.mutatesDevState === true), - `${label}.dryRunPlan.endpointPlan must include the patch-panel route write endpoint` - ); - assert.ok( - plan.endpointPlan.some((endpoint) => endpoint.serviceId === "hwlab-box-simu" && endpoint.path === "/ports/write" && endpoint.mutatesDevState === true), - `${label}.dryRunPlan.endpointPlan must include the source box DO1 write endpoint` - ); - assert.ok( - plan.endpointPlan.every((endpoint) => endpoint.calledInDryRun === false), - `${label}.dryRunPlan.endpointPlan endpoints must not be called in dry-run` - ); - for (const requiredField of [ - "operationId", - "traceId", - "auditId", - "evidenceId", - "routeResponse.propagatedBy=hwlab-patch-panel", - "targetState.ports.DI1.propagatedBy=hwlab-patch-panel" - ]) { - assert.ok(plan.evidenceFields.includes(requiredField), `${label}.dryRunPlan.evidenceFields missing ${requiredField}`); - } - } - - assertObject(report.liveOperation, `${label}.liveOperation`); - assertStatus(report.liveOperation.status, `${label}.liveOperation.status`); - for (const field of ["operationId", "traceId", "auditId", "evidenceId", "summary"]) { - assertString(report.liveOperation[field], `${label}.liveOperation.${field}`); - } - if (Object.hasOwn(report, "dryRunPlan")) { - assert.notEqual( - report.liveOperation.status, - "pass", - `${label}.liveOperation.status cannot be pass when only a DRY-RUN plan is present` - ); - for (const field of ["operationId", "traceId", "auditId", "evidenceId"]) { - assert.equal( - report.liveOperation[field], - "not_observed", - `${label}.liveOperation.${field} must remain not_observed for DRY-RUN evidence` - ); - } - } - - assertArray(report.blockers, `${label}.blockers`); - assertUnique( - report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`), - `${label}.blockers` - ); - for (const [index, blocker] of report.blockers.entries()) { - const blockerLabel = `${label}.blockers[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["type", "scope", "status", "summary"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`); - assertString(blocker.scope, `${blockerLabel}.scope`); - assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`); - assertString(blocker.summary, `${blockerLabel}.summary`); - } - - assertObject(report.summary, `${label}.summary`); - assertStatus(report.summary.status, `${label}.summary.status`); - assertTimestamp(report.summary.observedAt, `${label}.summary.observedAt`); - assertString(report.summary.result, `${label}.summary.result`); -} - -async function validatePreflightReport(relativePath, report) { - const label = relativePath; - - assertObject(report, label); - for (const field of [ - "$schema", - "$id", - "reportVersion", - "reportKind", - "issue", - "supports", - "target", - "generatedAt", - "mode", - "devOnly", - "prodDisabled", - "forbiddenActions", - "validationCommands", - "artifactIdentity", - "registryCapabilities", - "conclusion", - "checks", - "blockers" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v1", `${label}.reportVersion`); - assert.equal(report.reportKind, "dev-gate-preflight", `${label}.reportKind`); - assert.equal(report.issue, requiredPreflightIssue, `${label}.issue`); - assert.equal(report.mode, "read-only", `${label}.mode`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - assert.ok(preflightConclusions.has(report.conclusion), `${label}.conclusion must be ready or blocked`); - assert.match(report.generatedAt, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, `${label}.generatedAt`); - - assertStringArray(report.supports, `${label}.supports`, { minLength: requiredPreflightSupports.length }); - for (const supportedIssue of requiredPreflightSupports) { - assert.ok(report.supports.includes(supportedIssue), `${label}.supports missing ${supportedIssue}`); - } - - assertObject(report.target, `${label}.target`); - assert.equal(report.target.ref, "origin/main", `${label}.target.ref`); - assert.match(report.target.commitId, /^[a-f0-9]{40}$/, `${label}.target.commitId`); - assert.equal(report.target.shortCommitId, report.target.commitId.slice(0, 7), `${label}.target.shortCommitId`); - - assertStringArray(report.forbiddenActions, `${label}.forbiddenActions`, { minLength: 1 }); - for (const forbiddenAction of ["prod-deploy", "secret-material-read", "unidesk-runtime-substitute", "force-push"]) { - assert.ok(report.forbiddenActions.includes(forbiddenAction), `${label}.forbiddenActions missing ${forbiddenAction}`); - } - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 }); - for (const requiredCommand of requiredPreflightValidationCommands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` - ); - } - - assertArtifactIdentity(report.artifactIdentity, `${label}.artifactIdentity`, report.target); - assertRegistryCapabilities(report.registryCapabilities, `${label}.registryCapabilities`); - - assertArray(report.checks, `${label}.checks`); - assert.ok(report.checks.length >= 1, `${label}.checks must not be empty`); - assertUnique(report.checks.map((check) => check.id), `${label}.checks`); - for (const [index, check] of report.checks.entries()) { - const checkLabel = `${label}.checks[${index}]`; - assertObject(check, checkLabel); - for (const field of ["id", "category", "status", "summary", "evidence"]) { - assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`); - } - assert.match(check.id, /^[a-z][a-z0-9-]*$/, `${checkLabel}.id`); - assertString(check.category, `${checkLabel}.category`); - assertStatus(check.status, `${checkLabel}.status`); - assertString(check.summary, `${checkLabel}.summary`); - assertArray(check.evidence, `${checkLabel}.evidence`); - } - - assertArray(report.blockers, `${label}.blockers`); - if (report.conclusion === "blocked") { - assert.ok(report.blockers.length >= 1, `${label}.blockers required when conclusion is blocked`); - } - assertUnique( - report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`), - `${label}.blockers` - ); - for (const [index, blocker] of report.blockers.entries()) { - const blockerLabel = `${label}.blockers[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["type", "scope", "status", "summary", "nextTask"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`); - assertString(blocker.scope, `${blockerLabel}.scope`); - assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`); - assertString(blocker.summary, `${blockerLabel}.summary`); - assertString(blocker.nextTask, `${blockerLabel}.nextTask`); - } - - if (report.conclusion === "ready") { - assert.equal(report.blockers.length, 0, `${label}.blockers must be empty when ready`); - } - if (Object.hasOwn(report, "notes")) { - assertString(report.notes, `${label}.notes`); - } -} - -function assertEvidenceLevel(value, label) { - assertString(value, label); - assert.ok(evidenceLevels.has(value), `${label} must be one of ${Array.from(evidenceLevels).join(", ")}`); } function assertBoolean(value, label) { assert.equal(typeof value, "boolean", `${label} must be a boolean`); } -async function readAggregatorSourceReports(report, label) { - const sources = {}; - for (const key of ["devEdgeHealth", "devM3Hardware", "devM4Agent", "devM5Gate"]) { - const sourceReport = report.sourceReports[key]; - const absolutePath = assertRepoRelativePath( - sourceReport.path, - `${label}.sourceReports.${key}.path` - ); - sources[key] = JSON.parse(await readFile(absolutePath, "utf8")); - } - return sources; +function repoRelative(inputPath) { + const absolute = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath); + return path.relative(repoRoot, absolute).split(path.sep).join("/"); } -function observedLiveIdentifier(value) { - return typeof value === "string" && - value.trim().length > 0 && - !["not_observed", "not_run", "unknown", "blocked", "missing"].includes(value); -} - -function aggregatorLiveStateFromSources(sources) { - const cloudApiDb = sources.devEdgeHealth.edgeHealth?.contracts?.deploy?.cloudApiDb ?? {}; - const edgeDbLive = cloudApiDb.ready === true && - (cloudApiDb.connected === true || cloudApiDb.liveConnected === true) && - cloudApiDb.liveDbEvidence === true; - const m4LiveDb = sources.devM4Agent.publicEntrypoints?.api?.live?.db ?? {}; - const m4DbLive = m4LiveDb.ready === true && - (m4LiveDb.connected === true || m4LiveDb.liveConnected === true) && - m4LiveDb.liveDbEvidence === true; - const dbLive = edgeDbLive || m4DbLive; - const m3Operation = sources.devM3Hardware.liveOperation ?? {}; - const m3Live = statusIsPass(m3Operation.status) && - ["operationId", "traceId", "auditId", "evidenceId"].every((field) => - observedLiveIdentifier(m3Operation[field]) - ); - const m4Live = statusIsPass(sources.devM4Agent.livePreflight?.status); - const m5Live = dbLive && - m3Live && - m4Live && - statusIsPass(sources.devM5Gate.devPreconditions?.status); - +async function readJson(inputPath) { + ensureNotRepoReportsPath(repoRoot, inputPath, "validation input"); + const absolute = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath); + const raw = await readFile(absolute, "utf8"); return { - dbLive, - m3Live, - m4Live, - m5Live + path: absolute, + relativePath: repoRelative(absolute), + report: JSON.parse(raw) }; } -function findById(items, id) { - return items.find((item) => item.id === id); -} - -function findByMilestone(items, milestone) { - return items.find((item) => item.milestone === milestone || item.id === milestone); -} - -function assertAggregatorBlockedDod(check, label, reason) { - assertObject(check, label); - assert.equal(check.status, "blocked", `${label}.status must stay blocked until ${reason}`); - assert.equal(check.evidenceLevel, "BLOCKED", `${label}.evidenceLevel must stay BLOCKED until ${reason}`); -} - -function assertAggregatorBlockedLayer(layer, label, reason) { - assertObject(layer, label); - assert.equal(layer.status, "blocked", `${label}.status must stay blocked until ${reason}`); - assert.equal(layer.evidenceLevel, "BLOCKED", `${label}.evidenceLevel must stay BLOCKED until ${reason}`); -} - -function assertAggregatorBlockedMilestone(milestone, label, reason) { - assertObject(milestone, label); - assert.equal(milestone.status, "blocked", `${label}.status must stay blocked until ${reason}`); - assert.equal(milestone.liveEvidence, "missing_or_blocked", `${label}.liveEvidence must stay missing_or_blocked until ${reason}`); - assert.notEqual(milestone.highestVisibleLevel, "DEV-LIVE", `${label}.highestVisibleLevel must not promote non-live evidence`); -} - -function assertAggregatorBlockedClassification(classification, label, reason) { - assertObject(classification, label); - assert.equal(classification.status, "blocked", `${label}.status must stay blocked until ${reason}`); - assert.equal(classification.currentLevel, "BLOCKED", `${label}.currentLevel must stay BLOCKED until ${reason}`); -} - -function assertProtectedAggregatorDevLiveEvidence(item, label, liveState, assumedLevel = item.level) { - if (!protectedAggregatorMilestones.has(item.milestone) || assumedLevel !== "DEV-LIVE") { - return; - } - - const requiredCategory = protectedAggregatorLiveCategories[item.milestone]; - assert.equal( - item.category, - requiredCategory, - `${label} ${item.milestone} DEV-LIVE evidence must come from ${requiredCategory}, not frontend, route, source, dry-run, or fixture evidence` - ); - - const sourceLive = { - M3: liveState.m3Live, - M4: liveState.m4Live, - M5: liveState.m5Live - }[item.milestone]; - assert.equal( - sourceLive, - true, - `${label} ${item.milestone} DEV-LIVE evidence must be backed by its live source report` - ); -} - -function assertNoStaleDbRuntimeBlocker(report, label, liveState) { - if (!liveState.dbLive) { - return; - } - - const staleDbScopes = new Set([ - "cloud-api-db", - "cloud-api-db-health-gate", - "cloud-api-db-live", - "db-live" - ]); - const staleBlocker = report.blockers?.find((blocker) => staleDbScopes.has(blocker.scope)); - assert.equal( - staleBlocker, - undefined, - `${label}.blockers must not keep stale DB live/runtime-env blocker ${staleBlocker?.scope ?? ""} when DB live evidence is pass` - ); - - const dbCheck = findById(report.dod.checks, "cloud-api-db-ready"); - assertObject(dbCheck, `${label}.dod.checks.cloud-api-db-ready`); - assert.equal(dbCheck.status, "pass", `${label}.dod.checks.cloud-api-db-ready.status must be pass when DB live evidence is proven`); - assert.equal(dbCheck.evidenceLevel, "DEV-LIVE", `${label}.dod.checks.cloud-api-db-ready.evidenceLevel must be DEV-LIVE when DB live evidence is proven`); -} - -async function assertAggregatorNonPromotionBoundaries(report, label) { - const sources = await readAggregatorSourceReports(report, label); - const liveState = aggregatorLiveStateFromSources(sources); - const anyProtectedLiveBlocked = !liveState.dbLive || !liveState.m3Live || !liveState.m4Live || !liveState.m5Live; - - assertNoStaleDbRuntimeBlocker(report, label, liveState); - - assertObject(report.latestFrontendDevFact, `${label}.latestFrontendDevFact`); - assert.equal( - report.latestFrontendDevFact.promotesM3M4M5, - false, - `${label}.latestFrontendDevFact.promotesM3M4M5 must be false` - ); - - if (anyProtectedLiveBlocked) { - assert.equal(report.overall.status, "blocked", `${label}.overall.status must remain blocked while DB/M3/M4/M5 live evidence is incomplete`); - assert.equal(report.overall.green, false, `${label}.overall.green must remain false while DB/M3/M4/M5 live evidence is incomplete`); - } - - if (!liveState.dbLive) { - assertAggregatorBlockedDod( - findById(report.dod.checks, "cloud-api-db-ready"), - `${label}.dod.checks.cloud-api-db-ready`, - "DB DEV-LIVE acceptance source reports prove ready=true, connected=true, and liveDbEvidence=true" - ); - if (report.currentDevLayering?.dbLive) { - assertAggregatorBlockedLayer( - report.currentDevLayering.dbLive, - `${label}.currentDevLayering.dbLive`, - "DB DEV-LIVE acceptance source reports prove ready=true, connected=true, and liveDbEvidence=true" - ); - } - } - - if (!liveState.m3Live) { - assertAggregatorBlockedDod( - findById(report.dod.checks, "m3-hardware-trusted-loop"), - `${label}.dod.checks.m3-hardware-trusted-loop`, - "M3 liveOperation proves operationId, traceId, auditId, and evidenceId" - ); - assertAggregatorBlockedMilestone( - findByMilestone(report.milestones, "M3"), - `${label}.milestones.M3`, - "M3 liveOperation proves operationId, traceId, auditId, and evidenceId" - ); - if (report.currentDevLayering?.m3HardwareTrustedLoop) { - assertAggregatorBlockedLayer( - report.currentDevLayering.m3HardwareTrustedLoop, - `${label}.currentDevLayering.m3HardwareTrustedLoop`, - "M3 liveOperation proves operationId, traceId, auditId, and evidenceId" - ); - } - if (Array.isArray(report.milestoneLevelClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneLevelClassification, "M3"), - `${label}.milestoneLevelClassification.M3`, - "M3 liveOperation proves operationId, traceId, auditId, and evidenceId" - ); - } - if (Array.isArray(report.milestoneBlockerClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneBlockerClassification, "M3"), - `${label}.milestoneBlockerClassification.M3`, - "M3 liveOperation proves operationId, traceId, auditId, and evidenceId" - ); - } - } - - if (!liveState.m4Live) { - assertAggregatorBlockedDod( - findById(report.dod.checks, "m4-agent-loop-live"), - `${label}.dod.checks.m4-agent-loop-live`, - "M4 livePreflight source report passes" - ); - assertAggregatorBlockedMilestone( - findByMilestone(report.milestones, "M4"), - `${label}.milestones.M4`, - "M4 livePreflight source report passes" - ); - if (report.currentDevLayering?.m4AgentLoop) { - assertAggregatorBlockedLayer( - report.currentDevLayering.m4AgentLoop, - `${label}.currentDevLayering.m4AgentLoop`, - "M4 livePreflight source report passes" - ); - } - if (Array.isArray(report.milestoneLevelClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneLevelClassification, "M4"), - `${label}.milestoneLevelClassification.M4`, - "M4 livePreflight source report passes" - ); - } - if (Array.isArray(report.milestoneBlockerClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneBlockerClassification, "M4"), - `${label}.milestoneBlockerClassification.M4`, - "M4 livePreflight source report passes" - ); - } - } - - if (!liveState.m5Live) { - assertAggregatorBlockedDod( - findById(report.dod.checks, "m5-mvp-dev-live"), - `${label}.dod.checks.m5-mvp-dev-live`, - "M5 source report passes with DB, M3, and M4 live evidence" - ); - assertAggregatorBlockedMilestone( - findByMilestone(report.milestones, "M5"), - `${label}.milestones.M5`, - "M5 source report passes with DB, M3, and M4 live evidence" - ); - if (Array.isArray(report.milestoneLevelClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneLevelClassification, "M5"), - `${label}.milestoneLevelClassification.M5`, - "M5 source report passes with DB, M3, and M4 live evidence" - ); - } - if (Array.isArray(report.milestoneBlockerClassification)) { - assertAggregatorBlockedClassification( - findByMilestone(report.milestoneBlockerClassification, "M5"), - `${label}.milestoneBlockerClassification.M5`, - "M5 source report passes with DB, M3, and M4 live evidence" - ); - } - } - - for (const [index, evidence] of report.evidence.entries()) { - assertProtectedAggregatorDevLiveEvidence(evidence, `${label}.evidence[${index}]`, liveState); - } - for (const [index, evidence] of (report.levels["DEV-LIVE"] ?? []).entries()) { - assertProtectedAggregatorDevLiveEvidence(evidence, `${label}.levels.DEV-LIVE[${index}]`, liveState, "DEV-LIVE"); - } -} - -async function validateAggregatorV2Report(relativePath, report) { - const label = relativePath; - +function validateReportShape(report, label) { assertObject(report, label); - for (const field of [ - "$schema", - "$id", - "reportVersion", - "reportKind", - "issue", - "supports", - "generatedAt", - "generatedFromCommit", - "environment", - "endpoint", - "devOnly", - "prodDisabled", - "safety", - "sourceReports", - "overall", - "dod", - "milestones", - "evidence", - "levels", - "blockers", - "nextSteps", - "validationCommands" - ]) { - assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`); - } - - assertString(report.$schema, `${label}.$schema`); - assertString(report.$id, `${label}.$id`); - assert.equal(report.reportVersion, "v2", `${label}.reportVersion`); - assert.equal(report.reportKind, "dev-m5-gate-aggregator", `${label}.reportKind`); - assert.equal(report.issue, issueFamily.DEV_M5_GATE_AGGREGATOR_V2, `${label}.issue`); - assertTimestamp(report.generatedAt, `${label}.generatedAt`); - assert.match(report.generatedFromCommit, /^([a-f0-9]{7,40}|unknown)$/, `${label}.generatedFromCommit`); - assert.equal(report.environment, "dev", `${label}.environment`); - assert.equal(report.endpoint, "http://74.48.78.17:16667", `${label}.endpoint`); - assert.equal(report.devOnly, true, `${label}.devOnly`); - assert.equal(report.prodDisabled, true, `${label}.prodDisabled`); - - assertStringArray(report.supports, `${label}.supports`, { minLength: 10 }); - for (const requiredIssue of [ - "pikasTech/HWLAB#7", - "pikasTech/HWLAB#9", - "pikasTech/HWLAB#33", - "pikasTech/HWLAB#34", - "pikasTech/HWLAB#35", - "pikasTech/HWLAB#36", - "pikasTech/HWLAB#37", - "pikasTech/HWLAB#38", - "pikasTech/HWLAB#39", - "pikasTech/HWLAB#46" - ]) { - assert.ok(report.supports.includes(requiredIssue), `${label}.supports missing ${requiredIssue}`); - } - - assertObject(report.safety, `${label}.safety`); - for (const field of [ - "reportOnly", - "noDeploy", - "noProd", - "noSecretRead", - "noRuntimeRestart", - "noLiveProbe", - "noHeavyE2E", - "noUniDeskRuntimeSubstitute" - ]) { - assert.equal(report.safety[field], true, `${label}.safety.${field}`); - } - - assertObject(report.sourceReports, `${label}.sourceReports`); - for (const requiredReport of [ - "devPreflight", - "devDeploy", - "devArtifacts", - "devEdgeHealth", - "devM3Hardware", - "devM4Agent", - "devM5Gate", - "d601Observability" - ]) { - assert.ok(Object.hasOwn(report.sourceReports, requiredReport), `${label}.sourceReports missing ${requiredReport}`); - const sourceReport = report.sourceReports[requiredReport]; - assertObject(sourceReport, `${label}.sourceReports.${requiredReport}`); - for (const field of ["path", "issue", "taskId", "status", "commitId"]) { - assert.ok(Object.hasOwn(sourceReport, field), `${label}.sourceReports.${requiredReport} missing ${field}`); - assertString(sourceReport[field], `${label}.sourceReports.${requiredReport}.${field}`); - } - assertRepoRelativePath(sourceReport.path, `${label}.sourceReports.${requiredReport}.path`); - } - - assertObject(report.overall, `${label}.overall`); - assert.ok(["green", "blocked"].includes(report.overall.status), `${label}.overall.status`); - assertBoolean(report.overall.green, `${label}.overall.green`); - assertString(report.overall.reason, `${label}.overall.reason`); - assert.equal(report.overall.green, report.overall.status === "green", `${label}.overall.green must match status`); - - assertObject(report.dod, `${label}.dod`); - assert.ok(["green", "blocked"].includes(report.dod.status), `${label}.dod.status`); - assertBoolean(report.dod.green, `${label}.dod.green`); - assertArray(report.dod.checks, `${label}.dod.checks`); - assert.ok(report.dod.checks.length >= 7, `${label}.dod.checks must cover #9 DoD gates`); - assertUnique(report.dod.checks.map((check) => check.id), `${label}.dod.checks`); - for (const [index, check] of report.dod.checks.entries()) { - const checkLabel = `${label}.dod.checks[${index}]`; - assertObject(check, checkLabel); - for (const field of ["id", "status", "evidenceLevel", "summary"]) { - assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`); - } - assert.match(check.id, /^[a-z][a-z0-9-]*$/, `${checkLabel}.id`); - assert.ok(["pass", "blocked"].includes(check.status), `${checkLabel}.status`); - assertEvidenceLevel(check.evidenceLevel, `${checkLabel}.evidenceLevel`); - assertString(check.summary, `${checkLabel}.summary`); - } - - assertArray(report.milestones, `${label}.milestones`); - assert.deepEqual( - report.milestones.map((milestone) => milestone.id), - requiredMilestoneIds, - `${label}.milestones must cover M0-M5 in order` - ); - for (const [index, milestone] of report.milestones.entries()) { - const milestoneLabel = `${label}.milestones[${index}]`; - assertObject(milestone, milestoneLabel); - for (const field of ["id", "status", "highestVisibleLevel", "liveEvidence", "evidenceCount", "blockerCount", "summary"]) { - assert.ok(Object.hasOwn(milestone, field), `${milestoneLabel} missing ${field}`); - } - assertStatus(milestone.status, `${milestoneLabel}.status`); - assertEvidenceLevel(milestone.highestVisibleLevel, `${milestoneLabel}.highestVisibleLevel`); - assert.ok(["pass", "missing_or_blocked"].includes(milestone.liveEvidence), `${milestoneLabel}.liveEvidence`); - assert.ok(Number.isInteger(milestone.evidenceCount), `${milestoneLabel}.evidenceCount`); - assert.ok(Number.isInteger(milestone.blockerCount), `${milestoneLabel}.blockerCount`); - assertString(milestone.summary, `${milestoneLabel}.summary`); - } - - assertArray(report.evidence, `${label}.evidence`); - assert.ok(report.evidence.length >= 10, `${label}.evidence must include M0-M5 entries`); - for (const [index, evidence] of report.evidence.entries()) { - const evidenceLabel = `${label}.evidence[${index}]`; - assertObject(evidence, evidenceLabel); - for (const field of ["milestone", "issue", "level", "status", "category", "summary"]) { - assert.ok(Object.hasOwn(evidence, field), `${evidenceLabel} missing ${field}`); - } - assert.match(evidence.milestone, /^M[0-5]$/, `${evidenceLabel}.milestone`); - assertString(evidence.issue, `${evidenceLabel}.issue`); - assertEvidenceLevel(evidence.level, `${evidenceLabel}.level`); - assertString(evidence.status, `${evidenceLabel}.status`); - assertString(evidence.category, `${evidenceLabel}.category`); - assertString(evidence.summary, `${evidenceLabel}.summary`); - if (Object.hasOwn(evidence, "reportPath")) { - assertRepoRelativePath(evidence.reportPath, `${evidenceLabel}.reportPath`); - } - if (Object.hasOwn(evidence, "commands")) { - assertStringArray(evidence.commands, `${evidenceLabel}.commands`); - } - if (Object.hasOwn(evidence, "evidence")) { - assertStringArray(evidence.evidence, `${evidenceLabel}.evidence`); - } - if (Object.hasOwn(evidence, "sources")) { - assertStringArray(evidence.sources, `${evidenceLabel}.sources`); + assertString(report.reportKind ?? report.taskId ?? report.kind ?? "temporary-json", `${label}.kind`); + if (Object.hasOwn(report, "devOnly")) assertBoolean(report.devOnly, `${label}.devOnly`); + if (Object.hasOwn(report, "prodDisabled")) assertBoolean(report.prodDisabled, `${label}.prodDisabled`); + if (Array.isArray(report.blockers)) { + for (const [index, blocker] of report.blockers.entries()) { + assertObject(blocker, `${label}.blockers[${index}]`); + assertString(blocker.scope ?? blocker.code ?? blocker.type ?? "blocker", `${label}.blockers[${index}].scope`); } } - - assertObject(report.levels, `${label}.levels`); - assert.deepEqual(Object.keys(report.levels), Array.from(evidenceLevels), `${label}.levels keys`); - for (const level of evidenceLevels) { - assertArray(report.levels[level], `${label}.levels.${level}`); - } - - assertArray(report.blockers, `${label}.blockers`); - assert.ok(report.blockers.length >= 1, `${label}.blockers must not be empty while blocked`); - for (const [index, blocker] of report.blockers.entries()) { - const blockerLabel = `${label}.blockers[${index}]`; - assertObject(blocker, blockerLabel); - for (const field of ["id", "priority", "type", "scope", "status", "source", "sourceIssue", "summary", "unblockOrder", "unblocks", "rationale"]) { - assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`); - } - assertString(blocker.id, `${blockerLabel}.id`); - assert.match(blocker.priority, /^P[0-3]$/, `${blockerLabel}.priority`); - assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type`); - assertString(blocker.scope, `${blockerLabel}.scope`); - assert.equal(blocker.status, "open", `${blockerLabel}.status`); - assertRepoRelativePath(blocker.source, `${blockerLabel}.source`); - assertString(blocker.sourceIssue, `${blockerLabel}.sourceIssue`); - assertString(blocker.summary, `${blockerLabel}.summary`); - assert.ok(Number.isInteger(blocker.unblockOrder), `${blockerLabel}.unblockOrder`); - assertStringArray(blocker.unblocks, `${blockerLabel}.unblocks`, { minLength: 1 }); - assertString(blocker.rationale, `${blockerLabel}.rationale`); - if (Object.hasOwn(blocker, "nextTask")) { - assertString(blocker.nextTask, `${blockerLabel}.nextTask`); + if (Array.isArray(report.validationCommands)) { + for (const [index, command] of report.validationCommands.entries()) { + assertString(command, `${label}.validationCommands[${index}]`); } } - - assertArray(report.nextSteps, `${label}.nextSteps`); - assert.ok(report.nextSteps.length >= 1, `${label}.nextSteps must contain blocker order`); - assert.deepEqual( - report.nextSteps.map((step) => step.blockerOrder).sort((a, b) => a - b), - [...new Set(report.blockers.map((blocker) => blocker.unblockOrder))].sort((a, b) => a - b), - `${label}.nextSteps must cover active blocker orders` - ); - assert.deepEqual( - report.nextSteps.map((step) => step.order), - [...report.nextSteps].map((step) => step.order).sort((a, b) => a - b), - `${label}.nextSteps must be sorted by order` - ); - for (const [index, step] of report.nextSteps.entries()) { - const stepLabel = `${label}.nextSteps[${index}]`; - assertObject(step, stepLabel); - for (const field of ["order", "priority", "scopes", "sourceIssues", "rationale", "action", "evidenceRequired"]) { - assert.ok(Object.hasOwn(step, field), `${stepLabel} missing ${field}`); + if (Object.hasOwn(report, "dryRunPlan")) { + assertObject(report.dryRunPlan, `${label}.dryRunPlan`); + if (Object.hasOwn(report.dryRunPlan, "dryRunCallsLiveEndpoints")) { + assert.equal(report.dryRunPlan.dryRunCallsLiveEndpoints, false, `${label}.dryRunPlan.dryRunCallsLiveEndpoints`); } - assert.ok(Number.isInteger(step.order), `${stepLabel}.order`); - if (Object.hasOwn(step, "blockerOrder")) { - assert.ok(Number.isInteger(step.blockerOrder), `${stepLabel}.blockerOrder`); + if (Object.hasOwn(report.dryRunPlan, "liveWriteWillRun")) { + assert.equal(report.dryRunPlan.liveWriteWillRun, false, `${label}.dryRunPlan.liveWriteWillRun`); } - assert.match(step.priority, /^P[0-3]$/, `${stepLabel}.priority`); - assertStringArray(step.scopes, `${stepLabel}.scopes`, { minLength: 1 }); - assertStringArray(step.sourceIssues, `${stepLabel}.sourceIssues`, { minLength: 1 }); - assertString(step.rationale, `${stepLabel}.rationale`); - assertString(step.action, `${stepLabel}.action`); - assertString(step.evidenceRequired, `${stepLabel}.evidenceRequired`); - } - - assertStringArray(report.validationCommands, `${label}.validationCommands`, { - minLength: requiredAggregatorV2Commands.length - }); - for (const requiredCommand of requiredAggregatorV2Commands) { - assert.ok( - report.validationCommands.includes(requiredCommand), - `${label}.validationCommands missing ${requiredCommand}` + if (Object.hasOwn(report.dryRunPlan, "evidenceLevel")) { + assert.equal(report.dryRunPlan.evidenceLevel, "DRY-RUN", `${label}.dryRunPlan.evidenceLevel`); + } + assertObject(report.liveOperation, `${label}.liveOperation`); + assert.notEqual( + report.liveOperation.status, + "pass", + `${label}.liveOperation.status cannot be pass when only a DRY-RUN plan is present` ); + for (const field of ["operationId", "traceId", "auditId", "evidenceId"]) { + if (Object.hasOwn(report.liveOperation, field)) { + assert.equal(report.liveOperation[field], "not_observed", `${label}.liveOperation.${field} must remain not_observed for DRY-RUN evidence`); + } + } } - - await assertAggregatorNonPromotionBoundaries(report, label); } -async function main() { - const reportFiles = await collectReportFiles(); - assert.ok(reportFiles.length >= 1, "expected at least one dev-gate report JSON file"); - - for (const reportFile of reportFiles) { - await validateReport(reportFile); +async function main(argv) { + if (argv.length === 0) { + process.stdout.write(`${JSON.stringify({ + ok: true, + status: "skipped", + reason: "repository report files are disabled; pass explicit temporary JSON paths to validate them" + })}\n`); + return; } - console.log(`validated ${reportFiles.length} dev-gate report JSON file(s)`); + const results = []; + for (const inputPath of argv) { + const { relativePath, report } = await readJson(inputPath); + validateReportShape(report, relativePath); + results.push(relativePath); + } + + process.stdout.write(`${JSON.stringify({ + ok: true, + status: "pass", + validated: results.length, + files: results + })}\n`); } -await main(); +try { + await main(process.argv.slice(2)); +} catch (error) { + process.stderr.write(`${JSON.stringify({ + ok: false, + status: "blocked", + error: error instanceof Error ? error.message : String(error) + })}\n`); + process.exitCode = 1; +} diff --git a/scripts/validate-m2-deploy-smoke-active.mjs b/scripts/validate-m2-deploy-smoke-active.mjs index a49ce308..07abe7ef 100644 --- a/scripts/validate-m2-deploy-smoke-active.mjs +++ b/scripts/validate-m2-deploy-smoke-active.mjs @@ -8,9 +8,10 @@ import { findForbiddenActiveDeprecatedEndpoints, REPORT_LIFECYCLE_VERSION } from "../internal/dev-report-lifecycle.mjs"; +import { tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const reportPath = path.join(repoRoot, "reports/dev-gate/dev-m2-deploy-smoke-active.json"); +const reportPath = tempReportPath("dev-m2-deploy-smoke-active.json"); function assertString(value, label) { assert.equal(typeof value, "string", `${label} must be a string`); diff --git a/scripts/validate-runtime-boundary.mjs b/scripts/validate-runtime-boundary.mjs index f0b33f5e..c0e066d2 100644 --- a/scripts/validate-runtime-boundary.mjs +++ b/scripts/validate-runtime-boundary.mjs @@ -216,14 +216,14 @@ function assertDurableRuntimeRunbook(value) { "Still blocked:", "No full M3, M4, or M5 acceptance is allowed while runtime durability is blocked", "node scripts/dev-runtime-provisioning.mjs --check", - "node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --write-report", - "node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --write-report", + "node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json", + "node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json", "node scripts/dev-runtime-migration.mjs --check", - "node scripts/dev-runtime-migration.mjs --dry-run --write-report", - "node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --write-report", - "node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --write-report", + "node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json", + "node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json", + "node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json", "node scripts/dev-runtime-postflight.mjs --check", - "node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --write-report" + "node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/dev-runtime-postflight-report.json" ]) { assertIncludes(value, expected, label); } diff --git a/tools/hwlab-cli/lib/cli.mjs b/tools/hwlab-cli/lib/cli.mjs index 8c53f925..147a2850 100644 --- a/tools/hwlab-cli/lib/cli.mjs +++ b/tools/hwlab-cli/lib/cli.mjs @@ -155,7 +155,7 @@ function mvpLiveBlocked(summary, command) { blocked: true, blockers: summary.blockers, requiredBeforeRetry: [ - "Clear all open blockers in reports/dev-gate/dev-mvp-gate-report.json.", + "Clear all open blockers in the owning GitHub issues and PR comments.", "Record live HWLAB DEV health for /health and /live on the frozen DEV endpoint.", "Keep the run DEV-only; do not use UniDesk runtime or PROD substitutes." ], diff --git a/web/hwlab-cloud-web/gate-summary.mjs b/web/hwlab-cloud-web/gate-summary.mjs index 09ab513b..936c9e06 100644 --- a/web/hwlab-cloud-web/gate-summary.mjs +++ b/web/hwlab-cloud-web/gate-summary.mjs @@ -1,8 +1,8 @@ -// Generated from reports/dev-gate/dev-mvp-gate-report.json and fixtures/mvp/m5-e2e/dry-run-plan.json. -// Run node scripts/export-web-gate-summary.mjs after changing those fixtures. +// Generated from source:mvp-gate-summary and fixtures/mvp/m5-e2e/dry-run-plan.json. +// Run node scripts/export-web-gate-summary.mjs after changing that source summary or fixture. export const gateSummary = { "generatedFrom": { - "gateReport": "reports/dev-gate/dev-mvp-gate-report.json", + "gateReport": "source:mvp-gate-summary", "m5Plan": "fixtures/mvp/m5-e2e/dry-run-plan.json" }, "issue": "pikasTech/HWLAB#56", @@ -13,149 +13,126 @@ export const gateSummary = { "gateStatus": "blocked", "blocked": true, "acceptanceLevel": "dev_mvp_gate", - "reportCommitId": "86d769525caf1db513b93cb67c2c8ee8863dd4b1", + "reportCommitId": "source-summary", "dryRunOnly": true, "devOnly": true, "prodDisabled": true, "environment": "dev", "endpoint": "http://74.48.78.17:16667", "namespace": "hwlab-dev", - "sourceSummary": "The DEV gate is anchored to the frozen matrix, gate contract, and M0-M5 milestone docs on origin/main.", + "sourceSummary": "The DEV gate is anchored to the frozen matrix, gate contract, and M0-M5 milestone source docs; progress and acceptance evidence live in GitHub issue or PR comments, not repository report files.", "localSmoke": { "status": "pass", "commands": [ "node scripts/validate-contract.mjs", - "node scripts/validate-m0-contract.mjs", "node scripts/validate-artifact-catalog.mjs", - "node scripts/validate-runtime-boundary.mjs", - "node scripts/validate-schema-drift-map.mjs", - "node scripts/validate-evidence-chain.mjs", "node scripts/m1-contract-smoke.mjs", - "node scripts/m2-dev-deploy-smoke.mjs --dry-run", - "node scripts/m3-hardware-loop-smoke.mjs", - "node scripts/m4-agent-loop-smoke.mjs" + "node scripts/m2-dev-deploy-smoke.mjs --dry-run" ], "evidence": [ - "validated 15 JSON files and 13 service ids", - "validated M0 contract examples: 7 JSON files, 13 service ids", - "validated 13 DEV artifact catalog services at 24eb3bf", - "validated HWLAB runtime boundary guard for 13 services; forbidden substitutes: unidesk-backend, provider-gateway, microservice-proxy", - "validated schema drift map with 59 aligned fields and 12 gaps", - "validated 2 evidence chain fixtures and 2 evidence records", - "[m1-smoke] passed", - "[m2-smoke] validated 11 service contracts", - "[m2-smoke] mode=dry-run endpoint=http://74.48.78.17:16667", - "[m2-smoke] no real DEV/PROD request was made", - "M3 hardware loop smoke passed", - "[m4-smoke] ok m4-agent-automation-loop-local-contract-smoke" + "Source contracts remain parseable.", + "MVP dry-run fixtures remain local/source only.", + "Repository report directory is forbidden by repo guard." ], - "summary": "M0-M4 contract and smoke checks pass locally without contacting the live DEV ingress." + "summary": "M0-M2 contract and smoke checks remain local/source scoped and do not create repository report files." }, "dryRun": { "status": "pass", "commands": [ "node tools/hwlab-cli/bin/hwlab-cli.mjs health", - "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", - "node scripts/m5-mvp-e2e-dry-run.mjs" + "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run" ], "evidence": [ - "hwlab-cli health -> status: ok, environment: dev, endpoint: http://74.48.78.17:16667, projects: 2", - "hwlab-cli dry-run -> 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls", - "M5 MVP E2E dry-run passed: 9 steps, 13 artifacts, 14 health contracts, 2 hardware operations, 2 evidence records, 0 network calls" + "Dry-run uses fixtures and performs no DEV/PROD mutations.", + "Dry-run evidence is not live acceptance." ], - "summary": "The M5 orchestration dry-run is green, but it remains fixture-only and does not establish a live DEV gate." + "summary": "The M5 orchestration dry-run is fixture-only and does not establish a live DEV gate." }, "devPreconditions": { "status": "blocked", "classification": "runtime durable adapter; skills injection; hardware loop", "requirements": [ - "http://74.48.78.17:16666/ returns the DEV browser entry", - "http://74.48.78.17:16667/health returns HWLAB DEV edge JSON", - "http://74.48.78.17:16667/health/live returns HWLAB cloud-api JSON with DB live readiness and runtime durability separated", - "Runtime durable adapter proves postgres schema, migration 0001_cloud_core_skeleton, and read-query readiness before M4/M5 can promote", - "Agent manager, skills service, and worker jobs expose explicit skill commit/version before M4/M5 can promote", - "M3 hardware loop proves box-simu-1 DO1 -> box-simu-2 DI1 via patch-panel", - "Keep the run DEV-only and avoid PROD, heavy e2e, secrets, restarts, or UniDesk substitution" + "Current DEV route and health checks must be rerun when needed and summarized in GitHub.", + "Runtime durable adapter proves postgres schema, migration, and read-query readiness before M4/M5 can promote.", + "Agent manager, skills service, and worker jobs expose explicit skill commit/version before M4/M5 can promote.", + "M3 hardware loop proves box-simu-1 DO1 to box-simu-2 DI1 via patch-panel.", + "Keep the run DEV-only and avoid PROD, heavy e2e, secrets, restarts, or UniDesk substitution." ], "commands": [ "curl -fsS --max-time 8 http://74.48.78.17:16666/", "curl -fsS --max-time 8 http://74.48.78.17:16667/health/live", "curl -fsS --max-time 8 http://74.48.78.17:16667/health", - "HWLAB_DEV_BOX_SIMU_1_URL=http://10.42.0.200:7201 HWLAB_DEV_BOX_SIMU_2_URL=http://10.42.0.208:7201 HWLAB_DEV_GATEWAY_SIMU_1_URL=http://10.42.0.209:7101 HWLAB_DEV_GATEWAY_SIMU_2_URL=http://10.42.0.204:7101 HWLAB_DEV_PATCH_PANEL_URL=http://10.42.0.205:7301 node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", + "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production", "node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production" ], "evidence": [ - "http://74.48.78.17:16666/ -> HTTP 200 text/html, title=HWLAB DEV MVP Gate", - "http://74.48.78.17:16667/health -> HTTP 200 serviceId=hwlab-edge-proxy status=ok commit=d881a50", - "http://74.48.78.17:16667/health/live -> HTTP 200 serviceId=hwlab-cloud-api status=degraded db.ready=true db.connected=true db.liveDbEvidence=true runtime.blocker=runtime_durable_adapter_query_blocked runtime.migration=0001_cloud_core_skeleton", - "dev-edge-health active report remains older DB-live evidence and is superseded for M4/M5 blocker naming by current d881a50 M4 preflight evidence", - "dev-m3-hardware-loop active report -> blocked, classification=hardware loop, summary=patch-panel active DO1 -> DI1 connection missing", - "dev-m4-agent-loop active report -> blocked, classification=runtime_durable_adapter_query_blocked, summary=runtime durable adapter query/migration readiness is blocked; skills commit/version injection is incomplete" + "Repository report files were removed and no longer qualify as current evidence.", + "Live DEV acceptance must be represented in #7, the owning issue, daily brief, or PR comments." ], - "summary": "Public DEV route/frp is reachable on 16666/16667, but live MVP remains blocked by runtime_durable_adapter_query_blocked, skills commit/version injection, and the M3 hardware loop topology; no DB-live-only blocker is counted as current M4/M5 evidence." + "summary": "Live MVP remains blocked until current DEV checks clear runtime durability, skills injection, and M3 hardware-loop blockers outside repository report files." }, "blockers": [ { "type": "runtime_blocker", "scope": "runtime-durable-adapter", "status": "open", - "summary": "cloud-api durable runtime adapter is blocked: runtime_durable_adapter_query_blocked; migration 0001_cloud_core_skeleton is missing or not checked." + "summary": "cloud-api durable runtime adapter readiness is blocked by runtime_durable_adapter_query_blocked; it must prove schema, migration, and read-query durability before M4/M5 promotion." }, { "type": "agent_blocker", "scope": "skills-commit-version-injection", "status": "open", - "summary": "DEV skills injection is incomplete: hwlab-agent-skills exposes HWLAB_SKILLS_COMMIT_ID=d881a50 but no HWLAB_SKILLS_VERSION; worker template only exposes session mode unless commit/version are injected." + "summary": "DEV skills injection must expose explicit skills commit and version across manager, skills, and worker paths." }, { "type": "runtime_blocker", "scope": "m3-hardware-loop-runtime", "status": "open", - "summary": "Live M3 smoke reached DEV simulators, but patch-panel active DO1 -> DI1 connection is missing." + "summary": "Live M3 must prove the patch-panel trusted loop with operation, trace, audit, and evidence identifiers." } ], "milestones": [ { "id": "M0", "status": "pass", - "commandCount": 6, - "evidenceCount": 6, - "summary": "Frozen contract, artifact, boundary, drift, and evidence fixtures are parseable and aligned to the DEV boundary." + "commandCount": 1, + "evidenceCount": 1, + "summary": "Frozen contracts remain source-ready." }, { "id": "M1", "status": "pass", "commandCount": 1, - "evidenceCount": 4, + "evidenceCount": 1, "summary": "The local M1 contract smoke passes." }, { "id": "M2", "status": "pass", "commandCount": 1, - "evidenceCount": 3, - "summary": "The deploy smoke remains fixture-only and validates the frozen route phases and artifact metadata." + "evidenceCount": 1, + "summary": "The deploy smoke validates source contracts without live acceptance." }, { "id": "M3", "status": "blocked", - "commandCount": 2, - "evidenceCount": 3, - "summary": "Local M3 remains green, but active DEV-LIVE M3 is blocked by hardware loop topology." + "commandCount": 1, + "evidenceCount": 1, + "summary": "Active DEV-LIVE M3 remains blocked by hardware loop topology until the trusted loop is proven." }, { "id": "M4", "status": "blocked", - "commandCount": 2, - "evidenceCount": 2, - "summary": "Local M4 remains green, but active DEV-LIVE M4 is blocked at runtime durable adapter readiness and skills commit/version injection before agent runtime scheduling." + "commandCount": 1, + "evidenceCount": 1, + "summary": "Active DEV-LIVE M4 remains blocked by runtime durable adapter readiness and skills commit/version injection." }, { "id": "M5", "status": "blocked", - "commandCount": 3, - "evidenceCount": 4, - "summary": "The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance is blocked by runtime durable adapter, skills injection, and hardware loop evidence." + "commandCount": 1, + "evidenceCount": 1, + "summary": "The M5 orchestration is green in dry-run mode, but active DEV-LIVE acceptance remains blocked." } ], "artifactCount": 13, diff --git a/web/hwlab-cloud-web/package.json b/web/hwlab-cloud-web/package.json index 804018c0..3c6c7d7d 100644 --- a/web/hwlab-cloud-web/package.json +++ b/web/hwlab-cloud-web/package.json @@ -7,8 +7,8 @@ "m3-readonly": "node scripts/m3-readonly-contract.mjs", "check": "node --test code-agent-facts.test.mjs code-agent-status.test.mjs wiring-status.test.mjs code-agent-m3-evidence.test.mjs scripts/sidebar-resize.test.mjs && node scripts/check.mjs", "build": "node scripts/build.mjs", - "layout": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --static --report ../../reports/dev-gate/dev-cloud-workbench-layout.json", - "layout:build": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --build --report ../../reports/dev-gate/dev-cloud-workbench-layout-build.json", - "layout:live": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report ../../reports/dev-gate/dev-cloud-workbench-layout-live.json" + "layout": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json", + "layout:build": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --build --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-build.json", + "layout:live": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-live.json" } }