From 92c3d200a10aa06b08131051e151b006e71b3880 Mon Sep 17 00:00:00 2001 From: Code Queue Review Date: Sun, 24 May 2026 06:10:48 +0000 Subject: [PATCH] feat: add cloud-api runtime DB image entrypoints --- cmd/hwlab-cloud-api/migrate.mjs | 12 +++++ cmd/hwlab-cloud-api/provision.mjs | 12 +++++ docs/reference/deployment-publish.md | 13 +++-- docs/reference/dev-runtime-boundary.md | 54 ++++++++++++++----- package.json | 2 +- scripts/src/dev-cd-apply.mjs | 4 +- scripts/src/dev-cd-apply.test.mjs | 18 +++++-- scripts/src/dev-runtime-migration.mjs | 12 ++++- scripts/src/dev-runtime-migration.test.mjs | 48 +++++++++++++++++ scripts/src/dev-runtime-provisioning.test.mjs | 31 +++++++++++ scripts/validate-runtime-boundary.mjs | 38 +++++++++++++ 11 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 cmd/hwlab-cloud-api/migrate.mjs create mode 100644 cmd/hwlab-cloud-api/provision.mjs diff --git a/cmd/hwlab-cloud-api/migrate.mjs b/cmd/hwlab-cloud-api/migrate.mjs new file mode 100644 index 00000000..619319f6 --- /dev/null +++ b/cmd/hwlab-cloud-api/migrate.mjs @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import { + formatDevRuntimeMigrationFailure, + runDevRuntimeMigrationCli +} from "../../scripts/src/dev-runtime-migration.mjs"; + +try { + process.exitCode = await runDevRuntimeMigrationCli(process.argv.slice(2)); +} catch (error) { + process.stdout.write(`${JSON.stringify(formatDevRuntimeMigrationFailure(error), null, 2)}\n`); + process.exitCode = 1; +} diff --git a/cmd/hwlab-cloud-api/provision.mjs b/cmd/hwlab-cloud-api/provision.mjs new file mode 100644 index 00000000..a698ff4f --- /dev/null +++ b/cmd/hwlab-cloud-api/provision.mjs @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import { + formatDevRuntimeProvisioningFailure, + runDevRuntimeProvisioningCli +} from "../../scripts/src/dev-runtime-provisioning.mjs"; + +try { + process.exitCode = await runDevRuntimeProvisioningCli(process.argv.slice(2)); +} catch (error) { + process.stdout.write(`${JSON.stringify(formatDevRuntimeProvisioningFailure(error), null, 2)}\n`); + process.exitCode = 1; +} diff --git a/docs/reference/deployment-publish.md b/docs/reference/deployment-publish.md index 9d9b613e..bb419668 100644 --- a/docs/reference/deployment-publish.md +++ b/docs/reference/deployment-publish.md @@ -390,15 +390,18 @@ 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 --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 cmd/hwlab-cloud-api/provision.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json +node cmd/hwlab-cloud-api/migrate.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 -Lease. Runners without explicit rollout authorization may run the source-only -`--check` variants and prepare PRs/artifacts, but must leave DEV apply, -rollout, live verification, and lock ownership to the host commander. +Lease. Provisioning and migration run from the current `hwlab-cloud-api` image +as stable image-internal operator entrypoints; they receive DB input only +through SecretRef-backed env and print only redacted structured status. Runners +without explicit rollout authorization may run the source-only `--check` +variants and prepare PRs/artifacts, but must leave DEV apply, rollout, live +verification, and lock ownership to the host commander. The previous verified manual path for `hwlab-cloud-web` was: diff --git a/docs/reference/dev-runtime-boundary.md b/docs/reference/dev-runtime-boundary.md index 135516ef..9c4b7505 100644 --- a/docs/reference/dev-runtime-boundary.md +++ b/docs/reference/dev-runtime-boundary.md @@ -220,11 +220,23 @@ below are true. ## DEV DB Provisioning Automation -The repo-owned DEV DB provisioning entrypoint is: +The cloud-api image owns a stable DEV DB provisioning entrypoint: + +```sh +node cmd/hwlab-cloud-api/provision.mjs --check +node cmd/hwlab-cloud-api/provision.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json +node cmd/hwlab-cloud-api/provision.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json +``` + +This is the preferred image-internal operator command for `hwlab-cloud-api`. +It delegates to the same repo-owned provisioning implementation as the root +script and receives admin/target DB inputs only through SecretRef-backed env. + +The repo-level compatibility entrypoint remains: ```sh node scripts/dev-runtime-provisioning.mjs --check -node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json ``` The default `--check` path validates the redacted target DB URL contract only: @@ -237,7 +249,7 @@ kubeconfig material. An authorized DEV provisioning apply is: ```sh -node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json ``` The apply path uses `HWLAB_CLOUD_DB_URL` from @@ -258,11 +270,27 @@ untracked manual DB write. ## Runtime Migration Automation -The repo-owned runtime migration entrypoint is: +The cloud-api image owns a stable runtime migration entrypoint: + +```sh +node cmd/hwlab-cloud-api/migrate.mjs --check +node cmd/hwlab-cloud-api/migrate.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json +node cmd/hwlab-cloud-api/migrate.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json +``` + +This is the preferred image-internal operator command for `hwlab-cloud-api`. +It delegates to the same repo-owned migration implementation as the root +script, so there is only one migration contract and one output format. The +cloud-api artifact build copies `cmd/`, `scripts/`, `internal/`, `package.json`, +and runtime dependencies into the image; the DEV CD runtime migration Job uses +`node cmd/hwlab-cloud-api/migrate.mjs` from the current cloud-api image and +injects DB inputs only through SecretRef-backed env. + +The repo-level compatibility entrypoint remains: ```sh node scripts/dev-runtime-migration.mjs --check -node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json ``` Those default paths are source-only. They validate @@ -275,14 +303,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 --report /tmp/hwlab-dev-gate/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 ``` 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 --report /tmp/hwlab-dev-gate/report.json +node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json ``` The apply command is scoped to DEV Postgres schema objects declared by @@ -306,7 +334,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 --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/dev-runtime-postflight-report.json ``` Default `--check` is source-only. Live mode first reads @@ -335,13 +363,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 --report /tmp/hwlab-dev-gate/report.json` | 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/dev-runtime-migration-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 --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 | +| 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/dev-runtime-provisioning-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/dev-runtime-migration-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/dev-runtime-migration-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/dev-runtime-postflight-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/package.json b/package.json index e8dcc51a..aafc2ddb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "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/code-agent-trace-store.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", + "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/code-agent-trace-store.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-cloud-api/provision.mjs && node --check cmd/hwlab-cloud-api/migrate.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 cmd/hwlab-cloud-api/provision.mjs --check && node cmd/hwlab-cloud-api/migrate.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", diff --git a/scripts/src/dev-cd-apply.mjs b/scripts/src/dev-cd-apply.mjs index 8f217c08..ada6da29 100644 --- a/scripts/src/dev-cd-apply.mjs +++ b/scripts/src/dev-cd-apply.mjs @@ -2681,7 +2681,7 @@ export async function runDevCdApply(argv, io = {}) { kind: "runtime-k8s-job", jobNamePrefix: "hwlab-runtime-provision", commandArgs: [ - "scripts/dev-runtime-provisioning.mjs", + "cmd/hwlab-cloud-api/provision.mjs", "--apply", "--confirm-dev", "--confirmed-non-production", @@ -2698,7 +2698,7 @@ export async function runDevCdApply(argv, io = {}) { kind: "runtime-k8s-job", jobNamePrefix: "hwlab-runtime-migrate", commandArgs: [ - "scripts/dev-runtime-migration.mjs", + "cmd/hwlab-cloud-api/migrate.mjs", "--apply", "--confirm-dev", "--confirmed-non-production", diff --git a/scripts/src/dev-cd-apply.test.mjs b/scripts/src/dev-cd-apply.test.mjs index 013cf42f..d4b91bc2 100644 --- a/scripts/src/dev-cd-apply.test.mjs +++ b/scripts/src/dev-cd-apply.test.mjs @@ -1287,8 +1287,7 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a 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")); - const provisioningCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-provisioning.mjs")); - const migrationCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-migration.mjs")); + const provisioningCall = commandLog.find((entry) => entry.args.includes("cmd/hwlab-cloud-api/provision.mjs")); const postflightCall = commandLog.find((entry) => entry.args.includes("scripts/dev-runtime-postflight.mjs")); const refreshCall = commandLog.find((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")); assert.ok(publishCall?.env.HWLAB_CD_TRANSACTION_ID); @@ -1305,8 +1304,19 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a ); assert.ok(provisioningJobApply); assert.ok(migrationJobApply); - assert.equal(JSON.parse(provisioningJobApply.input).spec.template.spec.containers[0].args[0], "scripts/dev-runtime-provisioning.mjs"); - assert.equal(JSON.parse(migrationJobApply.input).spec.template.spec.containers[0].args[0], "scripts/dev-runtime-migration.mjs"); + const provisioningJob = JSON.parse(provisioningJobApply.input); + const migrationJob = JSON.parse(migrationJobApply.input); + assert.equal(provisioningJob.spec.template.spec.containers[0].args[0], "cmd/hwlab-cloud-api/provision.mjs"); + assert.equal(migrationJob.spec.template.spec.containers[0].args[0], "cmd/hwlab-cloud-api/migrate.mjs"); + assert.equal( + provisioningJob.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CD_TRANSACTION_ID"), + true + ); + assert.equal( + migrationJob.spec.template.spec.containers[0].env.some((entry) => entry.name === "HWLAB_CD_TRANSACTION_ID"), + true + ); + assert.equal(provisioningCall, undefined); assert.equal(postflightCall?.env.HWLAB_CD_TRANSACTION_ID, publishCall.env.HWLAB_CD_TRANSACTION_ID); assert.deepEqual(postflightCall.args.slice(0, 4), ["scripts/dev-runtime-postflight.mjs", "--live", "--confirm-dev", "--confirmed-non-production"]); assert.equal(refreshCall.args[refreshCall.args.indexOf("--target-ref") + 1], "abc1234abc1234abc1234abc1234abc1234abc1"); diff --git a/scripts/src/dev-runtime-migration.mjs b/scripts/src/dev-runtime-migration.mjs index 77a859e0..54a3574b 100644 --- a/scripts/src/dev-runtime-migration.mjs +++ b/scripts/src/dev-runtime-migration.mjs @@ -699,13 +699,15 @@ function usage() { } export function formatDevRuntimeMigrationFailure(error) { + const message = redactFailureText(error instanceof Error ? error.message : String(error)); return { issue, conclusion: { status: "blocked", summary: "DEV runtime migration command failed before producing a report." }, - error: error instanceof Error ? error.message : String(error), + error: message, + traceId: sha256(message).slice(0, 16), safety: { devOnly: true, prodAllowed: false, @@ -713,3 +715,11 @@ export function formatDevRuntimeMigrationFailure(error) { } }; } + +function redactFailureText(value) { + return String(value) + .replace(/postgres(?:ql)?:\/\/[^\s"'<>]+/giu, "[redacted-postgres-url]") + .replace(/(password\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]") + .replace(/(token\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]") + .replace(/(kubeconfig\s*[=:]\s*)[^\s,;]+/giu, "$1[redacted]"); +} diff --git a/scripts/src/dev-runtime-migration.test.mjs b/scripts/src/dev-runtime-migration.test.mjs index 9963ce9a..41381254 100644 --- a/scripts/src/dev-runtime-migration.test.mjs +++ b/scripts/src/dev-runtime-migration.test.mjs @@ -1,5 +1,8 @@ import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import path from "node:path"; import test from "node:test"; +import { promisify } from "node:util"; import { buildDevRuntimeMigrationReport, @@ -11,6 +14,9 @@ import { CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS } from "../../internal/db/schema.mjs"; +const execFileAsync = promisify(execFile); +const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); + test("source check is non-secret and does not attempt live DB access", async () => { const report = await buildDevRuntimeMigrationReport(parseArgs(["--check"]), { env: { @@ -33,6 +39,48 @@ test("source check is non-secret and does not attempt live DB access", async () assert.equal(JSON.stringify(report).includes("db.example.test"), false); }); +test("cloud-api image migration entrypoint exposes the same non-secret source check", async () => { + const { stdout } = await execFileAsync( + process.execPath, + ["cmd/hwlab-cloud-api/migrate.mjs", "--check"], + { + cwd: repoRoot, + env: { + ...process.env, + HWLAB_CLOUD_DB_URL: "postgresql://hwlab:secret-password@db.example.test:5432/hwlab?sslmode=require", + HWLAB_CLOUD_DB_SSL_MODE: "disable" + } + } + ); + const report = JSON.parse(stdout); + + assert.equal(report.conclusion.status, "ready"); + assert.equal(report.actions.liveDbReadAttempted, false); + assert.equal(report.actions.liveDbWriteAttempted, false); + assert.equal(report.safety.printsSecretValues, false); + assert.equal(report.safety.dbUrlValueRedacted, true); + assert.equal(report.migration.id, CLOUD_CORE_MIGRATION_ID); + assert.equal(JSON.stringify(report).includes("secret-password"), false); + assert.equal(JSON.stringify(report).includes("db.example.test"), false); +}); + +test("cloud-api image migration entrypoint redacts invalid DSN-shaped argv failures", async () => { + const secretArg = "postgresql://hwlab:secret-password@db.example.test:5432/hwlab?sslmode=require"; + await assert.rejects( + execFileAsync(process.execPath, ["cmd/hwlab-cloud-api/migrate.mjs", secretArg], { cwd: repoRoot }), + (error) => { + const report = JSON.parse(error.stdout); + assert.equal(report.conclusion.status, "blocked"); + assert.equal(report.error, "unknown argument: [redacted-postgres-url]"); + assert.match(report.traceId, /^[0-9a-f]{16}$/u); + assert.equal(report.safety.secretValuesPrinted, false); + assert.equal(JSON.stringify(report).includes("secret-password"), false); + assert.equal(JSON.stringify(report).includes("db.example.test"), false); + return true; + } + ); +}); + test("apply mode refuses missing DEV confirmation before touching query client", async () => { const queryClient = createFakeQueryClient({ migrationReady: false }); const report = await buildDevRuntimeMigrationReport(parseArgs(["--apply"]), { diff --git a/scripts/src/dev-runtime-provisioning.test.mjs b/scripts/src/dev-runtime-provisioning.test.mjs index fefb9174..6c97a9c3 100644 --- a/scripts/src/dev-runtime-provisioning.test.mjs +++ b/scripts/src/dev-runtime-provisioning.test.mjs @@ -1,11 +1,17 @@ import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import path from "node:path"; import test from "node:test"; +import { promisify } from "node:util"; import { buildDevRuntimeProvisioningReport, parseArgs } from "./dev-runtime-provisioning.mjs"; +const execFileAsync = promisify(execFile); +const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); + test("source check parses target role/database without leaking DB URL material", async () => { const report = await buildDevRuntimeProvisioningReport(parseArgs(["--check"]), { env: { @@ -25,6 +31,31 @@ test("source check parses target role/database without leaking DB URL material", assertNoFixtureSecrets(report); }); +test("cloud-api image provisioning entrypoint exposes non-secret source check", async () => { + const { stdout } = await execFileAsync( + process.execPath, + ["cmd/hwlab-cloud-api/provision.mjs", "--check"], + { + cwd: repoRoot, + env: { + ...process.env, + HWLAB_CLOUD_DB_URL: fixturePostgresUrl({ password: fixtureSecret("super") }), + HWLAB_CLOUD_DB_SSL_MODE: "disable" + } + } + ); + const report = JSON.parse(stdout); + + assert.equal(report.conclusion.status, "ready"); + assert.equal(report.actions.liveDbReadAttempted, false); + assert.equal(report.actions.liveDbWriteAttempted, false); + assert.equal(report.db.targetRoleNamePresent, true); + assert.equal(report.db.targetDatabaseNamePresent, true); + assert.equal(report.db.targetPasswordPresent, true); + assert.equal(report.safety.secretValuesPrinted, false); + assertNoFixtureSecrets(report); +}); + test("source check without injected DB URL is ready and performs no live access", async () => { const report = await buildDevRuntimeProvisioningReport(parseArgs(["--check"]), { env: {}, diff --git a/scripts/validate-runtime-boundary.mjs b/scripts/validate-runtime-boundary.mjs index c0e066d2..f3a2b671 100644 --- a/scripts/validate-runtime-boundary.mjs +++ b/scripts/validate-runtime-boundary.mjs @@ -215,6 +215,13 @@ function assertDurableRuntimeRunbook(value) { "Moved:", "Still blocked:", "No full M3, M4, or M5 acceptance is allowed while runtime durability is blocked", + "node cmd/hwlab-cloud-api/provision.mjs --check", + "node cmd/hwlab-cloud-api/provision.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json", + "node cmd/hwlab-cloud-api/provision.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json", + "node cmd/hwlab-cloud-api/migrate.mjs --check", + "node cmd/hwlab-cloud-api/migrate.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json", + "node cmd/hwlab-cloud-api/migrate.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json", + "DEV CD runtime migration Job uses", "node scripts/dev-runtime-provisioning.mjs --check", "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", @@ -229,6 +236,31 @@ function assertDurableRuntimeRunbook(value) { } } +function assertCloudApiImageRuntimeEntrypoints({ packageJson, ciJson, artifactPublisher, devCdApplySource }) { + const label = "cloud-api image runtime maintenance entrypoints"; + for (const expected of [ + "COPY internal ./internal", + "COPY cmd ./cmd", + "COPY scripts ./scripts", + "COPY package.json ./package.json" + ]) { + assertIncludes(artifactPublisher, expected, `${label} artifact publisher`); + } + for (const expected of [ + "cmd/hwlab-cloud-api/provision.mjs", + "cmd/hwlab-cloud-api/migrate.mjs" + ]) { + assertIncludes(devCdApplySource, expected, `${label} DEV CD job command`); + assertIncludes(packageJson.scripts?.check, `node --check ${expected}`, `${label} package check`); + assertIncludes(packageJson.scripts?.check, `node ${expected} --check`, `${label} package runtime check`); + } + assert.equal( + ciJson.commands?.some((command) => command.run === "npm run check"), + true, + `${label} CI must run npm run check` + ); +} + function assertGuardFixture(guard) { assert.equal(guard.guard, "hwlab-runtime-boundary"); assert.equal(guard.schemaVersion, "v1"); @@ -406,6 +438,12 @@ assertProdDisabled( await readJSON(guard.requiredK3sSkeleton.prodDisabledPath) ); assertDurableRuntimeRunbook(await readText("docs/reference/dev-runtime-boundary.md")); +assertCloudApiImageRuntimeEntrypoints({ + packageJson: await readJSON("package.json"), + ciJson: await readJSON("CI.json"), + artifactPublisher: await readText("scripts/dev-artifact-publish.mjs"), + devCdApplySource: await readText("scripts/src/dev-cd-apply.mjs") +}); console.log( `validated HWLAB runtime boundary guard for ${SERVICE_IDS.length} services; forbidden substitutes: ${forbiddenRuntimeSubstitutes.join(", ")}`