From f40f6ada34f0cd23c375f41fd1eb8be94d8cc6f1 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 08:46:18 +0200 Subject: [PATCH 1/5] fix: declare HWPOD native Cloud shell profile --- config/hwlab-node-lanes.yaml | 25 +++++++++++++------ scripts/src/hwlab-native-development.test.ts | 5 +++- scripts/src/hwlab-native-development.ts | 24 ++++++++++++++++-- scripts/src/hwlab-node-lanes.ts | 23 +++++++++++++++-- ...ab-node-web-probe-native-readiness.test.ts | 3 ++- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 35d8c2ea..a8f859af 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -259,6 +259,11 @@ lanes: bindHost: 0.0.0.0 probeHost: 127.0.0.1 port: 5175 + accessProfile: + id: hwpod-native-l1 + allowedNavIds: + - admin.hwpodGroups + startPath: /hwpods/devices caserun: publicExposure: publicBaseUrl: https://lab-dev-caserun.hwpod.com @@ -980,26 +985,32 @@ templates: hwpod: application: hwpod authentication: none - path: / + path: /hwpods/devices navigationTimeoutMs: 30000 settleMs: 700 commandTimeoutSeconds: 120 viewport: - width: 1440 - height: 900 + width: 1920 + height: 1080 readySelectors: - "[data-testid='hwpod-console']" - - "[data-resources] .resource" + - .platform-sidebar minBodyTextLength: 40 interaction: - selector: "[data-resources] .resource:first-child" - observedSelector: "[data-resources] .resource:first-child" - observedAttribute: aria-current + selector: .desktop-sidebar-toggle + observedSelector: .platform-sidebar + observedAttribute: data-collapsed failedResponseStatus: 400 criticalPathPrefixes: - /v1/hwpod - /health/ready screenshotName: native-hwpod-ready.png + layout: + requireNoDocumentOverflow: true + requireNoMainContentOverflow: true + mainContentSelector: .platform-content + internalScrollSelectors: + - .hwpod-workspace outputLimits: errors: 20 failedResponses: 20 diff --git a/scripts/src/hwlab-native-development.test.ts b/scripts/src/hwlab-native-development.test.ts index 40a41f1b..d7143be1 100644 --- a/scripts/src/hwlab-native-development.test.ts +++ b/scripts/src/hwlab-native-development.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { hwlabNativeDevelopmentEnvironment, hwlabNativeDevelopmentHelp } from "./hwlab-native-development"; +import { hwlabHwpodNativeDevelopmentEnvironment, hwlabNativeDevelopmentEnvironment, hwlabNativeDevelopmentHelp } from "./hwlab-native-development"; import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes"; import { resolveConfigRef } from "./ops/config-refs"; @@ -67,6 +67,9 @@ test("L1 HWPOD spec registry resolves PostgreSQL authority and frozen built-ins connectionTimeoutMs: 5000, }); expect(native!.healthTimeoutMs).toBe(6000); + expect(native!.web.accessProfile).toEqual({ id: "hwpod-native-l1", allowedNavIds: ["admin.hwpodGroups"], startPath: "/hwpods/devices" }); + const env = hwlabHwpodNativeDevelopmentEnvironment(native!); + expect(JSON.parse(env.HWPOD_WEB_ACCESS_PROFILE_JSON!)).toEqual(native!.web.accessProfile); expect(native!.specRegistry.builtInConfigRefs).toHaveLength(1); const resolved = resolveConfigRef(native!.specRegistry.builtInConfigRefs[0], "HWPOD built-in spec"); expect(resolved.value).toMatchObject({ kind: "Hwpod", metadata: { name: "constart-71freq-c" } }); diff --git a/scripts/src/hwlab-native-development.ts b/scripts/src/hwlab-native-development.ts index a46ac886..4817c577 100644 --- a/scripts/src/hwlab-native-development.ts +++ b/scripts/src/hwlab-native-development.ts @@ -17,6 +17,7 @@ export function hwlabNativeDevelopmentHelp(): Record { "bun scripts/cli.ts hwlab nodes native-development workbench status --node --lane ", "bun scripts/cli.ts hwlab nodes native-development workbench api|worker|web start|stop|restart|status|logs --node --lane ", "bun scripts/cli.ts hwlab nodes native-development workbench cli --node --lane ", + "bun scripts/cli.ts hwlab nodes native-development hwpod status --node --lane ", "bun scripts/cli.ts hwlab nodes native-development hwpod api|worker|web start|stop|restart|status|logs --node --lane ", "bun scripts/cli.ts hwlab nodes native-development caserun api|web start|stop|restart|status|logs --node --lane ", ], @@ -96,6 +97,24 @@ export function runHwlabNativeDevelopmentCommand(args: string[]): Record 0) throw new Error(`native-development hwpod status does not accept positional arguments: ${remaining.join(" ")}`); + const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", "hwpod", "service", "status"]; + const result = runCommand(command, spec.workspace, { timeoutMs: 30_000, env: { ...process.env, ...env } }); + const payload = parsePayload(result.stdout); + return { + ok: result.exitCode === 0 && payload.ok !== false, + command: "hwlab nodes native-development hwpod status", + node, + lane, + action: "status", + configSource: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.hwpod`, + userEndpoint: { url: `${native.publicExposure.publicBaseUrl}${(native as NonNullable).web.accessProfile.startPath}` }, + serviceResult: payload, + ...(result.exitCode === 0 ? {} : { error: { code: "hwlab_native_cli_failed", exitCode: result.exitCode, stderr: result.stderr.trim().slice(-2000) } }), + }; + } const service = parseService(serviceRaw, application); const action = parseAction(actionRaw); const command = ["bun", "tools/hwlab-cli/bin/hwlab-cli.ts", application, "service", service, action]; @@ -104,7 +123,7 @@ export function runHwlabNativeDevelopmentCommand(args: string[]): Record).web.accessProfile.startPath}` : `${native.publicExposure.publicBaseUrl}/${application === "workbench" ? "workbench" : "caserun"}` } : null; return { ok: result.exitCode === 0 && payload.ok !== false, @@ -281,7 +300,7 @@ function hwlabCaseRunNativeDevelopmentEnvironment(native: NonNullable): NodeJS.ProcessEnv { +export function hwlabHwpodNativeDevelopmentEnvironment(native: NonNullable): NodeJS.ProcessEnv { const authorizationSource = readEnvSourceFile({ root: "/root/.unidesk/.state/secrets", sourceRef: native.runtimeApiAuth.sourceRef, @@ -323,6 +342,7 @@ function hwlabHwpodNativeDevelopmentEnvironment(native: NonNullable { assert.equal(options.action, "script"); if (options.action !== "script") return; assert.equal(options.url, "https://lab-dev-hwpod.hwpod.com"); - assert.equal(options.viewport, "1440x900"); + assert.equal(options.viewport, "1920x1080"); assert.equal(options.authentication, "none"); + assert.equal(options.scriptSource.path, "builtin:native-readiness/hwpod"); }); test("CaseRun native readiness resolves the CaseRun YAML HTTPS origin", () => { From 896ab3763acc11456945f7c690db329f28358ded Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 09:01:06 +0200 Subject: [PATCH 2/5] fix: render HWPOD native Web runtime config --- config/hwlab-node-lanes.yaml | 3 +- scripts/src/hwlab-native-development.test.ts | 2 + scripts/src/hwlab-native-development.ts | 1 + scripts/src/hwlab-node-lanes.ts | 96 +++++++++++--------- 4 files changed, 57 insertions(+), 45 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index a8f859af..14e4033c 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -197,7 +197,7 @@ lanes: bindHost: 0.0.0.0 probeHost: 127.0.0.1 port: 5174 - runtimeConfig: + runtimeConfig: &hwlabV03NativeCloudWebRuntimeConfig displayTime: timeZone: Asia/Shanghai locale: zh-CN @@ -259,6 +259,7 @@ lanes: bindHost: 0.0.0.0 probeHost: 127.0.0.1 port: 5175 + runtimeConfig: *hwlabV03NativeCloudWebRuntimeConfig accessProfile: id: hwpod-native-l1 allowedNavIds: diff --git a/scripts/src/hwlab-native-development.test.ts b/scripts/src/hwlab-native-development.test.ts index d7143be1..4a204f36 100644 --- a/scripts/src/hwlab-native-development.test.ts +++ b/scripts/src/hwlab-native-development.test.ts @@ -69,6 +69,8 @@ test("L1 HWPOD spec registry resolves PostgreSQL authority and frozen built-ins expect(native!.healthTimeoutMs).toBe(6000); expect(native!.web.accessProfile).toEqual({ id: "hwpod-native-l1", allowedNavIds: ["admin.hwpodGroups"], startPath: "/hwpods/devices" }); const env = hwlabHwpodNativeDevelopmentEnvironment(native!); + expect(JSON.parse(env.HWPOD_WEB_RUNTIME_CONFIG!)).toEqual(native!.web.runtimeConfig); + expect(native!.web.runtimeConfig).toEqual(hwlabRuntimeLaneSpecForNode("v03", "NC01").nativeDevelopment!.workbench.web.runtimeConfig); expect(JSON.parse(env.HWPOD_WEB_ACCESS_PROFILE_JSON!)).toEqual(native!.web.accessProfile); expect(native!.specRegistry.builtInConfigRefs).toHaveLength(1); const resolved = resolveConfigRef(native!.specRegistry.builtInConfigRefs[0], "HWPOD built-in spec"); diff --git a/scripts/src/hwlab-native-development.ts b/scripts/src/hwlab-native-development.ts index 4817c577..558c9327 100644 --- a/scripts/src/hwlab-native-development.ts +++ b/scripts/src/hwlab-native-development.ts @@ -342,6 +342,7 @@ export function hwlabHwpodNativeDevelopmentEnvironment(native: NonNullable { const raw = asRecord(value, path); const api = asRecord(raw.api, `${path}.api`); From 88cbc1eccae103a6078f6f11667bbed7e4d8a493 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 09:07:04 +0200 Subject: [PATCH 3/5] fix: validate HWPOD native shell layout --- config/hwlab-node-lanes.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 14e4033c..ded2ac7b 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -1011,7 +1011,7 @@ templates: requireNoMainContentOverflow: true mainContentSelector: .platform-content internalScrollSelectors: - - .hwpod-workspace + - .platform-content outputLimits: errors: 20 failedResponses: 20 From 7f1dd07a075fec4a5088027798168bd535d9cab5 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 09:09:47 +0200 Subject: [PATCH 4/5] fix: allow HWPOD shell content scrolling --- config/hwlab-node-lanes.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index ded2ac7b..83f6267e 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -1008,7 +1008,7 @@ templates: screenshotName: native-hwpod-ready.png layout: requireNoDocumentOverflow: true - requireNoMainContentOverflow: true + requireNoMainContentOverflow: false mainContentSelector: .platform-content internalScrollSelectors: - .platform-content From 7317317be093626e7e56a988de573388cc6c6ac2 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 09:14:12 +0200 Subject: [PATCH 5/5] =?UTF-8?q?docs:=E5=9B=BA=E5=8C=96=20HWPOD=20native=20?= =?UTF-8?q?L1=20=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/reference/hwlab.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/reference/hwlab.md b/docs/reference/hwlab.md index af65ab82..11d74812 100644 --- a/docs/reference/hwlab.md +++ b/docs/reference/hwlab.md @@ -101,20 +101,34 @@ HWLAB v0.2/v0.3 仓库内 `docs/reference/spec-*`,以及已收编的 `cloud-wo - 当前 lane 入口: - development `NC01/v03` 前端/API 入口为 `https://lab-dev.hwpod.com`; - production release lane `NC01/production` 的正式入口为 `https://lab.hwpod.com`。 -- HWPOD L1 Native 是独立于 Cloud Web 的运行面: +- HWPOD L1 Native 是进程独立、界面复用完整 Cloud Web Shell 的运行面: - API、Temporal worker、Web 和固定端口必须从 owning YAML 读取: - `config/hwlab-node-lanes.yaml#lanes..targets..nativeDevelopment.hwpod`; - 启停和状态统一使用 `hwlab nodes native-development hwpod api|worker|web`; + - 聚合状态统一使用 + `bun scripts/cli.ts hwlab nodes native-development hwpod status --node --lane `; - L1 API、worker 和 Web 是 NC01 host 上的 native 进程: - 任何情况下都不依赖 CI/CD、GitOps、Argo、Kubernetes 或集群 rollout; - 正常启动、首次拉起、配置变更、合并后复测和故障处理均适用; + - L1 Web 必须复用完整 Cloud Web 的 `AppShell`、Router、总根导航和 + `/hwpods/devices` 页面,禁止另建 standalone SPA 或复制一套 HWPOD 页面; + - 可见根导航和首屏路径由 + `nativeDevelopment.hwpod.web.accessProfile` 声明,runtime config 从同一 owning YAML + 渲染并注入,禁止前端或启动脚本硬编码默认值; + - topology 首屏由 native HWPOD API 从 YAML-first/PostgreSQL spec registry 构建, + 禁止代理 deployment Cloud API 作为 L1 首屏数据源; - CLI `--over-api` 直接调用 owning YAML 固定端口上的 native API: - 同 host 回归使用 YAML probe host; - 禁止改走 Cloud API 或 Kubernetes Service; - L1 只验收本机 API、worker、Web 小回环: - 公网入口与 public-edge 是独立可选检查; - 不得成为 L1 启动、执行或完成条件; + - 浏览器验收统一使用 + `bun scripts/cli.ts web-probe native-readiness --node --lane --profile hwpod`; - HWPOD Web 无业务登录页时,由 owning YAML profile 声明 `authentication: none`,不得改用临时 Playwright 脚本或弱化浏览器错误断言。 + - native API 已接受操作且 Temporal workflow 已创建时,后续公共 + `/v1/hwpod-node-ops` 失败必须分类为外部 Cloud API 或 node 运行面故障, + 不得误判为 L1 API、worker 或 Temporal 未启动。 - HWPOD L0 与 L1 的 host 边界: - L0 `--local` 直接执行当前 host 的 function 和文件系统,不提供跨 host 路由; - 跨 host 的 Windows spec 在 API host 做 L0 时,只比较 compiler/plan 合同;